From 806347c94324625a6361d7775768212c532b966f Mon Sep 17 00:00:00 2001 From: root Date: Tue, 4 Jul 2017 20:11:07 +0000 Subject: [PATCH] Initial version completely based on SolrSearch for Omeka --- ESearchPlugin.php | 398 +++++ LICENSE | 5 +- README.md | 204 ++- RELEASE-LIST.md | 26 + addons/exhibits.json | 35 + addons/pages.json | 8 + composer.json | 5 + composer.lock | 267 +++ composer.phar | Bin 0 -> 1838958 bytes controllers/AdminController.php | 254 +++ controllers/ResultsController.php | 173 ++ forms/ESearch_Form_Reindex.php | 29 + forms/ESearch_Form_Results.php | 141 ++ forms/ESearch_Form_Server.php | 87 + helpers/ESearch_Helpers_Facet.php | 136 ++ helpers/ESearch_Helpers_Index.php | 287 ++++ helpers/ESearch_Helpers_View.php | 97 ++ jobs/ESearch_Job_Reindex.php | 25 + languages/en_US.mo | Bin 0 -> 3889 bytes languages/en_US.po | 222 +++ languages/nl_BE.mo | Bin 0 -> 2239 bytes languages/nl_BE.po | 223 +++ languages/template.base.pot | 17 + languages/template.pot | 222 +++ lib/SolrSearch/Addon/Addon.php | 160 ++ lib/SolrSearch/Addon/Config.php | 210 +++ lib/SolrSearch/Addon/Field.php | 66 + lib/SolrSearch/Addon/Indexer.php | 313 ++++ lib/SolrSearch/Addon/Manager.php | 259 +++ lib/SolrSearch/DbPager.php | 94 + lib/SolrSearch/Utils.php | 49 + lib/solr-php-client/Document.php | 367 ++++ lib/solr-php-client/Exception.php | 50 + .../HttpTransport/Abstract.php | 89 + lib/solr-php-client/HttpTransport/Curl.php | 198 +++ .../HttpTransport/CurlNoReuse.php | 196 +++ .../HttpTransport/FileGetContents.php | 216 +++ .../HttpTransport/Interface.php | 94 + .../HttpTransport/Response.php | 255 +++ .../HttpTransportException.php | 79 + .../InvalidArgumentException.php | 50 + .../NoServiceAvailableException.php | 50 + lib/solr-php-client/ParserException.php | 50 + lib/solr-php-client/Response.php | 247 +++ lib/solr-php-client/Service.php | 1187 +++++++++++++ lib/solr-php-client/Service/Balancer.php | 914 ++++++++++ lib/solr-php-client/Solr/HighlightOpts.php | 931 ++++++++++ lib/solr-php-client/Solr/SolrOpts.php | 277 +++ models/ESearchExclude.php | 20 + models/ESearchExcludeTable.php | 39 + models/ESearchField.php | 169 ++ models/ESearchFieldTable.php | 235 +++ plugin.ini | 12 + plugin.php | 45 + routes.ini | 8 + vendor/autoload.php | 7 + vendor/composer/ClassLoader.php | 445 +++++ vendor/composer/LICENSE | 21 + vendor/composer/autoload_classmap.php | 9 + vendor/composer/autoload_files.php | 10 + vendor/composer/autoload_namespaces.php | 9 + vendor/composer/autoload_psr4.php | 14 + vendor/composer/autoload_real.php | 70 + vendor/composer/autoload_static.php | 64 + vendor/composer/installed.json | 261 +++ .../elasticsearch/.github/CONTRIBUTING.md | 48 + .../elasticsearch/.github/ISSUE_TEMPLATE.md | 31 + .../.github/PULL_REQUEST_TEMPLATE.md | 14 + vendor/elasticsearch/elasticsearch/.gitignore | 26 + .../elasticsearch/elasticsearch/.gitmodules | 3 + vendor/elasticsearch/elasticsearch/.php_cs | 22 + .../elasticsearch/elasticsearch/.travis.yml | 59 + .../elasticsearch/BREAKING_CHANGES.md | 23 + .../elasticsearch/elasticsearch/CHANGELOG.md | 247 +++ vendor/elasticsearch/elasticsearch/LICENSE | 635 +++++++ vendor/elasticsearch/elasticsearch/NOTICE | 32 + vendor/elasticsearch/elasticsearch/README.md | 377 +++++ .../elasticsearch/elasticsearch/composer.json | 40 + .../docs/breaking-changes.asciidoc | 21 + .../docs/build/Elasticsearch/Client.asciidoc | 1174 +++++++++++++ .../Elasticsearch/ClientBuilder.asciidoc | 342 ++++ .../Namespaces/CatNamespace.asciidoc | 515 ++++++ .../Namespaces/ClusterNamespace.asciidoc | 210 +++ .../Namespaces/IndicesNamespace.asciidoc | 1071 ++++++++++++ .../Namespaces/IngestNamespace.asciidoc | 106 ++ .../Namespaces/NodesNamespace.asciidoc | 125 ++ .../Namespaces/SnapshotNamespace.asciidoc | 220 +++ .../Namespaces/TasksNamespace.asciidoc | 84 + .../elasticsearch/docs/build/PROJECT_VERSION | 1 + .../elasticsearch/docs/build/SAMI_VERSION | 1 + .../elasticsearch/docs/build/classes.asciidoc | 26 + .../docs/build/interfaces.asciidoc | 7 + .../docs/build/namespaces.asciidoc | 10 + .../elasticsearch/docs/build/renderer.index | 1 + .../elasticsearch/docs/community.asciidoc | 89 + .../elasticsearch/docs/configuration.asciidoc | 442 +++++ .../docs/connection-pool.asciidoc | 204 +++ .../elasticsearch/docs/crud.asciidoc | 245 +++ .../elasticsearch/docs/futures.asciidoc | 259 +++ .../docs/index-operations.asciidoc | 274 +++ .../elasticsearch/docs/index.asciidoc | 40 + .../elasticsearch/docs/installation.asciidoc | 79 + .../elasticsearch/docs/namespaces.asciidoc | 82 + .../elasticsearch/docs/overview.asciidoc | 8 + .../docs/per-request-configuration.asciidoc | 295 ++++ .../docs/php-version-requirement.asciidoc | 4 + .../docs/php_json_objects.asciidoc | 161 ++ .../elasticsearch/docs/quickstart.asciidoc | 269 +++ .../docs/search-operations.asciidoc | 274 +++ .../elasticsearch/docs/security.asciidoc | 115 ++ .../elasticsearch/docs/selectors.asciidoc | 110 ++ .../elasticsearch/docs/serializers.asciidoc | 167 ++ .../elasticsearch/phpunit-integration.xml | 23 + .../elasticsearch/elasticsearch/phpunit.xml | 24 + .../src/Elasticsearch/Client.php | 1508 +++++++++++++++++ .../src/Elasticsearch/ClientBuilder.php | 696 ++++++++ .../src/Elasticsearch/Common/EmptyLogger.php | 35 + .../Exceptions/AlreadyExpiredException.php | 16 + .../Exceptions/BadMethodCallException.php | 18 + .../Exceptions/BadRequest400Exception.php | 16 + .../ClientErrorResponseException.php | 16 + .../Exceptions/Conflict409Exception.php | 16 + .../Exceptions/Curl/CouldNotConnectToHost.php | 19 + .../Curl/CouldNotResolveHostException.php | 19 + .../Curl/OperationTimeoutException.php | 19 + .../Exceptions/ElasticsearchException.php | 16 + .../Exceptions/Forbidden403Exception.php | 16 + .../Exceptions/InvalidArgumentException.php | 18 + .../Common/Exceptions/MaxRetriesException.php | 16 + .../Common/Exceptions/Missing404Exception.php | 16 + .../Exceptions/NoDocumentsToGetException.php | 16 + .../Exceptions/NoNodesAvailableException.php | 16 + .../Exceptions/NoShardAvailableException.php | 16 + .../Exceptions/RequestTimeout408Exception.php | 16 + .../Exceptions/RoutingMissingException.php | 17 + .../Common/Exceptions/RuntimeException.php | 16 + .../ScriptLangNotSupportedException.php | 16 + .../Serializer/JsonErrorException.php | 69 + .../ServerErrorResponseException.php | 16 + .../Common/Exceptions/TransportException.php | 16 + .../Exceptions/UnexpectedValueException.php | 18 + .../ConnectionPool/AbstractConnectionPool.php | 86 + .../ConnectionPoolInterface.php | 29 + .../Selectors/RandomSelector.php | 29 + .../Selectors/RoundRobinSelector.php | 36 + .../Selectors/SelectorInterface.php | 24 + .../Selectors/StickyRoundRobinSelector.php | 47 + .../ConnectionPool/SimpleConnectionPool.php | 34 + .../ConnectionPool/SniffingConnectionPool.php | 155 ++ .../ConnectionPool/StaticConnectionPool.php | 93 + .../StaticNoPingConnectionPool.php | 76 + .../Elasticsearch/Connections/Connection.php | 718 ++++++++ .../Connections/ConnectionFactory.php | 67 + .../ConnectionFactoryInterface.php | 35 + .../Connections/ConnectionInterface.php | 99 ++ .../Endpoints/AbstractEndpoint.php | 303 ++++ .../src/Elasticsearch/Endpoints/Bulk.php | 83 + .../Endpoints/BulkEndpointInterface.php | 25 + .../Elasticsearch/Endpoints/Cat/Aliases.php | 76 + .../Endpoints/Cat/Allocation.php | 76 + .../src/Elasticsearch/Endpoints/Cat/Count.php | 56 + .../Elasticsearch/Endpoints/Cat/Fielddata.php | 74 + .../Elasticsearch/Endpoints/Cat/Health.php | 52 + .../src/Elasticsearch/Endpoints/Cat/Help.php | 47 + .../Elasticsearch/Endpoints/Cat/Indices.php | 60 + .../Elasticsearch/Endpoints/Cat/Master.php | 51 + .../Elasticsearch/Endpoints/Cat/NodeAttrs.php | 51 + .../src/Elasticsearch/Endpoints/Cat/Nodes.php | 52 + .../Endpoints/Cat/PendingTasks.php | 51 + .../Elasticsearch/Endpoints/Cat/Plugins.php | 51 + .../Elasticsearch/Endpoints/Cat/Recovery.php | 57 + .../Endpoints/Cat/Repositories.php | 51 + .../Elasticsearch/Endpoints/Cat/Segments.php | 63 + .../Elasticsearch/Endpoints/Cat/Shards.php | 57 + .../Elasticsearch/Endpoints/Cat/Snapshots.php | 73 + .../src/Elasticsearch/Endpoints/Cat/Tasks.php | 52 + .../Endpoints/Cat/ThreadPool.php | 55 + .../Elasticsearch/Endpoints/ClearScroll.php | 93 + .../Endpoints/Cluster/AllocationExplain.php | 62 + .../Endpoints/Cluster/Health.php | 59 + .../Cluster/Nodes/AbstractNodesEndpoint.php | 47 + .../Endpoints/Cluster/Nodes/HotThreads.php | 51 + .../Endpoints/Cluster/Nodes/Info.php | 77 + .../Endpoints/Cluster/Nodes/Shutdown.php | 49 + .../Endpoints/Cluster/Nodes/Stats.php | 111 ++ .../Endpoints/Cluster/PendingTasks.php | 46 + .../Endpoints/Cluster/Reroute.php | 68 + .../Endpoints/Cluster/Settings/Get.php | 48 + .../Endpoints/Cluster/Settings/Put.php | 63 + .../Elasticsearch/Endpoints/Cluster/State.php | 82 + .../Elasticsearch/Endpoints/Cluster/Stats.php | 70 + .../src/Elasticsearch/Endpoints/Count.php | 86 + .../Endpoints/CountPercolate.php | 90 + .../src/Elasticsearch/Endpoints/Create.php | 107 ++ .../src/Elasticsearch/Endpoints/Delete.php | 75 + .../Elasticsearch/Endpoints/DeleteByQuery.php | 103 ++ .../src/Elasticsearch/Endpoints/Exists.php | 74 + .../src/Elasticsearch/Endpoints/Explain.php | 100 ++ .../Elasticsearch/Endpoints/FieldStats.php | 73 + .../src/Elasticsearch/Endpoints/Get.php | 113 ++ .../src/Elasticsearch/Endpoints/Index.php | 123 ++ .../Indices/Alias/AbstractAliasEndpoint.php | 38 + .../Endpoints/Indices/Alias/Delete.php | 83 + .../Endpoints/Indices/Alias/Exists.php | 77 + .../Endpoints/Indices/Alias/Get.php | 77 + .../Endpoints/Indices/Alias/Put.php | 97 ++ .../Endpoints/Indices/Aliases/Get.php | 75 + .../Endpoints/Indices/Aliases/Update.php | 77 + .../Endpoints/Indices/Analyze.php | 79 + .../Endpoints/Indices/Cache/Clear.php | 64 + .../Endpoints/Indices/ClearCache.php | 62 + .../Elasticsearch/Endpoints/Indices/Close.php | 61 + .../Endpoints/Indices/Create.php | 77 + .../Endpoints/Indices/Delete.php | 51 + .../Endpoints/Indices/Exists.php | 60 + .../Endpoints/Indices/Exists/Types.php | 63 + .../Endpoints/Indices/Field/Get.php | 88 + .../Elasticsearch/Endpoints/Indices/Flush.php | 66 + .../Endpoints/Indices/ForceMerge.php | 57 + .../Endpoints/Indices/Gateway/Snapshot.php | 52 + .../Elasticsearch/Endpoints/Indices/Get.php | 79 + .../Endpoints/Indices/Mapping/Delete.php | 63 + .../Endpoints/Indices/Mapping/Get.php | 59 + .../Endpoints/Indices/Mapping/GetField.php | 79 + .../Endpoints/Indices/Mapping/Put.php | 96 ++ .../Elasticsearch/Endpoints/Indices/Open.php | 61 + .../Endpoints/Indices/Recovery.php | 52 + .../Endpoints/Indices/Refresh.php | 54 + .../Endpoints/Indices/Rollover.php | 109 ++ .../Elasticsearch/Endpoints/Indices/Seal.php | 50 + .../Endpoints/Indices/Segments.php | 54 + .../Endpoints/Indices/Settings/Get.php | 79 + .../Endpoints/Indices/Settings/Put.php | 86 + .../Endpoints/Indices/ShardStores.php | 59 + .../Endpoints/Indices/Shrink.php | 101 ++ .../Endpoints/Indices/Snapshotindex.php | 52 + .../Elasticsearch/Endpoints/Indices/Stats.php | 86 + .../Endpoints/Indices/Status.php | 56 + .../Template/AbstractTemplateEndpoint.php | 32 + .../Endpoints/Indices/Template/Delete.php | 78 + .../Endpoints/Indices/Template/Exists.php | 77 + .../Endpoints/Indices/Template/Get.php | 73 + .../Endpoints/Indices/Template/Put.php | 110 ++ .../Endpoints/Indices/Type/Exists.php | 60 + .../Endpoints/Indices/Upgrade/Get.php | 65 + .../Endpoints/Indices/Upgrade/Post.php | 65 + .../Endpoints/Indices/Validate/Query.php | 71 + .../Endpoints/Indices/ValidateQuery.php | 77 + .../src/Elasticsearch/Endpoints/Info.php | 42 + .../Endpoints/Ingest/Pipeline/Delete.php | 54 + .../Endpoints/Ingest/Pipeline/Get.php | 51 + .../Endpoints/Ingest/Pipeline/Put.php | 71 + .../Endpoints/Ingest/Simulate.php | 65 + .../Elasticsearch/Endpoints/MPercolate.php | 78 + .../Elasticsearch/Endpoints/MTermVectors.php | 70 + .../src/Elasticsearch/Endpoints/Mget.php | 93 + .../src/Elasticsearch/Endpoints/Msearch.php | 104 ++ .../src/Elasticsearch/Endpoints/Percolate.php | 98 ++ .../src/Elasticsearch/Endpoints/Ping.php | 42 + .../src/Elasticsearch/Endpoints/Reindex.php | 63 + .../Endpoints/RenderSearchTemplate.php | 77 + .../Elasticsearch/Endpoints/Script/Delete.php | 79 + .../Elasticsearch/Endpoints/Script/Get.php | 79 + .../Elasticsearch/Endpoints/Script/Put.php | 96 ++ .../src/Elasticsearch/Endpoints/Scroll.php | 102 ++ .../src/Elasticsearch/Endpoints/Search.php | 111 ++ .../Elasticsearch/Endpoints/SearchShards.php | 58 + .../Endpoints/SearchTemplate.php | 79 + .../Endpoints/Snapshot/Create.php | 119 ++ .../Endpoints/Snapshot/Delete.php | 101 ++ .../Elasticsearch/Endpoints/Snapshot/Get.php | 102 ++ .../Endpoints/Snapshot/Repository/Create.php | 107 ++ .../Endpoints/Snapshot/Repository/Delete.php | 77 + .../Endpoints/Snapshot/Repository/Get.php | 70 + .../Endpoints/Snapshot/Repository/Verify.php | 74 + .../Endpoints/Snapshot/Restore.php | 119 ++ .../Endpoints/Snapshot/Status.php | 100 ++ .../Elasticsearch/Endpoints/Source/Get.php | 78 + .../src/Elasticsearch/Endpoints/Suggest.php | 85 + .../Elasticsearch/Endpoints/Tasks/Cancel.php | 71 + .../src/Elasticsearch/Endpoints/Tasks/Get.php | 68 + .../Endpoints/Tasks/TasksList.php | 53 + .../Endpoints/Template/Delete.php | 51 + .../Elasticsearch/Endpoints/Template/Get.php | 51 + .../Elasticsearch/Endpoints/Template/Put.php | 68 + .../Elasticsearch/Endpoints/TermVectors.php | 95 ++ .../src/Elasticsearch/Endpoints/Update.php | 99 ++ .../Elasticsearch/Endpoints/UpdateByQuery.php | 119 ++ .../Helper/Iterators/SearchHitIterator.php | 161 ++ .../Iterators/SearchResponseIterator.php | 175 ++ .../Namespaces/AbstractNamespace.php | 77 + .../Namespaces/BooleanRequestWrapper.php | 58 + .../Elasticsearch/Namespaces/CatNamespace.php | 493 ++++++ .../Namespaces/ClusterNamespace.php | 205 +++ .../Namespaces/IndicesNamespace.php | 1163 +++++++++++++ .../Namespaces/IngestNamespace.php | 114 ++ .../Namespaces/NamespaceBuilderInterface.php | 37 + .../Namespaces/NodesNamespace.php | 134 ++ .../Namespaces/SnapshotNamespace.php | 235 +++ .../Namespaces/TasksNamespace.php | 91 + .../Serializers/ArrayToJSONSerializer.php | 49 + .../EverythingToJSONSerializer.php | 45 + .../Serializers/SerializerInterface.php | 34 + .../Serializers/SmartSerializer.php | 99 ++ .../src/Elasticsearch/Transport.php | 172 ++ .../tests/Elasticsearch/Tests/ClientTest.php | 420 +++++ .../Selectors/RoundRobinSelectorTest.php | 82 + .../StickyRoundRobinSelectorTest.php | 65 + .../SniffingConnectionPoolIntegrationTest.php | 25 + .../SniffingConnectionPoolTest.php | 426 +++++ .../StaticConnectionPoolTest.php | 231 +++ .../Tests/Endpoints/AbstractEndpointTest.php | 36 + .../Iterators/SearchResponseIteratorTest.php | 167 ++ .../Tests/RegisteredNamespaceTest.php | 65 + .../Serializers/ArrayToJSONSerializerTest.php | 51 + .../EverythingToJSONSerializerTest.php | 52 + .../Elasticsearch/Tests/YamlRunnerTest.php | 975 +++++++++++ .../elasticsearch/tests/bootstrap.php | 16 + .../travis/download_and_run_es.sh | 30 + .../elasticsearch/travis/generate_docs.sh | 1 + .../elasticsearch/util/EnsureClusterAlive.php | 31 + .../elasticsearch/util/RestSpecRunner.php | 29 + .../elasticsearch/util/SpecParser.php | 215 +++ .../elasticsearch/util/docsConfig.php | 30 + .../util/docstheme/layout/base.twig | 13 + .../elasticsearch/util/docstheme/macros.twig | 138 ++ .../elasticsearch/util/docstheme/manifest.yml | 9 + .../util/docstheme/pages/class.twig | 82 + .../util/docstheme/pages/classes.twig | 27 + .../util/docstheme/pages/interfaces.twig | 23 + .../util/docstheme/pages/namespaces.twig | 23 + .../util/templates/endpoint.twig | 150 ++ vendor/guzzlehttp/ringphp/.gitignore | 4 + vendor/guzzlehttp/ringphp/.travis.yml | 22 + vendor/guzzlehttp/ringphp/CHANGELOG.md | 54 + vendor/guzzlehttp/ringphp/LICENSE | 19 + 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 + vendor/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 ++++ vendor/guzzlehttp/ringphp/docs/testing.rst | 74 + vendor/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 +++ vendor/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 ++ vendor/guzzlehttp/ringphp/tests/bootstrap.php | 11 + vendor/guzzlehttp/streams/.gitignore | 6 + vendor/guzzlehttp/streams/.travis.yml | 17 + vendor/guzzlehttp/streams/CHANGELOG.rst | 94 + vendor/guzzlehttp/streams/LICENSE | 19 + vendor/guzzlehttp/streams/Makefile | 19 + vendor/guzzlehttp/streams/README.rst | 36 + vendor/guzzlehttp/streams/composer.json | 28 + vendor/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 + vendor/guzzlehttp/streams/src/FnStream.php | 147 ++ .../streams/src/GuzzleStreamWrapper.php | 117 ++ .../guzzlehttp/streams/src/InflateStream.php | 27 + .../guzzlehttp/streams/src/LazyOpenStream.php | 37 + vendor/guzzlehttp/streams/src/LimitStream.php | 161 ++ .../streams/src/MetadataStreamInterface.php | 11 + .../guzzlehttp/streams/src/NoSeekStream.php | 25 + vendor/guzzlehttp/streams/src/NullStream.php | 78 + vendor/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 +++ vendor/guzzlehttp/streams/tests/UtilsTest.php | 155 ++ vendor/psr/log/.gitignore | 1 + vendor/psr/log/LICENSE | 19 + vendor/psr/log/Psr/Log/AbstractLogger.php | 128 ++ .../log/Psr/Log/InvalidArgumentException.php | 7 + vendor/psr/log/Psr/Log/LogLevel.php | 18 + .../psr/log/Psr/Log/LoggerAwareInterface.php | 18 + vendor/psr/log/Psr/Log/LoggerAwareTrait.php | 26 + vendor/psr/log/Psr/Log/LoggerInterface.php | 123 ++ vendor/psr/log/Psr/Log/LoggerTrait.php | 140 ++ vendor/psr/log/Psr/Log/NullLogger.php | 28 + .../log/Psr/Log/Test/LoggerInterfaceTest.php | 140 ++ vendor/psr/log/README.md | 45 + vendor/psr/log/composer.json | 26 + vendor/react/promise/.gitignore | 5 + vendor/react/promise/.travis.yml | 22 + vendor/react/promise/CHANGELOG.md | 96 ++ vendor/react/promise/LICENSE | 22 + vendor/react/promise/README.md | 840 +++++++++ vendor/react/promise/composer.json | 29 + vendor/react/promise/phpunit.xml.dist | 28 + .../src/CancellablePromiseInterface.php | 11 + .../react/promise/src/CancellationQueue.php | 55 + vendor/react/promise/src/Deferred.php | 60 + .../promise/src/Exception/LengthException.php | 7 + .../promise/src/ExtendedPromiseInterface.php | 26 + vendor/react/promise/src/FulfilledPromise.php | 68 + vendor/react/promise/src/LazyPromise.php | 63 + vendor/react/promise/src/Promise.php | 216 +++ vendor/react/promise/src/PromiseInterface.php | 11 + .../react/promise/src/PromisorInterface.php | 11 + vendor/react/promise/src/RejectedPromise.php | 76 + .../src/UnhandledRejectionException.php | 31 + vendor/react/promise/src/functions.php | 244 +++ .../react/promise/src/functions_include.php | 5 + .../promise/tests/CancellationQueueTest.php | 100 ++ vendor/react/promise/tests/DeferredTest.php | 42 + .../promise/tests/FulfilledPromiseTest.php | 50 + .../react/promise/tests/FunctionAllTest.php | 114 ++ .../react/promise/tests/FunctionAnyTest.php | 204 +++ .../tests/FunctionCheckTypehintTest.php | 118 ++ .../react/promise/tests/FunctionMapTest.php | 198 +++ .../react/promise/tests/FunctionRaceTest.php | 211 +++ .../promise/tests/FunctionReduceTest.php | 347 ++++ .../promise/tests/FunctionRejectTest.php | 64 + .../promise/tests/FunctionResolveTest.php | 171 ++ .../react/promise/tests/FunctionSomeTest.php | 258 +++ .../react/promise/tests/LazyPromiseTest.php | 107 ++ .../PromiseAdapter/CallbackPromiseAdapter.php | 40 + .../PromiseAdapterInterface.php | 14 + vendor/react/promise/tests/PromiseTest.php | 84 + .../tests/PromiseTest/CancelTestTrait.php | 231 +++ .../tests/PromiseTest/FullTestTrait.php | 15 + .../tests/PromiseTest/NotifyTestTrait.php | 336 ++++ .../PromiseTest/PromiseFulfilledTestTrait.php | 351 ++++ .../PromiseTest/PromisePendingTestTrait.php | 68 + .../PromiseTest/PromiseRejectedTestTrait.php | 512 ++++++ .../PromiseTest/PromiseSettledTestTrait.php | 86 + .../tests/PromiseTest/RejectTestTrait.php | 368 ++++ .../tests/PromiseTest/ResolveTestTrait.php | 312 ++++ .../promise/tests/RejectedPromiseTest.php | 50 + .../react/promise/tests/Stub/CallableStub.php | 10 + vendor/react/promise/tests/TestCase.php | 43 + vendor/react/promise/tests/bootstrap.php | 7 + .../fixtures/SimpleFulfilledTestPromise.php | 21 + .../fixtures/SimpleFulfilledTestThenable.php | 21 + .../fixtures/SimpleRejectedTestPromise.php | 21 + .../tests/fixtures/SimpleTestCancellable.php | 13 + .../SimpleTestCancellableThenable.php | 18 + views/admin/admin/collections.php | 35 + views/admin/admin/fields.php | 72 + views/admin/admin/partials/field.php | 45 + views/admin/admin/partials/navigation.php | 20 + views/admin/admin/reindex.php | 27 + views/admin/admin/results.php | 26 + views/admin/admin/server.php | 26 + views/admin/javascripts/accordion.js | 18 + views/shared/common/pagination.php | 19 + views/shared/css/fields.css | 1171 +++++++++++++ .../css/img/glyphicons-halflings-white.png | Bin 0 -> 8777 bytes views/shared/css/img/glyphicons-halflings.png | Bin 0 -> 13826 bytes views/shared/css/img/remove.gif | Bin 0 -> 111 bytes views/shared/css/img/separator.gif | Bin 0 -> 112 bytes views/shared/css/results.css | 2 + views/shared/css/results.css.map | 7 + views/shared/css/sass/_buttons.scss | 199 +++ views/shared/css/sass/_mixins.scss | 1359 +++++++++++++++ views/shared/css/sass/_sprites.scss | 191 +++ views/shared/css/sass/_variables.scss | 209 +++ views/shared/css/sass/fields.scss | 26 + views/shared/css/sass/results.scss | 78 + views/shared/results/index.php | 161 ++ 519 files changed, 61743 insertions(+), 4 deletions(-) create mode 100755 ESearchPlugin.php mode change 100644 => 100755 LICENSE mode change 100644 => 100755 README.md create mode 100755 RELEASE-LIST.md create mode 100755 addons/exhibits.json create mode 100755 addons/pages.json create mode 100644 composer.json create mode 100644 composer.lock create mode 100755 composer.phar create mode 100755 controllers/AdminController.php create mode 100755 controllers/ResultsController.php create mode 100755 forms/ESearch_Form_Reindex.php create mode 100755 forms/ESearch_Form_Results.php create mode 100755 forms/ESearch_Form_Server.php create mode 100755 helpers/ESearch_Helpers_Facet.php create mode 100755 helpers/ESearch_Helpers_Index.php create mode 100755 helpers/ESearch_Helpers_View.php create mode 100755 jobs/ESearch_Job_Reindex.php create mode 100755 languages/en_US.mo create mode 100755 languages/en_US.po create mode 100755 languages/nl_BE.mo create mode 100755 languages/nl_BE.po create mode 100755 languages/template.base.pot create mode 100755 languages/template.pot create mode 100755 lib/SolrSearch/Addon/Addon.php create mode 100755 lib/SolrSearch/Addon/Config.php create mode 100755 lib/SolrSearch/Addon/Field.php create mode 100755 lib/SolrSearch/Addon/Indexer.php create mode 100755 lib/SolrSearch/Addon/Manager.php create mode 100755 lib/SolrSearch/DbPager.php create mode 100755 lib/SolrSearch/Utils.php create mode 100755 lib/solr-php-client/Document.php create mode 100755 lib/solr-php-client/Exception.php create mode 100755 lib/solr-php-client/HttpTransport/Abstract.php create mode 100755 lib/solr-php-client/HttpTransport/Curl.php create mode 100755 lib/solr-php-client/HttpTransport/CurlNoReuse.php create mode 100755 lib/solr-php-client/HttpTransport/FileGetContents.php create mode 100755 lib/solr-php-client/HttpTransport/Interface.php create mode 100755 lib/solr-php-client/HttpTransport/Response.php create mode 100755 lib/solr-php-client/HttpTransportException.php create mode 100755 lib/solr-php-client/InvalidArgumentException.php create mode 100755 lib/solr-php-client/NoServiceAvailableException.php create mode 100755 lib/solr-php-client/ParserException.php create mode 100755 lib/solr-php-client/Response.php create mode 100755 lib/solr-php-client/Service.php create mode 100755 lib/solr-php-client/Service/Balancer.php create mode 100755 lib/solr-php-client/Solr/HighlightOpts.php create mode 100755 lib/solr-php-client/Solr/SolrOpts.php create mode 100755 models/ESearchExclude.php create mode 100755 models/ESearchExcludeTable.php create mode 100755 models/ESearchField.php create mode 100755 models/ESearchFieldTable.php create mode 100755 plugin.ini create mode 100755 plugin.php create mode 100755 routes.ini create mode 100644 vendor/autoload.php create mode 100644 vendor/composer/ClassLoader.php create mode 100644 vendor/composer/LICENSE create mode 100644 vendor/composer/autoload_classmap.php create mode 100644 vendor/composer/autoload_files.php create mode 100644 vendor/composer/autoload_namespaces.php create mode 100644 vendor/composer/autoload_psr4.php create mode 100644 vendor/composer/autoload_real.php create mode 100644 vendor/composer/autoload_static.php create mode 100644 vendor/composer/installed.json create mode 100644 vendor/elasticsearch/elasticsearch/.github/CONTRIBUTING.md create mode 100644 vendor/elasticsearch/elasticsearch/.github/ISSUE_TEMPLATE.md create mode 100644 vendor/elasticsearch/elasticsearch/.github/PULL_REQUEST_TEMPLATE.md create mode 100755 vendor/elasticsearch/elasticsearch/.gitignore create mode 100644 vendor/elasticsearch/elasticsearch/.gitmodules create mode 100644 vendor/elasticsearch/elasticsearch/.php_cs create mode 100644 vendor/elasticsearch/elasticsearch/.travis.yml create mode 100644 vendor/elasticsearch/elasticsearch/BREAKING_CHANGES.md create mode 100644 vendor/elasticsearch/elasticsearch/CHANGELOG.md create mode 100644 vendor/elasticsearch/elasticsearch/LICENSE create mode 100644 vendor/elasticsearch/elasticsearch/NOTICE create mode 100644 vendor/elasticsearch/elasticsearch/README.md create mode 100644 vendor/elasticsearch/elasticsearch/composer.json create mode 100644 vendor/elasticsearch/elasticsearch/docs/breaking-changes.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Client.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/ClientBuilder.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/CatNamespace.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/ClusterNamespace.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/IndicesNamespace.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/IngestNamespace.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/NodesNamespace.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/SnapshotNamespace.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/TasksNamespace.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/PROJECT_VERSION create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/SAMI_VERSION create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/classes.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/interfaces.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/namespaces.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/build/renderer.index create mode 100644 vendor/elasticsearch/elasticsearch/docs/community.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/configuration.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/connection-pool.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/crud.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/futures.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/index-operations.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/index.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/installation.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/namespaces.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/overview.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/per-request-configuration.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/php-version-requirement.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/php_json_objects.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/quickstart.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/search-operations.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/security.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/selectors.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/docs/serializers.asciidoc create mode 100644 vendor/elasticsearch/elasticsearch/phpunit-integration.xml create mode 100644 vendor/elasticsearch/elasticsearch/phpunit.xml create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Client.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/ClientBuilder.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/EmptyLogger.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/AlreadyExpiredException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadMethodCallException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadRequest400Exception.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ClientErrorResponseException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Conflict409Exception.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/CouldNotConnectToHost.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/CouldNotResolveHostException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/OperationTimeoutException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ElasticsearchException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Forbidden403Exception.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/InvalidArgumentException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/MaxRetriesException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Missing404Exception.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoDocumentsToGetException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoNodesAvailableException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoShardAvailableException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RequestTimeout408Exception.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RoutingMissingException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RuntimeException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ScriptLangNotSupportedException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Serializer/JsonErrorException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ServerErrorResponseException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/TransportException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/UnexpectedValueException.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/AbstractConnectionPool.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/ConnectionPoolInterface.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RandomSelector.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RoundRobinSelector.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/SelectorInterface.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/StickyRoundRobinSelector.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/SimpleConnectionPool.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/SniffingConnectionPool.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticConnectionPool.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticNoPingConnectionPool.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/Connection.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactory.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactoryInterface.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionInterface.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/AbstractEndpoint.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Bulk.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/BulkEndpointInterface.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Aliases.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Allocation.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Count.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Fielddata.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Health.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Help.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Indices.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Master.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/NodeAttrs.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Nodes.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/PendingTasks.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Plugins.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Recovery.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Repositories.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Segments.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Shards.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Snapshots.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Tasks.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/ThreadPool.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/ClearScroll.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/AllocationExplain.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Health.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/AbstractNodesEndpoint.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/HotThreads.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Info.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Shutdown.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Stats.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/PendingTasks.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Reroute.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Settings/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Settings/Put.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/State.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Stats.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Count.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/CountPercolate.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Create.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Delete.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/DeleteByQuery.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Exists.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Explain.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/FieldStats.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Index.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/AbstractAliasEndpoint.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Delete.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Exists.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Put.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Aliases/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Aliases/Update.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Analyze.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Cache/Clear.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ClearCache.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Close.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Create.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Delete.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Exists.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Exists/Types.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Field/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Flush.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ForceMerge.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Gateway/Snapshot.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Delete.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/GetField.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Put.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Open.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Recovery.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Refresh.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Rollover.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Seal.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Segments.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Settings/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Settings/Put.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ShardStores.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Shrink.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Snapshotindex.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Stats.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Status.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/AbstractTemplateEndpoint.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Delete.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Exists.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Put.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Type/Exists.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Upgrade/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Upgrade/Post.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Validate/Query.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ValidateQuery.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Info.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Delete.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Put.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Simulate.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MPercolate.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MTermVectors.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Mget.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Msearch.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Percolate.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ping.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Reindex.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/RenderSearchTemplate.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Delete.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Put.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Scroll.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Search.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/SearchShards.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/SearchTemplate.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Create.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Delete.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Create.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Delete.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Verify.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Restore.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Status.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Source/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Suggest.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/Cancel.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/TasksList.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Delete.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Get.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Put.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/TermVectors.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Update.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/UpdateByQuery.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Helper/Iterators/SearchHitIterator.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Helper/Iterators/SearchResponseIterator.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/AbstractNamespace.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/BooleanRequestWrapper.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/CatNamespace.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/ClusterNamespace.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IndicesNamespace.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IngestNamespace.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NamespaceBuilderInterface.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NodesNamespace.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/SnapshotNamespace.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/TasksNamespace.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/ArrayToJSONSerializer.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/EverythingToJSONSerializer.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SerializerInterface.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SmartSerializer.php create mode 100644 vendor/elasticsearch/elasticsearch/src/Elasticsearch/Transport.php create mode 100644 vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ClientTest.php create mode 100644 vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/Selectors/RoundRobinSelectorTest.php create mode 100644 vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/Selectors/StickyRoundRobinSelectorTest.php create mode 100644 vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/SniffingConnectionPoolIntegrationTest.php create mode 100644 vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/SniffingConnectionPoolTest.php create mode 100644 vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/StaticConnectionPoolTest.php create mode 100644 vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Endpoints/AbstractEndpointTest.php create mode 100644 vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Helper/Iterators/SearchResponseIteratorTest.php create mode 100644 vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/RegisteredNamespaceTest.php create mode 100644 vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Serializers/ArrayToJSONSerializerTest.php create mode 100644 vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Serializers/EverythingToJSONSerializerTest.php create mode 100644 vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/YamlRunnerTest.php create mode 100644 vendor/elasticsearch/elasticsearch/tests/bootstrap.php create mode 100755 vendor/elasticsearch/elasticsearch/travis/download_and_run_es.sh create mode 100644 vendor/elasticsearch/elasticsearch/travis/generate_docs.sh create mode 100644 vendor/elasticsearch/elasticsearch/util/EnsureClusterAlive.php create mode 100644 vendor/elasticsearch/elasticsearch/util/RestSpecRunner.php create mode 100755 vendor/elasticsearch/elasticsearch/util/SpecParser.php create mode 100644 vendor/elasticsearch/elasticsearch/util/docsConfig.php create mode 100644 vendor/elasticsearch/elasticsearch/util/docstheme/layout/base.twig create mode 100644 vendor/elasticsearch/elasticsearch/util/docstheme/macros.twig create mode 100644 vendor/elasticsearch/elasticsearch/util/docstheme/manifest.yml create mode 100644 vendor/elasticsearch/elasticsearch/util/docstheme/pages/class.twig create mode 100644 vendor/elasticsearch/elasticsearch/util/docstheme/pages/classes.twig create mode 100644 vendor/elasticsearch/elasticsearch/util/docstheme/pages/interfaces.twig create mode 100644 vendor/elasticsearch/elasticsearch/util/docstheme/pages/namespaces.twig create mode 100644 vendor/elasticsearch/elasticsearch/util/templates/endpoint.twig create mode 100644 vendor/guzzlehttp/ringphp/.gitignore create mode 100644 vendor/guzzlehttp/ringphp/.travis.yml create mode 100644 vendor/guzzlehttp/ringphp/CHANGELOG.md create mode 100644 vendor/guzzlehttp/ringphp/LICENSE create mode 100644 vendor/guzzlehttp/ringphp/Makefile create mode 100644 vendor/guzzlehttp/ringphp/README.rst create mode 100644 vendor/guzzlehttp/ringphp/composer.json create mode 100644 vendor/guzzlehttp/ringphp/docs/Makefile create mode 100644 vendor/guzzlehttp/ringphp/docs/client_handlers.rst create mode 100644 vendor/guzzlehttp/ringphp/docs/client_middleware.rst create mode 100644 vendor/guzzlehttp/ringphp/docs/conf.py create mode 100644 vendor/guzzlehttp/ringphp/docs/futures.rst create mode 100644 vendor/guzzlehttp/ringphp/docs/index.rst create mode 100644 vendor/guzzlehttp/ringphp/docs/requirements.txt create mode 100644 vendor/guzzlehttp/ringphp/docs/spec.rst create mode 100644 vendor/guzzlehttp/ringphp/docs/testing.rst create mode 100644 vendor/guzzlehttp/ringphp/phpunit.xml.dist create mode 100644 vendor/guzzlehttp/ringphp/src/Client/ClientUtils.php create mode 100644 vendor/guzzlehttp/ringphp/src/Client/CurlFactory.php create mode 100644 vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php create mode 100644 vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php create mode 100644 vendor/guzzlehttp/ringphp/src/Client/Middleware.php create mode 100644 vendor/guzzlehttp/ringphp/src/Client/MockHandler.php create mode 100644 vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php create mode 100644 vendor/guzzlehttp/ringphp/src/Core.php create mode 100644 vendor/guzzlehttp/ringphp/src/Exception/CancelledException.php create mode 100644 vendor/guzzlehttp/ringphp/src/Exception/CancelledFutureAccessException.php create mode 100644 vendor/guzzlehttp/ringphp/src/Exception/ConnectException.php create mode 100644 vendor/guzzlehttp/ringphp/src/Exception/RingException.php create mode 100644 vendor/guzzlehttp/ringphp/src/Future/BaseFutureTrait.php create mode 100644 vendor/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php create mode 100644 vendor/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php create mode 100644 vendor/guzzlehttp/ringphp/src/Future/FutureArray.php create mode 100644 vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php create mode 100644 vendor/guzzlehttp/ringphp/src/Future/FutureInterface.php create mode 100644 vendor/guzzlehttp/ringphp/src/Future/FutureValue.php create mode 100644 vendor/guzzlehttp/ringphp/src/Future/MagicFutureTrait.php create mode 100644 vendor/guzzlehttp/ringphp/tests/Client/CurlFactoryTest.php create mode 100644 vendor/guzzlehttp/ringphp/tests/Client/CurlHandlerTest.php create mode 100644 vendor/guzzlehttp/ringphp/tests/Client/CurlMultiHandlerTest.php create mode 100644 vendor/guzzlehttp/ringphp/tests/Client/MiddlewareTest.php create mode 100644 vendor/guzzlehttp/ringphp/tests/Client/MockHandlerTest.php create mode 100644 vendor/guzzlehttp/ringphp/tests/Client/Server.php create mode 100644 vendor/guzzlehttp/ringphp/tests/Client/StreamHandlerTest.php create mode 100644 vendor/guzzlehttp/ringphp/tests/Client/server.js create mode 100644 vendor/guzzlehttp/ringphp/tests/CoreTest.php create mode 100644 vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureArrayTest.php create mode 100644 vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureValueTest.php create mode 100644 vendor/guzzlehttp/ringphp/tests/Future/FutureArrayTest.php create mode 100644 vendor/guzzlehttp/ringphp/tests/Future/FutureValueTest.php create mode 100644 vendor/guzzlehttp/ringphp/tests/bootstrap.php create mode 100644 vendor/guzzlehttp/streams/.gitignore create mode 100644 vendor/guzzlehttp/streams/.travis.yml create mode 100644 vendor/guzzlehttp/streams/CHANGELOG.rst create mode 100644 vendor/guzzlehttp/streams/LICENSE create mode 100644 vendor/guzzlehttp/streams/Makefile create mode 100644 vendor/guzzlehttp/streams/README.rst create mode 100644 vendor/guzzlehttp/streams/composer.json create mode 100644 vendor/guzzlehttp/streams/phpunit.xml.dist create mode 100644 vendor/guzzlehttp/streams/src/AppendStream.php create mode 100644 vendor/guzzlehttp/streams/src/AsyncReadStream.php create mode 100644 vendor/guzzlehttp/streams/src/BufferStream.php create mode 100644 vendor/guzzlehttp/streams/src/CachingStream.php create mode 100644 vendor/guzzlehttp/streams/src/DroppingStream.php create mode 100644 vendor/guzzlehttp/streams/src/Exception/CannotAttachException.php create mode 100644 vendor/guzzlehttp/streams/src/Exception/SeekException.php create mode 100644 vendor/guzzlehttp/streams/src/FnStream.php create mode 100644 vendor/guzzlehttp/streams/src/GuzzleStreamWrapper.php create mode 100644 vendor/guzzlehttp/streams/src/InflateStream.php create mode 100644 vendor/guzzlehttp/streams/src/LazyOpenStream.php create mode 100644 vendor/guzzlehttp/streams/src/LimitStream.php create mode 100644 vendor/guzzlehttp/streams/src/MetadataStreamInterface.php create mode 100644 vendor/guzzlehttp/streams/src/NoSeekStream.php create mode 100644 vendor/guzzlehttp/streams/src/NullStream.php create mode 100644 vendor/guzzlehttp/streams/src/PumpStream.php create mode 100644 vendor/guzzlehttp/streams/src/Stream.php create mode 100644 vendor/guzzlehttp/streams/src/StreamDecoratorTrait.php create mode 100644 vendor/guzzlehttp/streams/src/StreamInterface.php create mode 100644 vendor/guzzlehttp/streams/src/Utils.php create mode 100644 vendor/guzzlehttp/streams/tests/AppendStreamTest.php create mode 100644 vendor/guzzlehttp/streams/tests/AsyncReadStreamTest.php create mode 100644 vendor/guzzlehttp/streams/tests/BufferStreamTest.php create mode 100644 vendor/guzzlehttp/streams/tests/CachingStreamTest.php create mode 100644 vendor/guzzlehttp/streams/tests/DroppingStreamTest.php create mode 100644 vendor/guzzlehttp/streams/tests/Exception/SeekExceptionTest.php create mode 100644 vendor/guzzlehttp/streams/tests/FnStreamTest.php create mode 100644 vendor/guzzlehttp/streams/tests/GuzzleStreamWrapperTest.php create mode 100644 vendor/guzzlehttp/streams/tests/InflateStreamTest.php create mode 100644 vendor/guzzlehttp/streams/tests/LazyOpenStreamTest.php create mode 100644 vendor/guzzlehttp/streams/tests/LimitStreamTest.php create mode 100644 vendor/guzzlehttp/streams/tests/NoSeekStreamTest.php create mode 100644 vendor/guzzlehttp/streams/tests/NullStreamTest.php create mode 100644 vendor/guzzlehttp/streams/tests/PumpStreamTest.php create mode 100644 vendor/guzzlehttp/streams/tests/StreamDecoratorTraitTest.php create mode 100644 vendor/guzzlehttp/streams/tests/StreamTest.php create mode 100644 vendor/guzzlehttp/streams/tests/UtilsTest.php create mode 100644 vendor/psr/log/.gitignore create mode 100644 vendor/psr/log/LICENSE create mode 100644 vendor/psr/log/Psr/Log/AbstractLogger.php create mode 100644 vendor/psr/log/Psr/Log/InvalidArgumentException.php create mode 100644 vendor/psr/log/Psr/Log/LogLevel.php create mode 100644 vendor/psr/log/Psr/Log/LoggerAwareInterface.php create mode 100644 vendor/psr/log/Psr/Log/LoggerAwareTrait.php create mode 100644 vendor/psr/log/Psr/Log/LoggerInterface.php create mode 100644 vendor/psr/log/Psr/Log/LoggerTrait.php create mode 100644 vendor/psr/log/Psr/Log/NullLogger.php create mode 100644 vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php create mode 100644 vendor/psr/log/README.md create mode 100644 vendor/psr/log/composer.json create mode 100644 vendor/react/promise/.gitignore create mode 100644 vendor/react/promise/.travis.yml create mode 100644 vendor/react/promise/CHANGELOG.md create mode 100644 vendor/react/promise/LICENSE create mode 100644 vendor/react/promise/README.md create mode 100644 vendor/react/promise/composer.json create mode 100644 vendor/react/promise/phpunit.xml.dist create mode 100644 vendor/react/promise/src/CancellablePromiseInterface.php create mode 100644 vendor/react/promise/src/CancellationQueue.php create mode 100644 vendor/react/promise/src/Deferred.php create mode 100644 vendor/react/promise/src/Exception/LengthException.php create mode 100644 vendor/react/promise/src/ExtendedPromiseInterface.php create mode 100644 vendor/react/promise/src/FulfilledPromise.php create mode 100644 vendor/react/promise/src/LazyPromise.php create mode 100644 vendor/react/promise/src/Promise.php create mode 100644 vendor/react/promise/src/PromiseInterface.php create mode 100644 vendor/react/promise/src/PromisorInterface.php create mode 100644 vendor/react/promise/src/RejectedPromise.php create mode 100644 vendor/react/promise/src/UnhandledRejectionException.php create mode 100644 vendor/react/promise/src/functions.php create mode 100644 vendor/react/promise/src/functions_include.php create mode 100644 vendor/react/promise/tests/CancellationQueueTest.php create mode 100644 vendor/react/promise/tests/DeferredTest.php create mode 100644 vendor/react/promise/tests/FulfilledPromiseTest.php create mode 100644 vendor/react/promise/tests/FunctionAllTest.php create mode 100644 vendor/react/promise/tests/FunctionAnyTest.php create mode 100644 vendor/react/promise/tests/FunctionCheckTypehintTest.php create mode 100644 vendor/react/promise/tests/FunctionMapTest.php create mode 100644 vendor/react/promise/tests/FunctionRaceTest.php create mode 100644 vendor/react/promise/tests/FunctionReduceTest.php create mode 100644 vendor/react/promise/tests/FunctionRejectTest.php create mode 100644 vendor/react/promise/tests/FunctionResolveTest.php create mode 100644 vendor/react/promise/tests/FunctionSomeTest.php create mode 100644 vendor/react/promise/tests/LazyPromiseTest.php create mode 100644 vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php create mode 100644 vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php create mode 100644 vendor/react/promise/tests/PromiseTest.php create mode 100644 vendor/react/promise/tests/PromiseTest/CancelTestTrait.php create mode 100644 vendor/react/promise/tests/PromiseTest/FullTestTrait.php create mode 100644 vendor/react/promise/tests/PromiseTest/NotifyTestTrait.php create mode 100644 vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php create mode 100644 vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php create mode 100644 vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php create mode 100644 vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php create mode 100644 vendor/react/promise/tests/PromiseTest/RejectTestTrait.php create mode 100644 vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php create mode 100644 vendor/react/promise/tests/RejectedPromiseTest.php create mode 100644 vendor/react/promise/tests/Stub/CallableStub.php create mode 100644 vendor/react/promise/tests/TestCase.php create mode 100644 vendor/react/promise/tests/bootstrap.php create mode 100644 vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php create mode 100644 vendor/react/promise/tests/fixtures/SimpleFulfilledTestThenable.php create mode 100644 vendor/react/promise/tests/fixtures/SimpleRejectedTestPromise.php create mode 100644 vendor/react/promise/tests/fixtures/SimpleTestCancellable.php create mode 100644 vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php create mode 100755 views/admin/admin/collections.php create mode 100755 views/admin/admin/fields.php create mode 100755 views/admin/admin/partials/field.php create mode 100755 views/admin/admin/partials/navigation.php create mode 100755 views/admin/admin/reindex.php create mode 100755 views/admin/admin/results.php create mode 100755 views/admin/admin/server.php create mode 100755 views/admin/javascripts/accordion.js create mode 100755 views/shared/common/pagination.php create mode 100755 views/shared/css/fields.css create mode 100755 views/shared/css/img/glyphicons-halflings-white.png create mode 100755 views/shared/css/img/glyphicons-halflings.png create mode 100755 views/shared/css/img/remove.gif create mode 100755 views/shared/css/img/separator.gif create mode 100755 views/shared/css/results.css create mode 100755 views/shared/css/results.css.map create mode 100755 views/shared/css/sass/_buttons.scss create mode 100755 views/shared/css/sass/_mixins.scss create mode 100755 views/shared/css/sass/_sprites.scss create mode 100755 views/shared/css/sass/_variables.scss create mode 100755 views/shared/css/sass/fields.scss create mode 100755 views/shared/css/sass/results.scss create mode 100755 views/shared/results/index.php diff --git a/ESearchPlugin.php b/ESearchPlugin.php new file mode 100755 index 0000000..9622c5d --- /dev/null +++ b/ESearchPlugin.php @@ -0,0 +1,398 @@ +_db->query(<<_db->prefix}esearch_fields +SQL + ); + $this->_db->query(<<_db->prefix}esearch_excludes +SQL + ); + + try { + $es = ESearch_Helpers_Index::connect(); + $es->deleteByQuery('*:*'); + $es->commit(); + $es->optimize(); + } catch (Exception $e) {} + + self::_clearOptions(); + + } + + + /** + * If upgrading from 1.x, install the new schema. + * + * @param array $args Contains: `old_version` and `new_version`. + */ + public function hookUpgrade($args) + { + self::_createSolrTables(); + if (version_compare($args['old_version'], '1.0.1', '<=')) { + self::_installFacetMappings(); + self::_setOptions(); + } + + $fields = $this->_db->getTable('ESearchField'); + $featured = $fields->findBy(array('slug' => 'featured')); + if (is_null($featured) || empty($featured)) { + $this->_installGenericFacet('featured', __('Featured')); + } + + if (version_compare($args['old_version'], '2.2.1', '<=')) { + set_option('esearch_hl_max_analyzed_chars', '51200'); + } + } + + + /** + * Register the string translations. + */ + public function hookInitialize() + { + add_translation_source(dirname(__FILE__) . '/languages'); + } + + + /** + * Register the application routes. + * + * @param array $args With `router`. + */ + public function hookDefineRoutes($args) + { + $args['router']->addConfig(new Zend_Config_Ini( + SOLR_DIR.'/routes.ini' + )); + } + + + /** + * When a record is saved, try to extract and index a Solr document. + * + * @param array $args With `record`. + */ + public function hookAfterSaveRecord($args) + { + + ESearch_Utils::ensureView(); + + $record = $args['record']; + + $excludes = get_db()->getTable('ESearchExclude'); + $collection = get_collection_for_item($record); + if (!is_null($collection) && $excludes->isExcluded($collection)) { + return; + } + + // Try to extract a document for the record. + $mgr = new ESearch_Addon_Manager($this->_db); + $doc = $mgr->indexRecord($record); + + // Does the record have an add-on profile? + if ($addon = $mgr->findAddonForRecord($record)) { + + // Connect to Solr. + $es = ESearch_Helpers_Index::connect(); + + // If the record yields a Solr document, index it. + if (!is_null($doc)) { + $es->addDocuments(array($doc)); + $es->commit(); + $es->optimize(); + } + + // If not, remove an existing document. + else { + try { + $es->deleteById($mgr->getId($record)); + $es->commit(); + $es->optimize(); + } catch (Exception $e) {} + } + + } + + // Reindex related records. + $mgr->resaveRemoteParent($record); + $mgr->resaveChildren($record); + + } + + + /** + * When an item is saved, index the record if the item is set public, and + * clear an existing record if it is set private. + * + * @param array $args With `record`. + */ + public function hookAfterSaveItem($args) + { + + ESearch_Utils::ensureView(); + + $item = $args['record']; + + $excludes = get_db()->getTable('ESearchExclude'); + $collection = get_collection_for_item($item); + if (!is_null($collection) && $excludes->isExcluded($collection)) { + return; + } + + $es = ESearch_Helpers_Index::connect(); + + // Both public and private items will be indexed + $doc = ESearch_Helpers_Index::itemToDocument($item); + $es->addDocuments(array($doc)); + $es->commit(); + $es->optimize(); + + } + + + /** + * When a new element is added, register a facet mapping for it. + * + * @param array $args With `record` and `insert`. + */ + public function hookAfterSaveElement($args) + { + if ($args['insert']) { + $facet = new ESearchField($args['record']); + $facet->save(); + } + } + + + /** + * When a record is deleted, clear its Solr record. + * + * @param array $args With `record`. + */ + public function hookBeforeDeleteRecord($args) + { + + $record = $args['record']; + $mgr = new ESearch_Addon_Manager($this->_db); + $id = $mgr->getId($record); + + if (!is_null($id)) { + $es = ESearch_Helpers_Index::connect(); + try { + $es->deleteById($id); + $es->commit(); + $es->optimize(); + } catch (Exception $e) {} + } + + } + + + /** + * When an item is deleted, clear its Solr record. + * + * @param array $args With `record`. + */ + public function hookBeforeDeleteItem($args) + { + + $item = $args['record']; + $es = ESearch_Helpers_Index::connect(); + + try { + $es->deleteById('Item_' . $item['id']); + $es->commit(); + $es->optimize(); + } catch (Exception $e) {} + + } + + + /** + * When an element is deleted, remove its facet mapping. + * + * @param array $args With `record`. + */ + public function hookBeforeDeleteElement($args) + { + $table = $this->_db->getTable('ESearchField'); + $facet = $table->findByElement($args['record']); + + if(!empty($facet)) { + $facet->delete(); + } + } + + + /** + * Add a link to the administrative navigation bar. + * + * @param string $nav The array of label/URI pairs. + * @return array + */ + public function filterAdminNavigationMain($nav) + { + $nav[] = array( + 'label' => __('Solr Search'), 'uri' => url('esearch/server') + ); + return $nav; + } + + + /** + * Override the default simple-search URI to automagically integrate into + * the theme; leaves admin section alone for default search. + * + * @param string $uri URI for Simple Search. + * @return string + */ + public function filterSearchFormDefaultAction($uri) + { + if (!is_admin_theme()) $uri = url('esearch/results/interceptor'); + return $uri; + } + + + /** + * Install the facets table. + */ + protected function _createSolrTables() + { + $this->_db->query(<<_db->prefix}esearch_fields ( + id int(10) unsigned NOT NULL auto_increment, + element_id int(10) unsigned, + slug tinytext collate utf8_unicode_ci NOT NULL, + label tinytext collate utf8_unicode_ci NOT NULL, + is_indexed tinyint unsigned DEFAULT 0, + is_facet tinyint unsigned DEFAULT 0, + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +SQL + ); + + $this->_db->query(<<_db->prefix}esearch_excludes ( + id int(10) unsigned NOT NULL auto_increment, + collection_id int(10) unsigned NOT NULL, + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +SQL + ); + } + + + /** + * Install the default facet mappings. + */ + protected function _installFacetMappings() + { + $this->_db + ->getTable('ESearchField') + ->installFacetMappings(); + } + + + /** + * Install the default facet mappings. + * + * @param string $slug The facet `slug`. + * @param string $label The facet `label`. + */ + protected function _installGenericFacet($slug, $label) + { + $this->_db + ->getTable('ESearchField') + ->installGenericFacet($slub, $label); + } + + + /** + * Set the global options. + */ + protected function _setOptions() + { + set_option('esearch_host', 'localhost'); + set_option('esearch_port', '9200'); + set_option('esearch_core', '/solr/omeka/'); + set_option('esearch_facet_limit', '25'); + set_option('esearch_facet_sort', 'count'); + set_option('esearch_hl', '1'); + set_option('esearch_hl_snippets', '1'); + set_option('esearch_hl_fragsize', '250'); + set_option('esearch_hl_max_analyzed_chars', '51200'); + set_option('esearch_display_private_items', '1'); + } + + + /** + * Clear the global options. + */ + protected function _clearOptions() + { + delete_option('esearch_host'); + delete_option('esearch_port'); + delete_option('esearch_core'); + delete_option('esearch_facet_limit'); + delete_option('esearch_facet_sort'); + delete_option('esearch_hl'); + delete_option('esearch_hl_snippets'); + delete_option('esearch_hl_fragsize'); + delete_option('esearch_hl_max_analyzed_chars'); + delete_option('esearch_display_private_items'); + } + + +} diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 index 8dada3e..57bc88a --- a/LICENSE +++ b/LICENSE @@ -178,7 +178,7 @@ APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" + boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -199,3 +199,4 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + diff --git a/README.md b/README.md old mode 100644 new mode 100755 index c4fd7ff..296efae --- a/README.md +++ b/README.md @@ -1,2 +1,202 @@ -# ESearch -Elasticsearch Search Plugin for Omeka Classic +# [ESearch][plugin] + +![Elasitcsearch](https://elastico.co/es.png) + +**ESearch** replaces the default Omeka search interface with one powered by +[Elasticsearch][Elasticsearch], a scalable and feature-rich search engine that supports faceting +and hit highlighting. In most cases, Omeka's built-in searching capabilities +work great, but there are a couple of situations where it might make sense to +take a look at Elasticsearch: + + - When you have a really large collection, and want something a bit faster; + + - When your site contains a lot of text content, and you want to take + advantage of Elasticsearch's hit highlighting functionality, which displays a + preview snippet from each of the matching records; + + - When your site makes use of a lot of different taxonomies (collections, + item types, etc.), and you want to use Elasticsearch's faceting capabilities, which + make it possible for users to refine searches by cropping down the set of + results to focus on specific categories. + +## Requirements + +To use the plugin, you'll need access to an installation of Elasticsearch running +the core included in the plugin source code under `solr-core/omeka`. For +general information about how to get up and running with Elasticsearch, check out the +official [installation documentation][solr-install]. + +## Installation + +### Elasticsearch Core + +To deploy the Elasticsearch core, just copy the `solr-core/omeka` directory into your +Elasticsearch home directory. For example, if your deployment is based on the default +Elasticsearch 4 multicore template, you might end up with directories for `core0`, +`core1`, and `omeka`. Once the directory is in place, restart/reload Elasticsearch to +register the new core. + +### Omeka Plugin + +Once the core is up and running, install ElasticsearchSearch just like any other Omeka +plugin: + + 1. Download the plugin from the [Omeka addons repository][plugin] and unzip + the archive. + + 2. Upload the `ElasticsearchSearch` directory into the Omeka `plugins` directory. + + 3. Open up the "Plugins" page in Omeka and click the "Install" button for + Elasticsearch Search. + +For more information, check out the [Managing Plugins][managing-plugins] guide. + +## Configuration + +### Server Configuration + +To get started, click on the "Elasticsearch Search" tab, which displays a form with Elasticsearch +connection parameters: + + - **Server Host**: The location of the Elasticsearch server, without the port number. + + - **Server Port**: The port that Elasticsearch is listening on. + + - **Core URL**: The URL of the Elasticsearch core in which documents should be + indexed. + +After making changes to the connection parameters, click the "Save Settings" +button. If the plugin is able to connect to Elasticsearch, a greet notification saying +"Elasticsearch connection is valid" will be displayed. + +### Collections Configuration + +You can also decide not to index certain collections of items. By default, all +collections are indexed. However, if you go to the "Collections" tab, then you +can select collections to *exclude* from indexing. + +### Field Configuration + +This form makes it possible to configure (a) which metadata elements and Omeka +categories ("fields") are stored as searchable content in Elasticsearch and (b) which +fields should be used as "facets", groupings of records that can be used to +iteratively narrow down the set of results. + +> If you've installed any new metadata elements and they're missing from this +> form, click the "Load New Elements" button at the bottom of the page. The +> page will reload, and hopefully the new elements will be listed. + +For each element, there are three options: + + - **Facet Label**: The label used as the heading for the facet corresponding + to the field. In most cases, it probably just makes sense to use the + canonical name as the element (the default), but this makes it possible to + create a customized interface that doesn't map onto the nomenclature of the + metadata. + + - **Is Indexed?**: If checked, the content in this field will be stored as + full-text-searchable content in Elasticsearch. As a rule of thumb, it makes sense to + index any fields that contain non-trivial text content, but not fields that + contain non-semantic data or identifiers. + + - **Is Facet?**: If checked, the field will be used as a facet in the + results. As a rule of thumb, **a field might be a useful facet if it + contains a controlled vocabulary**. For example, imagine you use one of + three values in the Dublin Core "Type" field - `type1`, `type2`, and + `type3`. This would make a good facet, because users would be able to hone + in on the implicit relationships among items of the same type. It wouldn't + make sense to use something like the "Description" field as a facet, + though, two items will almost never share the exact same description (or, + at least, they probably shouldn't!). + +Use the accordion to expand and contract the fields in the three categories. +There are two types of fields - the "Omeka Categories," which aren't actually +metadata elements but rather high-level taxonomies that are baked in to the +struture of Omeka, and the metadata elements (Dublin Core and Item Type +Metadata) that can be used to describe items. + +After you've made changes, click the "Update Search Fields" to save the +configuration. + +### Results Configuration + +This form exposes options for two features in Elasticsearch: **hit highlighting**, which +makes it possible to display preview snippets for each result that excerpt +portions of the metadata that are relevant to the query, and **faceting**, +which makes it possible for users to progressively refine large result sets by +honing in on specific categories. + + - **Enable Highlighting**: Set whether highlighting snippets should be + displayed. + + - **Extent of Document Highlightable**: Set the amount of the document to + scan when highlighting. By default, to save time, this is limited to 51200 + characters. If you have documents in the results that don't have snippets, + you can make this larger. + + - **Number of Snippets**: The maximum number of snippets to display for a + result. + + - **Snippet Length**: The maximum length of each snippet. + + - **Facet Ordering**: The criteria by which to sort the facets in the + results. + + - **Facet Count**: The maximum number of facets to display. + +Click "Save Settings" to update the configuration. + +### Index Items + +After making changes in the "Field Configuration" and "Results Configuration" +tabs, it's necessary to reindex the content in the site in order for the +changes to take effect. ElasticsearchSearch doesn't do this automatically because +reindexing can take as long as a few minutes for really large sites. + +When you're ready, just click the "Clear and Reindex" button. This will spawn +off a background process behind the scenes that rebuilds the index according to +the new configuration options. + +### Private Items + +Currently, because of the complexity of the Omeka authorization system, we only +support search on items that have been marked *public*. The admin search +interface can be used to discover items that are private. + +### Featured Items + +As of version [2.1.0][210], Elasticsearch Search indexes and allows faceted searches on +featured items. If you're upgrading, for this to work, you'll need to do two +extra steps after going through the standard Omeka plugin upgrade process. + +1. Re-install the Elasticsearch configuration files as explained in the section on +[Installing the Elasticsearch Core][solr-core]. +2. Re-index everything from the ElasticsearchSearch admin panel. + +## Searching + +Once the content has been indexed, head to the public site and type a search query into the regular Omeka search input. When the query is submitted, ElasticsearchSearch will intercept the request and redirect to a custom interface that displays results from Elasticsearch with faceting and hit highlighting. + +## Thanks + +This work has been a collaboration. During development, we've gotten help from a number of others: + +* @anuragji +* @marcobat +* Adam Doan +* @cokernel +* Dave Lester + +[plugin]: http://omeka.org/add-ons/plugins/ElasticsearchSearch/ +[solr]: http://lucene.apache.org/solr +[solr-install]: https://wiki.apache.org/solr/ElasticsearchInstall +[managing-plugins]: https://omeka.org/codex/Managing_Plugins +[homebrew]: http://mxcl.github.com/homebrew/ +[node]: http://nodejs.org/ +[gems]: http://rubygems.org/ +[bundler]: http://gembundler.com/ +[rvm]: http://beginrescueend.com/ +[multicore]: http://wiki.apache.org/solr/CoreAdmin +[rvm]: https://rvm.io/ +[210]: https://github.com/scholarslab/ElasticsearchSearch/releases/tag/2.1.0 +[solr-core]: #solr-core diff --git a/RELEASE-LIST.md b/RELEASE-LIST.md new file mode 100755 index 0000000..017ce59 --- /dev/null +++ b/RELEASE-LIST.md @@ -0,0 +1,26 @@ + +# Release Checklist + +It's easiest to start from a working directory within an Omeka installation. +`git flow` should have been set up. Make sure to run `npm install` and `bundle +install` also. + +1. `VERSION=42.0.13` โ€” We'll use this value later. +1. `PATH=$PATH:./node_modules/.bin` +1. `git flow release start $VERSION` +1. Update the version numbers in these files: + * `plugin.ini` + * `package.json` +1. `git add plugin.ini package.json && git commit` +1. `grunt build` +1. `git add --all views/shared` +1. `git commit -m "Updated generated assets."` +1. Make sure extraneous files aren't in your working directory. (I'm looking at you, `tags`.) +1. `grunt package` +1. quick check the zip in `./pkg/` +1. test the zip +1. `git flow release finish $VERSION` +1. `git push` +1. `git push --tags` +1. upload the zip to http://omeka.org/add-ons/plugins/. + diff --git a/addons/exhibits.json b/addons/exhibits.json new file mode 100755 index 0000000..74027cb --- /dev/null +++ b/addons/exhibits.json @@ -0,0 +1,35 @@ +{ + "exhibits": { + + "table" : "Exhibit", + "result_type" : "Exhibit", + "tagged" : true, + "flag" : "public", + + "fields": [ + "title", + "description" + ], + + "children": { + "exhibit_pages": { + "table" : "ExhibitPage", + "result_type" : "Exhibit Page", + "parent_key" : "exhibit_id", + "fields" : [ + "title", { + "field" : "text", + "label" : "Page Text", + "facet" : false, + "is_title" : false, + "remote" : { + "table" : "ExhibitPageBlock", + "key" : "page_id" + } + } + ] + } + } + + } +} diff --git a/addons/pages.json b/addons/pages.json new file mode 100755 index 0000000..f82488a --- /dev/null +++ b/addons/pages.json @@ -0,0 +1,8 @@ +{ + "simple_pages": { + "table" : "SimplePagesPage", + "result_type" : "Simple Page", + "flag" : "is_published", + "fields" : ["title", "text"] + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..a6621db --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "elasticsearch/elasticsearch": "~5.0" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..68fb2c4 --- /dev/null +++ b/composer.lock @@ -0,0 +1,267 @@ +{ + "_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" + ], + "content-hash": "176780d3d713a41a474af726de5fb5b4", + "packages": [ + { + "name": "elasticsearch/elasticsearch", + "version": "v5.2.0", + "source": { + "type": "git", + "url": "https://github.com/elastic/elasticsearch-php.git", + "reference": "dc57bbcd4be2c9061ebe5be59058ea8bdebc904e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/dc57bbcd4be2c9061ebe5be59058ea8bdebc904e", + "reference": "dc57bbcd4be2c9061ebe5be59058ea8bdebc904e", + "shasum": "" + }, + "require": { + "guzzlehttp/ringphp": "~1.0", + "php": "^5.6|^7.0", + "psr/log": "~1.0" + }, + "require-dev": { + "cpliakas/git-wrapper": "~1.0", + "doctrine/inflector": "^1.1", + "mockery/mockery": "0.9.4", + "phpunit/phpunit": "^4.7|^5.4", + "sami/sami": "~3.2", + "symfony/finder": "^2.8", + "symfony/yaml": "^2.8" + }, + "suggest": { + "ext-curl": "*", + "monolog/monolog": "Allows for client-level logging and tracing" + }, + "type": "library", + "autoload": { + "psr-4": { + "Elasticsearch\\": "src/Elasticsearch/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Zachary Tong" + } + ], + "description": "PHP Client for Elasticsearch", + "keywords": [ + "client", + "elasticsearch", + "search" + ], + "time": "2017-04-12T20:40:50+00:00" + }, + { + "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-20T03:37:09+00:00" + }, + { + "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-12T19:18:40+00:00" + }, + { + "name": "psr/log", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10T12:19:37+00:00" + }, + { + "name": "react/promise", + "version": "v2.5.1", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "62785ae604c8d69725d693eb370e1d67e94c4053" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/62785ae604c8d69725d693eb370e1d67e94c4053", + "reference": "62785ae604c8d69725d693eb370e1d67e94c4053", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "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@gmail.com" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "time": "2017-03-25T12:08:31+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/composer.phar b/composer.phar new file mode 100755 index 0000000000000000000000000000000000000000..e44722e933f8ebf9b4501f04b42c9accc594ab00 GIT binary patch literal 1838958 zcmdqK34EMabv7P$A`+IcLrBI(_KadnV`l}~R-(wVE%2g9%4#`cERC#5Ml;IH$hML= z>L>5LfKkU_@3w7bMAZJWk#|c+VA&w zX^!T7?>+b2bI(2J+;h*p@6%Tm7n_aZ{z|P_t{u!R%q?_nKA-a)3_`usw@S#I>91Pc|Wdvd!g)n;yRrrek>)oQuTwUYbu0{f$HraZv1 z{P(PSW2Tb3uzuh`rBtiuHec1ZU;nKF?mYtgGx+v8$_C%ncr_S7vg9qeHpr(qgkxJCJLjX%wF=wMx}oxzVUMz?O0& zH`i({H2aIi1C`d?;{Lwr`g{@pFIlapT-jZ`o=H^RF!dQ%N$j?J_SYs6o zy5l1MfN0G$`|^FGJ4UC5_w3Zxvl_g3VFrWD=GNyRLg+*eybUgmw7;W@EZIWb!t+*s4>aiuT9$a;@AbwdxJjS(q&W+xMP&IpM}ve)sG@1$O>x zk7lS^YBqP37Gh{#3yO$_@CV%=&KsI109A~dAqprP{>3S-24Uaq>%VF>)}(5|2gYe#mU%18Uw;h@7)%yNYHNbG6Vj%qL zj#o|^hPD*!sLzL-IQ@~10>bG(f5IKh)*q==!U6s87FVC}N&o(8--CQRPL9-?u!><9 z{#&OT;f9~}ztl)*JD@wAQ4fV;jdp{ucDQSJSsZ&7TU7ot0W06z<+?(6-Z!qj+Hmx= z>k7nsuCl+<3JLj~N+;n{4!rW;4N2P(9V^e*W7_8dP>$4@gm2n&_9F~KTaJ!76B6{~ zt*1K_gb%*?f(1j-wkKnYwejgjWg#5dTTgOG2*2>rAAQb{q)dP|Tyz))tCdp7kO!*r zA$<3mU;Un;Y0HptXt3$Ipf}%qxFdw{K`%M!ora;U5*#mAXPs>iu>5d?!$SDWcRlDN z!_s!5jL+2%1@+4_u0G-Tr@s1BtKYsKtx{_-9ZS-SlDhM_GLmz1g%PL+TcPkNlgL0Db5rD!;w zoH0|}8UTj**E)=ZKR)Me7g(hyrYq6<(3by>(n|QXv!CUyMTXg76*xBp<8aER%OkZ` zxiMRs7O&!w4|D_*zW=1Xp5Tm53!-N=>$MOWiCLv;itw)SlXn~a83I&m)~n^>;KBly z6|&}F{caUN!h2^=`K8s*usN>&j@En?YHDGzg#`}QF6a;Oob^!G7s9VRe|XXGq?i(U z6k}x&1edAz*o zTyRv)c*SFS2~2RKWnwuYAaDj#Bt_M#1a~%s%W-%C_#ibd@=9W zfGc4Sk&Tz7U++|hkZ|e?FYqQGYnNrGfuf16hW9+x@rm&L7w!F)@o6Q3r1(bINnN8q zP7ML!k)PiEpN8(t6_R&psRci4u2iXU5GyK{gmXWm~ahAQnV)DP7pzSt-Zx_HP||Abxrpsue7?|AR!4>v^V@$}V-5UtvQ z@>qGH-e|=%;OD^~S^>iMeB*ZS#HOcbgv`ja6=2Ej&o{x!Cm-dgaulKZ@3{}eaj3_8>t2djKYV?34J?9Z_rW1FzUt3v-WBCu&FoZ|GQvbMNNgJJr$2$(h zaXjNhM+xEgmcCIl9Gxf`Ew$$2SjHdiun>NB_P?(;Eb#LaW+I|w6pP%^()87k<>AAv z24<{_yDqAUAiVZPkH67y^>xJ6z~;tuLV7--T7>Y_SAF`w4O7RyAqE}q+qYGc3BUH4 z)4yX_It}<(63HJZDTL>qc-F-$K{9@@7N=zUV;q+Wf9u{$Ubhk?6G=+`MY}_I&+lG# zz9H$vUQwykVX+ytMZHbJE57jQuNkIi0&^^~9rdDk$#iq5w7_ke=wVC^eM!k9e9k+5 z;@7*KxDBKcHcqeA>eLb5@|0VB@Tb$nxa`_Ej?jU+w0qMxt#_RWxgt43|MMg_VuS;q zeDh-s$=W1~h7Xo&t*w>j0v!^pQspW79QHM24?L>P(d>!?JnLwIEN zN#3fa1}qcT_~QO10%ZFWivACEzzF9)@PG|Q;Ic?vtc=m%McV3^XdP6fgomzp#<(F( zG2hoJ?yiqFtNtguJF7?tKX>o0-!~+1ujBH$O>~c};cwgQSVUO4@d6+5drGp-$etp% zJVy4grQd$Et3_CNLG@ME(y3Xs7H4O{LaBF#I_ZROdH39e)q7e-J;Y)V!O#Xbsof|1 z>=S>_ui_ExjCT!Wj_ip|u@=jPjurj-7Ru<2hCR0*7~UUg0g?>h3^=NYDykftz|8-9L261JP< z5LUl*ZN-puV39{BNMyD5HSHAP_iw#myJ1N=xxTIihZ3{-UsT-?UirdT`*B_e#4(*7 zA=+8l-$)MGl{#dEkABKM4WlRJw?$}nm0K_j;tu$S9Q|8V@(4e5$|qMDs+2DGwV(>> z3;q{ztW%jM{OAXE9WfmF_PsLCgnF4M%DMm}-1OeuQw&81GC~wGN*`1glyGMMmz##8 zgXqyo%9s-0QoBO9=@%96PIRIMm|{$Mi@GF)pMJrg{!hKI|z(!iN?@8P0WJ~!Pm%Pou^t(_xWcgH>-);TRX z;tX>a`5!JDIM5GzoSTq@mt3;@@kYth+t&<3(+|~-5T5qMYy9$HP21|wD3yi9s)aOu zrAC_YgsVS4ZSAL~vZq{kCz3XFQl*Ws@3Q~!3*Qta%D0@etK-gdckS4L2Jl z=Ow%15x8Zut)J=T$zlxu(ZKzQs$K|}E?;-saiBz^fY_L?>D(qf>3#3}kf9{|Vj8Or zmE}OAzgMRf;elWMO^@L^Yo)m2OX`Q|2oV10PX_!p=JM$1TELg7F3ymH%DmU9Xb~>` z>6T9!b<2+j_N`0@argL6;uaT9SQ7KID=R-t)uBM3aRhNDB zdxogHUH>?Yij)&-?a5BH5I!}3`d6)fN^2V-9Jgv2NhHoL632bo8Ny%f`rMxyj1~;RBC6XUQ0wGV~rPb2U{~cFNyG^Fdf(`n!bZp8U3_ z8JhM?bZACu2e}Ea$e}Hw81%3j6~e>&AA6UfOgSTndt>h+dlkLdb{#xe*;-tuz4KscwZh&YCsS5P}3 zvo$~nGDzJ=06$s#iue0cYsaXg(^orN`_5IaD&Z^7D|=4o+R>sYIPjmr0+Y=WPJiN+ zbH%IdeZCqeyKZ$6~-A87ZgQhzXY9MzB`@S`GqM>SQ zEfc6lOAW>J3nhy1w7bso`v7hIsRSlu9wFb+g>s**{uJT+Hb3gqMpc?qval#hXs}YP zw3dQD*}Y34VC){@zfIk~(GaEmlr$oj7mG#5!HT+!uUBk@zufb!0mGJ_0Rc9*I(V*D zCH&Mik6&k1)3T#gUF&|i|1H&3gzx{<-Ip#`->tZ!h5pC2eZnj6`PD<1lb~YslUSzI zYb1Q#FTd^scAe-khd+*K?}Y0a;q30a-f#4z^i5PvljV_E5IrMNn;z~^5x(s`)=At7Bz)r=)2_)du18!_W1z zBBjof*c8(U&Q%f5U#bI1*gtW`XN;;&^HiuxQFO1a@d(fV^yPkApp)drF*REoH$*-B znDUG8!F!(P*R4l|Z)1dSt>Pp6@x}WeZaq!8q)CoN$tm%*#WiXP2%q`LCvGu>ofMOF zI6`QnaE*=v;q0GH_$6(p2_>jhUL%Z=OY~xO!3lrfz0wxY?wV_Pg3g!uqRU_6ft&aTY=Tqv4>uzEgWe*mLU(j~JGY@`^AePZ7p3 zJwSKt2_H1I>-&bOlX{3S1iNp$9^=#l;gxUtlux?sBnJ_qm=gRaqaNkXzxybNzQmMp)Fkl8%|DUQ< z!cV;7!;dp;Jso(a*rKj|S%rb{tE(rxtyr#WAZf?q{s_;t@c(FY2p@a;SA5dj3h?YK z1$cfJ;Vj}I{G(S+daJW>nNICEkinsURSpqWe(%0-7>81!z(|ysJ5faXjvuqP>zENP ze*LX}b=7GC6UQjl0+KA_G3`->PI$rR{$saMv)p(wL&YO`ylMf$r~TcvK2u>iBp@k5 z@$A8M=~6D})6e6ms9%U( zigJ`}!na;>Ma$Tfir;E|U-3@0+l1BDZM&>uyN1;s`uewM!-Vhn^=I0jGS!APWrTaO zvY|SvEDuU+}sn4kl5Dr{>_;pq{6@ZYsUVX6YPao&Titu-@dFG3) zYKm;BYU{vV6`sIn{X3Lpgzx&uFWRsEwLZ@RX(-VlSgUH8@CW_>>MhRdB+muGFx%Y$ zVXL~H->A(K?kJzS+Q`r5TC{2+=}jm$iYejm-Scj*GoPH%B)65#iIm@mD^e*0vsO1?xTcB-b9{<$v}IFUm4aze}-CajW>+lj*M~Ll6F7{ozY#_X1-FxSax6oS|_00;9=+mHH$yvg8J@g#E z``)IeI)SWtAI6b237hYl{f@P{CcDi>eHtN4m-FSs=_Cy#5Wb|RdB_l~%|yVHqAjYBQ3t1r=+;x0S#4Z`NLlS(nX}_b(m@X6~z%Qo>WWec)YI6Eui)?MI}Y`*I7U zFg)b+_>m*GSmBgd*n#q*+?^0@Qrv{KDdmdrqFZ0{=Z4_vXbo9i{*H*=hL7d-Hjz0w zU+TLG+dBWQ#1cM#`xm{cVCrA2dQfNY@46-lzj^iBA7xFRi#o74GxdgVwb+C#^y1tf zFIH(5iBX=EZCk1-RD|pJPkr**I}9WGv{H=LhTwZd*-=RNpXF8FouSAng}fG~hzn49 z7aG_*ZV72~l^I;BP^sY*WncMl`2^?_vX=0*FMs<%qlz=muq@5b)@w`XZ{{)1J~#@* zK=>#5=zw=fM*@So9OimZMqZ1tBo@NY-@DEycF}!pLx*9ZJnP@Y^i}Kz5ffqGm*41f zZdQP4XQde)_`X855#eXv`lcrsZwj4g5*~?9BaXojQI^yYp8R(^K4>^Lx5u%y+?1o| z5V7FItUcAEHeXlT2=Dyu_xf~0#zx!l%P;_M^d~v?`^quGhi|&Ur$;fCc{I3UZs>ie z%#IV@anZx>w2lYj-b{!^M3v-VML$X8%^LV7d|dU9A7c0z%2}RB9#NgaX@fY<4?WV+ zNO<+FeI>)WG8!kKyyN&PP@L>D;hVnj<6j#-E)JHb@v`}99NnYfUJxDOgKrw`Hgp}x z6TxF_o!P-UDZGRIJ~dK=S6uzlGYuajGi}I=;q!<-siuCjmt%M{b@I!>A|Vr^tE zD2QKDE)stF=Q)q~ypEs)mR6fxo7 zfBqv^7!x+(ce@d{zGAyVb^-Rt@xHcbAN#p~*kNcp$hogy+*vL)c;B_!|X^88-~9QawTVjvpL4Z1_5i zPg{Jkfq$9G5@G+>AM!9m+(GlUMLgbGa=X$yAMMyl__E)5#%4o$VS9FVMj9jex0GbU zua14idwv7QihaC=d^U=~&W9-7fPGt@;6rmAw2E_OVb0*4`aVSd?O`>NguDOn%ieE1 zvpuaIquJmH+qFs`;p}I=;?uMGmd92pRqF>LO#h{Jf^hD#SNf3s#$_>ObaGl3U4*ax z??3XPi^8(VB7&Z#9suEuAK&~|6Pe`+ayViV@*OlxZASQ}C*A!ohKxJ!ZKkusQglEM zn}1PhA}qb+1|JSue(*-<+zuYX&3#c1r&VkTfAovrx!#B>E=v>^)?B7WsQy~xql6dS z@h{IeR2|Hy_7A8IV0qBIup}$a<{HN+!tcK4Hjk)-50^j`5jCmtR>D)xzv-JsR0oxu zz%^2v;pHrvtMfQV+Oq-gJ@uaV8oK34vmA_wwC5{ngkO5felJ`d45pGc?i|E4%Bn70 z2~Yd^*SrnRFFTqO48&5~JO=9%N-5z3-ts}e;kCS!3W}j}RW6>mQ5_e;+t2v7CmExb z7Y0ESap|!dDk8jXVcy5Om*==38ZYCfN*ld@S?Am-e*LuUm1J6B{^N$OgQ?r zJ8v^QU|GAFEOQSTFb59GU*z=Ta4e0~5WeT1AAO5q>A-2jg2ZBvW=e@6+LT41%4{-Rj{>``$Zx)tNJJG|JL1AL)BrdoJ(1}P!4mD(94Vn*JPtcIsQg zpM7%lfFbJOc}*aLm4_js`|#WoviVEeMZ!P+{@Z2@U573zIt}+ksNSvWl5qGRZ}vWK z#u|_gj2#K(?xTlG=UdI5w7dj06OsRKW7nC? zCA{rF&+=MrO*LmigTPKSRqatI4?Wpj91h)y183KBdGKH}AJ8$;et{Kxb zh16~O2${Yqvg-|sld!jI#s}vz^ji`q&R)&ObBZ3Q$OwB^|I!Ol#!@LtQApY-v24P(ZNUsyyi;5wgnxZiaf?wpyz)j;cw^c<+>ias4_$bPHLz>t z4R}u55T;sWPZkkcBD)B`d)0%UYz;m87#eazn%T^5wF-oH*L(fWI(7z zC!K?HqoN~x-hEI1Yol}X3VR=*i}n9D?LXmF|9EJZ;mt6t0h#QzyNZmoeF?rMc}V!E zzuM{ZJ63h1ZJA-snCSrlJL}U|2Q&Q@DpQ2_J@8GRv|eOb9H=rmy*X1_5XWUd_x7h- zgYGxGGpTE9!cV>WGtV<*88Q4e$hM)40O6~28B18I-{$ifGaTzALPu3>#O_dUkMLhV z{MofeUdEy=f$hTPOoFmAb)`jkQSt7fmE+o8TTI}3yy7A}`H64#XDBlq;{;u!a|;2t zexi5?pYXJP?`&mwjfw}}qDG9k+Z6rlJRwv>Oy5;cITp`OTONk50SlbwY7)nxWjS$bTruP3Mbr zMiYMIuI4R9WrjsgBX#n&tyHNlHsTvm?@=Fz@Rpx^>0^dA!)r>R^;E|MIVAMd?br4i z#=$JLA*18*6#XT>(7j0bn#Vb=I2U?UUlM+Q`YLahFIWL`S?gh>m`j?)ijONR2%miL z1%5uam(bzE3k;U?s9*FaCE#J|ixS>h|B^rUn5q4g2Obqa7(pZzM1sVI0bcO3r~a8S z@{A6B^+?9b2g-+u<)M#qSO^!Mae`l;w4W?dSBl7_T#Uy7@6iDueBTYf@LOQHwnS|c z%}i20j+HArM0na8W@DLeiES5^HDkrI)esO~_02oHJDTA-YfZa#w>Y%e#CM=z*CT^{cnNR5 z_w>gZ-W3vCE6ryR+tA@6Y(4xbi-xql-x6{lA*df!3JHhb_>tc>WSOpPyytR*2X15; zot7ib3B*Mmcft?fefs5wxV_!;RC3JIy!!O32HpwMB_K zz4(pR_L^l-`0(d~+8V+q{p}GSBJ6-* zp~{J6XZ(Jx%q0BW$$LI(6l5$o7V6cd*-EwAJ5MJavAkjiBYtqkPCpkj0-k9cyA&DO z@iC|_8XCeYD-RntVlGItBaLVbU*6!?NSejP8o#1aDpu=<;NsyxBXOTO=x`H$3pit8xZkX}2_N~D6YnwH8A1Ov-LZt`JJjkEUjMQ`{2RlRVK9+-OP4cRNJoFg z{JL2%k4z=p_p<@-b!W`(G|EmXV7Dt`!Y_Vw-IURqF%Q#-5eH7>YQ09$5$4|iFF!JL z874T5ZqzY@?qS07JgiPV;q9Nl)^A#_sQZd`0%>5}Tk{St1?qI2dW3x^e!}NPWT@dZ zy;-?sZ%_shzU8T3_CdCF_?>XGB80rvmKrVc)_zavBK+?c%=-{ZMqJ&{WX$7mD>F-s zRrp5d7ThU|I1LAs2Evzr=T7fOXQ(Bk0hmS|QZn~G9W}zKC%*ofCJdcOsVrdb4{88m zl)s4SN?pGau4%s5A6hH6=c?2#%70@sY3l>rOd>q^81Y;4vr}pJ%K2CA{+^H~iR8gz-lnHsEoK zY6X|Esj#kA5hE?K5AsEHta`lnv;O2ZZE z$z^bnFxc-{pHI_16Tb9)kM@0rDfZfD8MHpG&3QL~dh9acudliFRwFZ1L&O&xBFPxp zm;C5_zYbZOpd_ftlQHGPCEjAlw)5H-!jV%S;&&QCrzX+19OJB3pn`Cmg*b*(E(yPQ z>K5ATn;Wt-if=|5J&yt9Wmj<{boz=RhkJu@$1|2M)SmRwlq|) z^1?2}T;lEZo$TaJMYq++^!+pVU?NdWZc*!d+-fSJ)ew-Z(&JLcZyd!-7%g|AAj1lK5~D&{o1qNeTg@0>rU+#;eEfj&8HY1Z(D9k z$9ed06$!%KQy+fv{l&v^S@gx*x=ytP;mNl?cdxZ|*>TDSgGobk<>{+&r~}^<_CKki zO4=sjBTl^L6V~SC_gkCn>2xdB=z^}32~TO9?(a65yx$u2&BZiHc;~{a?_B4TiH=v3 zNNX`ovR3^H!n5D-hkl)SyeiUr1#!ta1r8oHGGX!ThkVXPaoaI4bQlM9aF?U_Ty=PM z&QUr^EjZyXkIa^=g=3j>-c@R|$Q}XJ>!0ki+Alel-uRS^POIj(VppVyJK*gMZ9fSJn0i2UqR<2jk{G{MOa!iaijI*iM2uHMXlfQz=m}{d+dN zs0|ZtnfuPetl>-Vmxd)|baA5+Yx0mbNw~ZB$6l))%T(})_2lVveJ1I8e@%4@;p7KT z?z(?^Xz{Sjbcnk4kZ|GNfBH{r@>tYL)WfW(#*4Kj!iTSU@$0RnEAji-<>ulVTz-7? zlwpTp+IvzcIM36T2^V{xzR_C7g7 z{<3xcjL)%XUud7=SQ#!y88Q_`u>2$T>YJdRy&~NEtHI}6ua3pdPqa6**pTx^ht#bi zJpAV8y~mn3mg$mcf*TIo%e+vRn|TuUd76qA;lEyS^Va*Zwb4ddQzYeDEV6ICN*v+F z*Pi3&%dxnriN4{dAE}s#yjzEi@Ygqe?AzAEA^bjC#m6?KA*XoQrPryP6K+}ib-(AZ z^?1;$5EB0nbj3pW)xUWDiAMg8W5aJcGsdD90vDy4@RVmg&&MD~j6RO~Ue}k+{f316`-;j3a#Lg>ydi zfgqttNE-wa%Z%;`k{R`#$2f8bKk&MX-(=(j0k;TSc^5TBjp6W;-ET8(+(+mr8{SC`Pvw^K- zzuUb<1)Xr=j#uol8o_L_8l$L*jZQDw(cza(iiGe-184aaMzD99MAB(7_#9Bn5hcvM z~Bmb*b2~I?gc4QTW&_xL#2ac0cy5en%+S z#LS??P)#71F46aQC>p|vkNnq#Mo}=h*=S>Q#K3g~eZ>trK4Qu;=>ht9(@4>m;i;#oQ=-AQs65L@}c%2(NhaZ9A;RtU2mY zfG20O@*7ml3IE{K>)v9O1NNocpRSgg&3SjhTf5d*cj^F=R2=P}P}&Lq=HvG@toe?# z;}oV$ijjx8IYqetODFk_$zV>UX>Ydh_0DN_{g2_7aY_>2*m`2!+QwNvlRaNOaWw6g zC%LMGZ~yqWcU#q9>&aJ*G#eJg#c7OM1ANDlsmfUQq+C&Fq$fU_#r3ExPSLfZsEu{Xn zDO*~YUTpKKv539MMrEPZ3<@kP?#C^Jxmi8dmYbTImiYAIbgPh%gjBEA=YrwQbyw=Z zTZ10%0W-Q=bCqWAK!Oh_kYUeGwjjB%u$%Z2LcODiR8K=Cr~Dk0l4TT>@sJ~xmz>CZzCQJMLVW_6a>&Dn>vP?$QsV#wqSC$`e-Wc<3tCd*yAoSNlB zxk%v^P$YPdsak8|xQovy9zKlAGn<$OGR(aLV0a<#+_&DDN+ZuEx^n!#?)~y1%+?%d zchq9_2=@G zraZ8EDJHh4_kH0MB+3rxO&lph;*mdyhxcsjQkSn-%$ay4Rtvcc4mdxgJd@kMk!!KIT$97YHuFA z>)nvwmn$`M-CicB@PQ1chs5BN4=->Aqg!}_*40vM>WVN*VL`&b(zW*Pi>QVwCMJqS zAy5_|*_3;1;^K$kq?PIRq%?3v0?qBgQe(Ui7(ihLj-E~OveOWp{r}$~@Kbmh=Euq%HQQsg zjp5F(o-Zvdt3t#{LqV?V|EJZ6x2NvXdI&WaHV{Y7V4K~XKEaF@MPcvgUWvPyEa2^1 zp(nR?ZEjVWU%4*C@OllVHbw+aw*C-p@NeS3c^MOx6Z^SI!&V8WJ( zIzE+Tq^H38f?pRxkx6M%q-G(U=I*7R<44qEow^#|V`07yJ_QM2D zM2gB=d;i*xqFU50DjZ#bx!_T>0yaflm~^7rBZaS%|FYU-H@q4SeOuD|a&X}`1r-8D zyGf+XEH}Xd;^aTCxF`KvDsTHm#=C;&?_-CSzS>v)QCy@?lj8REaE&b`2vhEyJ1m9LXnAB;@E*Ogm^rF z-TUF%UR}kC2wZWRK)Rrl*F>hdH(z!Kh^QUI8N1 z$@d%qKS37!9b3tQE+<;1can=jaxgH26ZLT{oWok7DRijR5rK83M{+h+H1tUDH?zfv z&x>q8xV`V}zC;$;5wbTTZJ+ohMgeWPEOuq^nxLhH#`TF(LSBFyS2vn)i2|3S>2QN1 z0j0^7GrX(=jr!t(xLHvL-7_gHf5y8nkD#`NTGw+Z31PV^iw=4^d{t@UC{?|>*eZ`w z`l)JquzD1g(;w+Lw?^bkXJuAZnp61Y%Cp&Mnp1jxpJ8(mL&c~^k7Sja&!Z8_HP)xm zE2!r{ywtxf&CCpOs{oGlQFx0Wjw;$_5&lllgeTS(`o!^+F^vWwm>Na%;!@xPyFC1aBGbzSX*%^C&K5v}%6#6^CH_dd<#E~lY9LI*G$>I;hnA6DwI@`1u^pO*MHmg&V z8W(p$g|bWfi|fdP{(3JX;8kLQ&MtU?mt_y-g{)fvD!S3admu5F`Y5WKtQZRWP2mF? zEQWnhjE|^yWCmFr2E7U`vTRSn{E)}VxBZ(l=*$=v-~BYVEp(^@!1o>enyP;kF5jC za@yBu$Le?i@_H6LUdeQdHj6ncouPo3j>{f0(H2x)@U2Y({2j?4egq73_(tazG|`5E zp$uB0>VcZDIq9h|E$HZo>LlW#puj1=HjR0@gSM#NWs5!5ZEJ80FQrjxyY+r+AtCrS zej#hz+A39r$VB9WI#GW(je#USiG8o|M^?-pk6}9Ek$BdTk&6$2?Z`Asv*n@s!V)6T z5|jo%hUmRGqCNXD{k?g#XMEx}yL$x3(Kf7o z6ySbwsGz;nqw*#Z!s7f=_d=Zi$RR`pUE6RQFLJYN*2oroS;iG!sW`z9iw@4FTgZqZ z^kG$T%od=wD4H<^OBKY2M-v$BTkV36;)zt6*uIW7l=nLSPuGM(1<1CiY}&{O?U4#0 zcFEzad0*GpVj#fRW06%cFRNJKZqM}y_aLHQzFtplU*Bvk?(b=yUp!KQ<{vyMPN_`OWws0zOH2VBk^*`*VZy`zr?) z>x<2tK1=EQLn@NJLwsU&4p-a>73?H_!{wVR!?SH*xqk>(@8tHEbI?aK zeVdEcv3wUz82-&kX6(DJ7)KPm1p5T>X&eqjDD2#688l&`5^0;4@SD z`*EKYl2Y1CjZCaqaoW&gMVLKXg#oF2isW8PMwU>UU47kI@%cKj*s6w|DB{ysx$EL+^pDrm3rAa3+=GoNv8z-=7aI~!R{l{3MHsQcFJh)c9$=Tp zlIeo$Ls~fbj~zj;y^|ECIj(Gx^b>PSok9|>XGXX!f^Fqp^ctHdvaF=+NR-lu6dV&~ zBe9J}Jwii}Y-cw&j?vF{6*qOYcn7(pH6ZazIyad7XkKCU==fOg*?!{`N(n1nj4V_6 zz-+8`F~y-_IOCny#O#I~xQCmjT1(tep06X@D)AnQNqLpa3-x*IH<9_G@DpLdh$HN5 zB!nrBWyGRDj6|YjJ>c!7a1=os=y|GS7Y1JBB|Jg+YkjVFqp){He3B4f&bQFa&EuYp z9I_r9e@fA-T+_Zygoza`YxB98I`$H3_11c7%kULQRQ70C$K_(Blt*IkRAz~v@|Ziz zKINU-N;dZ5UGV@LJe7SmVCpF&krY<3WYO$7T z8fGY77PQE=`UcJcr%mFlV8Asb0*vQ)ZJ@syppw~wXu(mUIXeT5*va{D7d?X_*TV+( zr?BUZWmAHku(x@`lhMR1R|uU+O=1K>sg;3E1Mf$TFFRnL;;WA?gYO%_XA3qnAj9EP zRji6#N5?srsIb0EL+JM8Y&`L-alt`)24FZ|xGu_5`4f+_sgxr6%4~w0TFLA}=K{D@ zPeJFEsUNYW$ag5=dMU7vX=36;XleG2+DE-uUn{;#nbvC93~@5rZYMxAziYO404s}_ z>cUJ#jD;E%?o|8cX=bvOsy5$#6F=GfC`R&VN$^A1fr_Y%sCSK4w;D?{d_nhx=TJhH zqu9n}bk3OjuWWrW1aKsWFa0r%WO6L=`BbDuNJq_`i+SpiqYf9mES7l}E;oepQGj0@ zc)L0ffxGJn7k4AW)5r?@snkUpczuwKcuesrOwxD&K@30;vWO+d1co=FL52h?mw7UY z`+Hu?0=fER^u3VGhunq;SeerMoKSMuJEzvJbeK7cfgH@xfqFf`gs&vEU~3FV=proI$;&SU+`n0-9yt zDk#UTrQkI}EfBikeK6Noh0n$Paq3mXE%r_q>v;sEBMlTFZAvdimvFj;Z=|Z_zpTaz zQY|lJmm_|(D}Er*HGecqQu%w@zHDtvApO|#T8=JnFV$-dpx@cjD>qXCFzax(u4X&_Qa<gG%Bq+q8Z3@cAW~#Du|wfJc*K& zJu1F6Ms(<9_$rzBUGN-42OZtE*LgI9&hFO%gz7vyRaS$utmn*ozlZCNz^Zb|&2)YZywAwA+5w}YPO<)cN zb!(c!9d2*8-IRemcf*};4S{A#VI&2KDtpaEXj`Hu*PG+MHV0%iLX|vI5aGW5`W#}W z@rN=++TsH_CaG=9IB#PkTtL6~A8j7>`PRXDFNtk&F};D~m4!;A+0-Cxr5%RJNO30_lm#OgXQ8`De$B|GOpx77OkG33G8$EXrX>ntFC6OG$f=Fekp;nzsyd*OlK zpL6ZRicrds=M)GH4F(>>eO)|oTY#=|>VOL#uVk|f@^Ib;{1exi$^s8C6g!3ow+@eG z41^biwxc2I9e)PF<_=F)S6WIk<_9xgYsJ)b za&@-9-BLK}6!|uk;$M8a5H* zFD+@C)J=^Kj}DFvPV5=O#1D}YLswVW5m98~8BsZ-o8uFM6C*=YBfCc?d}EVTTH~^_ z?j{=gIl5zX>XPBH@sT~dr$)Bs24IQY?2Pu_x$Iq~_dpf$J9b>M%WAkRv%)Hy$T%S) z2MqJqBE~#LaVs~(!JR52I6qUKK?Rt%RqbBO0UTouXM2j`^wg0&U}q-6Y3*l;>y1ur z&k8nzlRwoda0Ddcq;w$?W1B$!iQ`$I?8Kh%cSX)ZHbAKBZ8)~xl6D1yp+dS!7p9_E z5gWdZ>o)sdY|5orSuw>=roX5{0XEm0gjk>FwQ+o z>Yr(K`NrHi99;vz46^MA0Z$L{NnjSJUt`=($-u5*sFAHx+xF~%%=)oVC)otSF$~CJ zr7_yl9-W}%f~fpyayJmJw9C36q71n}`egU^oqH}E+&R7% z$LS4Q=m^0nzn(b zN0)uOHsyiJLN?`=Zpodq;oJ=y{FpDZ$c-Gqw#!`a&7|irS`lOD-=us(jCZsUQJV+m zW^KJD{Gdou<3SZqj!kAibp=6tpDnbwZMHvo>NRvA+tCoXW5?2ZTbB!R=F{r}C#fA7 z!u!ZiO8G{<$3Zr0l=cYq!}er`wZAzfh=yKW3G(ltn{*tXlKGj&{o6$ zhzEhdjpJ{Ye$c2yt=$S=tTFoG8%3*nDD4ug)#?ovW>S zg~>^V&Lvu}eX~#_d!}s+Ca>u5J#5>C#(kG*f!lw66-L@6L~Tyt>d6fm#+WYXPFb5y zZXo*+QbP{y&N5FM%swP%Sli<#~cPm)~xwFnZ@7(i9pWHf>q2ncz$yQt)j8Pzu z?wQIRhSK#NM+t1W!j5B z;|(b?`XsHymM)Cliso=@Z@Y~T8pMjbjjAwVkvulCf!9Sw=gYX>4Z^P}F?tSU%QrkiSd~Y42R3 zbFzOy(hIt1dNO!=?AblWI_*K5&|Lf|yEg%h!Nv1q*kPofLEL1dOdEh}!9#Pe^5kal zJUc~ElE95W$!sNw7RL@A2V8LpLJ}x+I$vZ<$3I(=d~Ab!4zDDeoZQSr>hi+mlzvp(4HWIFkO1Q2=I6TN zI-!)?^8v%eP=RaAI`^crMiAIaQ&rmiaqOatH%0h z3C~+=kWz`0CeAFZb?s0T$xqcVS_VhY#T5#uNs!+s9c4FzDx$K~EYTZ1f`cGrB#(En zk&L7|HOO#&sz1i{lw{n|wVP=?o7e-OEvDK!ames!FWeh)t+SG9(V3M@N5l%aI%|;QD>KmD*jH zUjEAew1g>TTdtZ9WvT1IUI z{utEpy^qGy#WTAPl`9A4c%KPFgn@qdw1!Ci=tf;Jgj~#Wn^=ZRk@jxMZA2EAaXDg= z>=yD0ADPoQC?uzZZG^1FV0^3<2!O*4VvRrc;0T`S+vO*UbN?tHx{oWO9ukkHQiH35 z55iodY$t;>avTN*N9?Ak17hN{wp(TfTXO4oc#@4$tqIo7LquS*@NF?jEmmN`pkOE5 z3JHz=R|5Azpumj5K;#N*yDL)mAj&wE(T&&Z)^%4{2>*YO1HhKF(QI7TAz*+v-3Rxc z#am`Xp4{XVz>1bfATWtsd#a1(?|%|RS1}i*O`aaTz~l1i==DB zUAq^Jj?*I?XW>QiSXFQ))pU_5HFSyE(teWANam7weJWmH~fx6>ttIMP6_Xjo5o!aQ?2@x`x#-emT*j98q0q< ztVSAu+8!t^?86aDd~>3z!>2J}5GZfsOY^h!+LGJGt07Yz-x#PNwM9YV zGXo=?zL1dmLV?mDUWPlOwX3w?&v|oCLH@Ogn8@Yryhx16h4hSs?a5$aBuP?KR+OkC zViT4%o5p8McZN?lojhqy?;p~_wh|Y!@DXZhuqrPX99wD|B2;jb!@!+uL&DOEQhQ3! zrnkYFcgioi?C*nsjJfzt_SXwgG#X}Cl$>%#fr14S=~b3MqZMwN;jLz0{uznTn=1t(99VVyKoU#!mXG;fYK2{Hx9UEP2qn^-qg z5dG)94WtoQHmuGanycXanVkU6AX5BbNaOPYLqhCMMFz+!BJPIl zx{=-6_Dl_5HZi=L$!~Dk9JxqJU*Nh9tR|SlZSf)#VGzl+5cDd2vccMtx>C|eW?%iv z%!)x9+Mi%V40Ga$gB9aEv=~BIH(Vp*DmIOZTzBUBBYwH>i@Ui@b_FvDu`j_PYuez2 zzf8m^h%FE~GBFmp-90%B>@rBXYc|AyD&#z*Aka>t1yO~=Eq@cMa|olDT{MwpA0xOe z)zt~-dYh5+3w?h>iyI~=C4c#fjsi!`I4z9Q%UZ&s5?ME{Nc<~cVE($JxTht+>aJ)p ztSMgOqbjjupTC(4S~!u7BjKegM%GbYtP;2xKrDttwSFRP+^VcqBK8G0B^wqf~xL;1egWEajJN8QP zDIgN+jTD3D@U!%m$mQF@RU5P*R>c~tptHDMV;*xW)dWU@+l>-il%7G;2bV3nHr-s; zrH?lqk&bKEckC#tQG;2M-Ip0E5QxS4T&0zpFD=ntVPB|(<=aSsYC>e9*>cZ?| zRYEOM9Y;ifpMuoMl$)2;t>~fi91~RnMTTNZXmUToqoPycMCdoSctG5XabLb?vMF~) zMbgM2Neu`k+!q1k5(Wp`eUr^I3X}QsdkT|>*7ZzIZe(D#F7yX1NsJGvN|@udheC183W#T#=3&Uf}`I(XRKR&{UqYelZd9q zO85sTGMVqtF!^emJvPNe@;xGEzH zws@`Go6fqy`|o32YauWnml~oZ13dZ z+1dVm`e;M%GlQp-y^uDyGc9eq|6HG@q1GJm-b;^brztJ5-BfFtjZv;KbD9uT{sUZb zJqUNt`RL*q>vDT1uiLaQ#Zj<@dgxO$gZLHcKJ?xo#arL)4e^ku=q2$ z*qmFat|}k%Ku=jFvuFlNxVx)gKRw2hdp95i&BFas26I@O&L75Nq)%r@MmKxWA6D{< z7x_Zd)D)ix4#15Vx5(N@o#BLVopEi82jh&eegjO2(4;T^a;!)76d%A*8UxL?+!VGEtExte5;S-dp086Z@yd8(h>G^g<>i%Q9}q{KnmQVmtjBIs|y?N_CNgVsGqB%R3!F5RDO830R22 zmDlUi7j8&5DZhUA#XGMrB7>zU{V3?`>$|x}9P!23CP!m5GGgB#h|c6vbfw8I#2NIK zepiDmaNmG2DMOG)Y}WkM2)VJLs35G|dFP#H2U5B;k%hNzx*OZ0l098wG2Mg2$}hU` z2OqSeXYRseINk=C%%M@pWF4-r?CwP4tI7br>U!Nf5Lca!z7Vi*H&GitfngaJub;R?k?8boL* zQIMsCySi7hH{uf6m)|FjaGvT_WKaE`IGy760I6T6#hB~rbr`lWkVnokdqmoo>7%K} zSpD;BBaYpmf|?=rY1aP!t-}{yyqz{#naKVnZtYLw6B`)btjX>CMNF-WumvIeqy*PY$ zx{R-O)@v)gifda5gEPa>sc;t2R-+r=j_iC*SI4pEXvEasl_CXU-zE!_J^i^owdzu? zuooI0-(Se#@>Bo!{w0JYgel5=P_Eh7 z&1kdrA1eo8O=*^wMXx!tdy&+wISVjG8B`U!u;y(40>x$<}@~f7=ZI1a_6kM@+$BSCvoxR}7g`qOJNOKJt<^vt&zX9v?Ht zbjc76XfnQ%?Y_?Hl zdLNh`Y3?phQ)M>rIq1k4Dy(w*CCNnvqAaWCTT@7um?>acea;5mH%%gTmm90X0$O3w z|BAdQ4|HHcz0%Q;U@TX_J&0I~UvQc)9UjN$s5_`dd|Z*AigO!}lUs3}_k_qH)*9|r z@4$S0rZT%!fWrr8tW|~bX_n#jU^P!|4i0j=9~iVNl7YuZ+_0Si!a|-`bXLWdOKaHB z<%hEIdF{)4=X+;zJNhgAKn83+tHZFy_BNedWwDCS5<8tvnRK`6NE{^(A_7AMO}q)` zBWgJ(&R4^Yd&WhEJ+Md7C}GJEp^NpG2_hFR4Ng;Z%QKFZLfDAbW9%FYIw&bLo6!jU zX5UEf0M4r5qfBnuMY%5_r8lsa! z2AMF1rJ}}bi0iONvYN@BnYM5@v31M%m56b3uC($0p_xi@dg_r}B=rPDUi%z0BL=T>eI=wcV32ql4)gk(r26Je(-4AnwI{qj_k1XrSyY zq##;C!dXq~lDeJ?2 z*q?H>c3=ywfL*5HJriA2z^>m3<&LFsa7-y28 z!G(#Q5l}y5>iS%QLAh?wj~BjkPV?=dZW6B}PMO6lkt#aslB09(Ll6uIOa2Wu0gcYh z(-?0&AMmvhkSSr`mS0Cg#BvFHneq_{L<4|~>*8u1!KJ7~1uHo$i)7yyUA+pCA7H?B z|JBO=MyY|NGR?JG3r$?S2yKcsu`g3@^wk>&ibBymk~G6{z!y@CFvrSc6=3Vu zd(U4q^+jO4JxDgY*i6TYAF=w088Yec`A~HYMXCm6Hw(U1>PCTuXt?TZ+$$rV-cf_)Tq_Q=d!u7aB&*^0!glE(BLj)y=1 zsL$>{W#Z79Vu-1eAw>UL2n#@sxKf-LRL5RZ*vCG+V%dsg;5uQ0lNZDk>|EyNJe1|#vx$+j(VW$wP!VvI!BWF&p$ zICMvV?8|pyaj+Xl3qb<1Z}9xbdYS8EDDvgIuvy=`$GOOp6VI+8(|%MQ@@_IpA}T zw95n%pWKPP@PbS=A@&rpLXqWoF>7?GudBTep!0rjZ?Ega7Rchh9Dbtqmf~!^jvq$I zUY$Yv_L(*Q&AAQEMZywCt&cg4&c?~H9}JLK<=7I;*y_?Ruq2UXoE4SW8h&tK#onMU z^%rXSM}^QF_4g0$*|lqMH)pfT2gK3*9`kds#o45560{|y z#p&Z-bohp=u$AI;Hn0ruNTsVGb>Xi%AyNsF1-#q0@6;6Jt8F`c*RBy)PaIWb{NSU0HNBBV*AYrg z!9rbd-$vkhB9!e!3V&$F@Fs1tSTO6onV9(d1Y@#a3dJ$Bz8bY}cBO<0bTM_xvb&Td z>^dU_CMqW2GBK8u#ca8e?`tLnN0)p-v+b4&#{bl&x|_MOQ3lb$@Tyv&_;VVHmBV!- zl3^M>P{V=EcNj}b#SD%};WbXi;s>MqA@0kgZeqV#x`reQl3m(9H}jz`4(9!%oC81DR7)0Z(KoP4)V?4T39J zu#7qfO9=`p0kv|_P_8mQ6Q2&_Mp>Qs#Q0Prm>EN=Tzx!AJUSQS!3>G#Rp*@n6?f8% zpYq1-V)4WT#|^2_SSiVhlR-lxjnOI$PlBKLHj-nXKS0U>7fVYH0v&&=0N~H~$OHQ< zll06qYK zvdoTPN(=Mrc>fyY6aNziOu3_yAyw`ck49%sV&X+TB?~Yge1jN4R(Er1)F~h+Ut6n^-yVgs?9X&aRlGZj+Da&U2ZCghGUZOefa13l$u2Kya)wI}38L-qpSb z(JHnS><4=mAJ+h2se}(LPxD*>^FfY+>CuEr^}fuU5`_KiAcHL2OV%aV6m?Y88s$yK z)K!&jDjkSuSJ{t@lU53J#wpkuP`MlmGmIA3f)vNHC+&SA)aAUN^h1eFJlv9=1B!&rG&7w$M@JQk^OJlqyW$bFzo zt0Ewde?Gl)T6G}U96qkp-2*;LWOjyT2HHrjzPG$l%rj)NFayRd^29*^3u*kQi=qXK zGg;QU=p)1|Y?NX~Ef_tTB3W>TW#zUR{hD%wf+%f`V$Be3P*ZdT*dmsx;QNy5M0^Tc zm)St4<1yh$SLg0RRak2pE5?4VQNNxMbN6lBN{vBtOth|iy@oJ&6f@-5^E1QDK!Kq6 zPv{0{-EK_4Gv7jVG}h*1d=SLeWDGcOOgARkACV^!Y(h+HDYDuAZiiG%j$w`>oYdtEAamZFacglJPP{S7gC;%7!0HlOD545-G zfJ<1$uRqX1oZy2w@9z(~b*(Hq z5IYJ+X|L!b9Cs6zP16s`yJWb0zhU$Zxg3N2Sj%6HE3Bj})PE}~scmPJ;{{VFr_OFa zPO>N(a9=ch6eO|%bu{)WHFD>o4Jmk?C?#}AnL>9bHfGBXYxEwRhTw$46|@o+aH5qM zf%bHT)c>X_ONu~ysvNgOq*Y<)A};I~ooEpgjw)5q@i}r^{g(1r8D1Jz%afz^Iuw4) z-5Ygo<0S8*X!A;o`1zA47A+Mg2YFFW^t*jQXP8#FlRJug=NL9w3F4@XkCVl_d+d9< z?YCgJNlXViR5wWd@A1}B71_XX3Y6iOxIp2gLg-;^(7UqjTVh%Snwg*nxLn!5X|^!& zQB+Bcc6*Y7W}J#xu921OB{Kshl3#Tzyr&({PeDu)3K$#`?g@PHYv|d7+lS#J z`E6Xe{x2*uv}gDD#Mt1-?g^98IL=b$pknIOHGOPjV z#&?l5th#{nbX`c9cJbDJOpL&-X{RGp>5?U9#&!zZRfv>|+Zp7jYq*tz?K*S_$ro5x z9;65u<>(9DS3Ngwb;L;wf(0WrNjSFF{YkEZommQQ$&xsnB61h4AYmcQt|JQ^+iZs8 zc`%NZ>%^VGRk>Dg{ipnEd2@HFNi3BnFpjU^Rcvozua+#JPIznJQ(Q zcpRu$o!$W~j+uXJB_R5VcCa+?(TT{+vM~whFo3{)44z$>;NdwmA-Y?7m1TQr1)aXs zoBLTMK)}lmI%DvjlG8P)%v>>1+@UyOg=cPE%TH-uYZ!Gy^bI%yTZccr(-oihir%da zF~b@qP#j$OAED!(q@VQgd(1y+;)5Cz-Dk*s4Rf?26N_7=7yy);rT8Mjer#;Y7ihe_ zWyC0UPnU1d?LST+hX!Frh810~5LXDy_`ttQ{UsULB;!R-Ynryj3{~p&we&LDvy(M^ zG(<8xG*X51Mp<}(!9JEE*AZ}3ZEE2*3K+<&E<`%gQSZqCo0L;@u|$|gF`ZgV-a|+{ zSGgDG!N1xX(Uz+bWj{7qLG% z&}NEc^uvL2aU7Fr4x1D~FJm%g_1Se)mv=G&^pMpm^>}P<;9aEU<=7gHvjhfW(_XAV zxgpC+(Fz5ucjqBj7!`8bzlUme@1e>tW*ALe3|p#RSZ~0L49%4qg?#54L*;69tbPc5 zA6HbU`IKcHBKtoXmH2u$SU_QW6CmAtZJ>zlntWkZru%LySdqwN0_=? z!&f5mOQ5oTh<{BlmE_4h?%J!hN_?~*pV0y&Z=*TtG>>BBM3p~9;WioHK`UgvBg%Cf zp&-XgEel3FZ6)&Q%g}0o8DUWjy&&b#dv0W|CDZy6bs0ab90o;l2U4|su*|i>M&XR) zDd3mpE0`dGw#p2vKxIaqZm*b*N;7f!i3kX=tEq8p+>7g?NZJ}BCggDs4HG(Ya455C zLF@K1%c4DkN^>V}_(Q`5-Wbjff(Ge2#a^|>1dJ++Kx-bKQd@yeQ&p}_y6bZyKVeOC zfOeMmCJd;N6;^Rf-MXlQ@*_^k!J%5y20Us^z|DsJ%m};JfC+6GaUMh&QUqI+YL(;_Q z9qg>s@EJzAWYW3_hl&3u${oCOUr6IT0JSOxX!2XHdCj+8^E~`dFn{Ycx9G2k!{nTH zQ2#V4*%|Rw(ncWkOT>9G6G6OgHuhmf6VZWtpXVq75c$=lSxAect*=@tisSMuNm zB-3fq{Bz_svg_r)?oZbT5+wRNN;|?#wWr6Ik;2=ETvXX2Tk+FnD+*3~5AS&5(|SP| z@|PMzqzd#%$^aw>hBTHJ0{q463S+;rux#O7MYU-s){r+H8pb1;T7;950A*lOJOmBa{|#j#JRL2^LLva z%SR=mT3i!%tmF$sb+BF9v5LJ%jkp67^poxj@+Re*A^x$rGX58o1Hv%+p(+0q7@by$ zo^Y*`NOD`PdDQX2Pu%eWZ3q%@T~RauDq*WhH#ici?22wugu{Q#2h?Sg$0hbztadr|98__ktmP^=$2@&@HMT2@PCzdw zy6+xNJ`35)?oXGPktmhD(acIx6DK*6#%sJh4+k`B=hxe}QQalxR;T{W1t0#Y2@-lc zn;#U8ERbq4ha~mb5lQr^SJvmmOXQc}Q_^HwX5Hv@c75;IObd?K7HG^M%4@TQp|6@B zFC!aMUgzp^h`H%aT8I5p4S(hjRO|aoymUH$0RIu8a<3Abjm(K#LOagR>lUM{Y^_tl zbN`P@DnL}o<5ESOTW1?QqZ+(Qw6OWFp}J(OuNOrq12&w zdgN6nu8pqTU*R_ja6F|8k@_-jBSmD3d-`(L#n*O2#KE^i$FT_+y~e@e!5Op*K>{&L zJY@_Q){!Li%-11sPp5M zHT*Dvj4og?xM_nZ;cVek2FH#wKu0p1wQKgP8;=InRS^26(`0BHhOMM8y}hn-&@lUf z-Dh;ekKxivQ2El}eIL$h=itNodrS708hv18q8WGbS*)d8fq1yTQp(BcGptasW|G5R zVGBJgA*r`lRG@T(Bqf?hBA@I8xS$Nw)k<)Zh(2G#T?P>1y&N-ypK2=cfKJ51Kp@); z7jDmlD#5on;Bz|B)r0cU7|B^hHIPn*y$r|%LNz(PTH(~Kr7bpR!F?`C!FOnEoOvJo=e3>pO-usjZcQj;3 z97V1kfdm61sZ-{?5?z{j>tq+cY8=BZk=R0hu8!{nxC_snpsK6T)z5fe8(uI!NrIQ) z62!ld$Mp=Od&Y;ym=BJ3vNz6}jl|sdhB6 z{QqU|-S^tMuKm&fc?yM1wnSrrout_<^`#haOo9ys>?8%32qZwUkfTG`zKu^0w5{q z69+*3g#3MtLV;TW}%Wp^6i>zqCU=Ui7XSaVm;7Nx6cu16uO9U z$^cn(lyO53YH)yQp{gCKg!N7?+RWA;L(VT+(B(ib zgpTR8gv_c^^UNK@f=Sm-o=pkc|E7BZ_}~Zs+i&#;{E3Kb`N`W>D>UL(?lSwW&x2rU z)Zvw&fzj9sdoTLdFc;`I?Zsc_LgW!m+EjC%f~u6$W;q-56R9{;;n*>|L=mUL>%Yut zcJqcq2Zb>3Kg%VJpXKnRZBndr2;%<{o=rc>cDU1^Iu&sVp4(Zg8dAI}f zY4OHlh$Xyjx#O9&_yC3rP)u4M?AOcti+ zpwnyiJiyA}$6w4a0P=wNhvls(f{&)6dL=n5%2x0^D^bo=9vv6!8KwFg>)bhG+sw zc@{C*e4Rnq{(JjB1hR;R2LlXSA3)4pr(Ai82efEf%{p&su9Ln7nW!{_`H0GMwGIj}t(b-m z!kH!eTwsS1l*Wdd4HK^Zl#l^;b{KJ0;N)=2Pnv;m5SCdUF7An~BA`Q-oa25)8Ym@e z-*&EXri%BVE4guM9@XvwgnDhPtn6-Vt?o0O9}9Euai($-WLjRwJKK4rVkX3pOdH${ zDJIRmtcj2M9#A$QD>$wGxKanjdWRh)_#>orX5u1yue+l#1uPTC@z^4Jo#}_-NFPC6 zc{Ti{hRr`lG*GA2f60{lmf}A!xiSn<%Scm#9n za3*Y==Zt>sq5c5f{3-`x|8g5!r`*W~dw{PpUP+H&N6b4}Q3flxf>PcuusK{iC{;EZ zHCbW=9EFH2#w4La8;Ub`h7e{jRk)I)Q<_TZFnl5A{-k%+?lft3Nj~xwU>t=M@VT%k z_Mz7u?Cv3YpCXlxJXg8oa@c~y6%KnOX$Cl1=#&|W89;ldx=RBUj4zng2$x=`Nsbxy zC~rNV!d?T&7nf5fMC|8&u&LaVoLYid{!TF7kZn2+=)%B7p-@M2&@TbEZ_v~|Lq^~B zU>H1RBATd+JOE$0aRz0=*bFxbeCxop8+Bnx0N-~e-VN?ETrux6AznrAI~(zE`7oVO z7>u4xx7;V4580a!Gbwkbis$Oa`>s<$p_$xXm6IuLt+2SwH>mBxGAPMd)l?+QM6-}L zYsM|1XdHikw9+_ueR0Nu$}8PR$BT~^AK~%BGW7&{$RE;I{I^-j6ps*<)fuF%p4?=r zNt>$3Uz=|wwoug?wobrhf+e?8Hpc8fn*l1$1>mu$<$6tDW<-<4Z-A{!c4Ff z$*xY}`JAA^?0Bof4(&O0aBgDP0sjlKjw#7~1NUrPp%S>0!f|GjOuO0zkd$|p%{Q6o z%-x6w6kVw=jmXwCK>8W*ja`m2*!C7J^`1kC^%*xGi?^b|C9V>WHHL1|O4H!;Z zq&vx%)u)pUgNaFQeBsF9OWtbJjH~?Rbm}k(*OUo>jx@`yRZq&R^M2I%m}VZbEX;Gw zb2yHOJ|$idv*Itf=LN~|S;6GmK;W&$1H^!&2$2K9i2N3*3hNW=#mVD}6r7R4M{;xI z--+X}>eAumxTW^<8=1$N$BkjC&d2nCoIxm77E!(T=qhR`D>L!osSKH)xbxFiVM zy;8%cchs)~;duCrlYp3NY@tfZ1%fWCOE^cQQ9&CLB7;zSaM3~{lWM%ZHGJMZ;8c#zQLfop1ndV7plFaI<-76sspk1Tx(OQV_vphl z-S5#wQ9~X(%TeqEK^3JF|L&C)gnp`y9jfeumub?7l@%0}Q7y}!>*4jPeZ@0U#%&r6 zo3WcfTUFKJn*F$t8{ckuoP&l`Ed;U89B_Vja_xqSf14Ripwn($ED~w~be8VsDRf1X z(G{~AXDK3rQNB{wAT{2YDnHRkSLW7{l2}ljNBuAq)R$_Af(t!d>C9-42JUIA%Q8>D z#BsOw0`A#Ax{p)VUG+!06EkZoI#r?0Q-6it)XUgBcl;75(N$O8eiaZ)uf38cGv0Un z5)Q{KA{|uew#m`F?tGDgs3$57E#Ce#BI;*k}Ggij# zr$)c&CckPNNJ#SpR%I6&Ce0|;P;(6^HOz)YF=13b7s19t?7#TQj;gY{cpRktU8onB;Zpk0A;JrGz$XxDK%tf0+t&YP_IUJ<`%=*B7u@>?)WXk3H z0^zr|vMjdLpl&f<7nJ8|TW94{i<0w!xzuH1LlelxdL(Xi1p0>sxD(yVisVe!Xg)@2`tqpyeuSeLEO)BF8iOU$tOH(qDu*Rw6Q z0fTeuDfp{NyW%#;KZ5U{{~=C(zeaDq#Sa4~J2SK|*^+dqXG|T2$Q2ZecVeVO`2#_ra$oWSYI;NEq3KtPB)|35h$h7^ z6WlPG8du)x>M{**?U6v1*zn^fhG9}9rt&Do+IjVFMDS{=K#*ZbJsh1(c7+{^MXf%;)0ten;*%N^t+{P~4Z0!Cpac zUfEny=8fUCt0#rpB!r?`{Mj>izq|<49>9r#qK~*~GGx)bc1Bm1mzwehaa?4J=sU>J zNxSPWiy5O!^jSiNRLcve+g|S&78>ZQ?1}P=xO!0J;e60$CMddc3S|mT4?(#wl)Q4G z7i9%+b^P2TQcjw%Mm%mZn^`y_EIsw~sC{u4-Y0?Z3v>Dj-6hc>SHVc(_}3uA&ytEH z`&Z!L+`oLTF23aqBkV&wm7aCN^`!tV4g?=z_&zKpV?#*Rd__=>#e?g-xG}zj>Jf4A zh>i!ITe!##$GyUDvbhU!fBZ%vzohV}M3Pht`{+0_^NxtvFxz%~ig?`j+>D|N)(H8A z6>uM8TdZravGPyJ-W2_){pf1mym)fqy#~IEx%BW%!+MhlKXs@kh5+$ttCbvYu!f#} z%aedw4v47YZy9H^G{qY@7wKHgKR^&_!4!G@-euo7WMTh=X+dSIB??-hKf~dI>kM;B%QQ@jzWD=|Ed}3Liqw!HgIgTi2CFw}}PJ94i`_ zoE;M&8*CNgX{iiiW`$>qv+%S!_qR4BG;2&CwE1A#AkvuaD>iO>I2DQBSY-W z_w>cgABIJ`x8fV@4W@=)08C6Ij8Q+x*9WvlOZ8W3N3>~D?2iynW{Kcl7>jww+3V=! zrs@*RQGKb9^i^;}>%r*n;QD-8TzW|5)2JVWV0#Jaz?Ov_iIKjqECgPfSdWYXX~+Xv zF1K(oO{L?m+3z=4=sWEi2Y~;({|H(Drw{x|Tgdv0;JO%vsA%-Y?D&EmJo z_$hf}0Ka^RpEvMTk{np~FCQc4Lx2(& z0r!6_Qxsft#vMEEIDwLyr959DK8zg8!l?Q%C(VKrtzMDUiJW8_tQbeKQ<~68t58c6 zs5R39z_CUu+`0#FCV^{jl3FnKg%}9Q^l2QfNS7So|>#Iz+W3(I5&0;j5R-l$P{ z;wXj|cMx!f%nVipg6lv<5fJCh3OrGR8=fV&i$Poh9ewM`s**}V&+;*0%n1oQLc7+u z>SI0N+C%Eb!+|k^$_KK6W^i2;v=|Ithr;Lv!D%yU()2Xj ztkySnP#j`)e`9?`jJnNcGA*7fcn+2i2M|pf!{Dy@9&^}$jY+{!G#Aw09j@LM;_d)z$rEF zbS{;^M!V6;uzNY4WsvXAp-f_Gt^p+_pfq(`@Ie(l3}6px;PHhu>lMdM=v`W!t8(EW z|5{gc0*cx8uMb4Erh*kRjlS*E5t?oX(nIACsmtsPqgdJ}QtaeQJ4Ti;s0J@E@- zLAk7H`|aK!$>FD&#mi}EjkVMe=3N_hN3WOgR9k~?p7vVE>qKIDj3uE8QmcG9p;+eh zg_P`^r1hm{~L%qZu(3{dhW|J+l9=?8BWGTJlEXX{mngN z#*G{wBdiOG---)8K!Wq5?E?R>RNFkYvxM6erDVYv(6o#qy;Ow(3y`lXGCy-{fd|kV z(=B-zJ!qKg&UQP#Cc^En?XUu8~NNDK2rR4OzdO z%bRP)(ESIz3>(UP@>lv~f#;N*XQ#E@wLAE0+=A_(cX{4oA853wnb~}o@kIBo2E)l~ z$%W_Z;poQ5DiWtiNeb;gdwubWe2A&Y^zgCg)Xv#W%)xtaioqqH$yPs zpdT&a29eSP!zBszxZPMW=iqEHmyUTOUwI!Nn*&D}IC3fv#O-}$dHLcxK3p0M&+579 zBw5?A^07GG$4JB5=a{iPHcWY%9 zofzJ_J~{|#d><%kWeYq0x%WQ*C|Pm@MLWYn|C3mFXRa)x2^KGil$o3N9M2&$4M3GG zm?_IEHW7g(yGC!6K)AAjv&Q8O0{>P1ngcjPTiL?4umXyu{15u709wMLLOY@ubBW?! z-IGX)ayXOdV*$y*9n0F_`Lj$MPYh5-2fRGZb0|wd9Rj+5P^rx74PL6S8=nWvz$3)6 z^C|&~XHW!h#Jey&CaNS4Z4Dp~I~JChyo~sPhc=hC^6@(+5ykHN1J!RbO2UWaw!Q>U z9G~Mpp|Xz_Lk*+ar`7fBQ<&4 zisnN~G^#iZxtCPiCgvJ7@KtK-&?7m~O4YpQ#1u+t)PS2QJ~srL*qsd0o%R!i{3}qh zv}YImp=|n6>b*~Q9*siNAa zjZ|b2D+M1;iEG^Qs6b}XLA|L16vQ4XGv@wEI@Dz34a)R5YzV&EG@eo;S}VIZO$NvY_3YH z@A=#+2}!^qU;>G?5z!2JK%K$tTVft9|2Wv=DEj^r*w9YmND8jEvM;ug4aWpba&X3s=;7Sr&3i65`ZF4wgFQ#u6MG@6?t?Z~9A1i#5p56hPx4i-pOkyH2BBSlkL5|&8;IaZ;+}NpnT#5ku1LX zu#X3D9I!WnNMnfh01wyj5B8nf42Oj^dfCE-plCAJJ)E-t{T0Z8O_=V#U|2|nb z9l2-ox+@WotnLS9?YbrwV;l)E1cAFd+j zIDe@PPDDooJrJtCLVL(yS(lZFZE)#B)cv@Hw9!ioNYjgre+J_Va`-i%$hyC*-I?E9 zJUj88Qrh^5y%N~lWzCbCxr_X6$ExgRSE^?yN}H!rUn7RRfcTJTrX@#%eoFFpgzZSo z{EUEfmq^t4d%cf93TVBsQxZ}^|0FJ%^9-LmchP+KOC9S6AlT%fuI`TYSHCqBzfg+q zwQqK;YY6g;ET5P-Ra?-GqJXS0quguNv#l9h zf}>Hu4Bd9p9>BkW)R7_Y5|EHIPBfEn7~)Fd@FN2hx>d&?9zxVC><%}OSBhR-(pfUP zoW^{5=-~zW1WhSv9#(L}nU~ELT}+xKUlYTo%#O|MIx`_&^OizH%xjD6j!RkVsbZR+ zhdj5tp&&QdeW29=r~88iqeneI!vp^9`* zs)c7d0gQ%W%v9q9ycxp6U~@I>*^y^t;_%f!1OJIe*1Y*pJ|i}UPjGj;_j4i|a`p?n z@$oUJaR^LI`-Ru@fu^zbFvU;u=nNLmf^-WXzEKSX(zFWvZR~gsGE0!5!WY7OWOp-& zs_cxfz}|<{w7>l0;2(DmN3Z_y=&#_Vx(NRxpI`qKL`pq3;XgWR!0O=n}#l2yl6hU{uUCB7r9hK!X5(Zlgs^?)m#r(A`=&iHZ_=v4`N5p7auC zgbtLknwIAvj-dOJuv*oj7WM{+Vo8Z~Bv2M$+KMcH=W57{ZcQPNZ-X^O$7U)lzq z4zYNVubghlW$czu)sQO5;v{f9?OR_KXylvc7%DlRSQP*{a^uo5z4ZS1V>uHsib)r;VAaLLU@$2xZv$=s{b zQ?1o6Px|BD(FlZqX>B>1b_f2Q!%kxiudmUAds6x&ZPnP@{Ce~GewyZVQEb8@)vA?T zR#l-dtQ3gt)4$9?^D%DR`$B52=;TGSBBjbV%-AQ&lTFnCQgjvHdpkgK?#UTI>WYr} z>B}dej@grLio2ZOzT$OO^CW94NZ2wTSO1^4-oWhK5pPmaY31tb+Ho-=Az0Zz>A+#neZ;BP=?!=aY8*>!^KB0v9|CI~kO5wJWKcjMn(N%m%01lZPRIZ0+`SSNL!f4m&VL4v@d|xruA3-XkI~` zD*oj>Tzmj6=`?*eZvIRpgAsiw$TN=ZEioWql80tiaGrL z%N*PpGL_ge-#@@bGi?M;!|m~TXvuv9RLP6><>zOLbT1)(r%+kb!O3hq`j*@thA9549y>y+l4w6<1CMi4 z-`o348JhZ*v)37VQsP z{Sm|Gg54n3fwD0S5+nACDYm?YHdI?}u7yMz=}ca>tO`O|lg)mYXj2F%l<#*mSYM%A zh>nvcySUfJooJCR8w1*?PU`}b8N(;H9A*&VPG4NY=FvI)78;aXFUebzlkzP#w-29# zr`+7uFMoG>(`~{0HtLiSoP^B8L$EMB#S+qELB4~T5pqw-i?7j(@&R+RmmQ;oAkp2V zK#*)|P9!p9XWd?^C$Yv0lDt6Yfbed((VhSY;Xy7^9bLGKgk~S6QZ=Qvtur@@@>_Z~ z!_feeOtvcyf;xGZAWz3ITvLL;`eA&&7}50;eHu7uhGpq3KtRHtK=|&FpEzm3^9~%l zc?GHu_2&}hVUx{Mp$L2z!rULbu}Rm*q6?JXGs$zMDso$6abo@;7ZRKqu+{P~SYL4^ zXBx0B40chZb?-ss41<*%W#a(QSvdYEYIg-7xmN~i%RXCu4s#FU(-iS&B+1e~jFUC= z$;Z4Fimo55+ZqQ;#5eSsz7f7GuzaX-%bn%kp^+?H+|;3ni`m@_I0`vL^&%&(J0u8R zV3qRa9{##$nFXyBKqRUJrjI4lR0>C+M%}0$2sAZP<4zw*TU^}Q>OIT=p$8z=c^4U~ zUJQGWX>!CoQA8MCU|{?8%7(XqxD+ezuY^iqsh9&=P@HX^ux6C`++6+EkBMn6vjp3v zhRCKTd*+%wuV#nR@;BJG9n%ZSjwNzHG5{pj+x7i5)DM({%DW|GfpGmk^cCGVs9Q%` z65xEJMbK4Gw~FUSF=Fz-Bx4_TWI9=7h8`eP3+Mn<0xnH0%lz~B2m<^O-cX`zl-;K* zD`)}a;}sjQucHYH1^2iv(w$Eb%R~z-hdB^5MV&Af_4)bQ4Jc(N{OSJw4r%hw*Efdn z4!@EOEycswbIqItgo@M311&9b>hyY7!|v@yrJm`HNtiErUL@~XRz;Q^NeI1~!ycMh`}~AV z$Bm$iicNeu^k&R^BVG&1G7{JZN(vD!7l6i(OhVNE) zpKm_@I`7Eu3XeuiFBOuO=mcwUW`oi@E)H=xj7@=y#p=!`G#_*Lmq8L1sD+?HQCQx$ z^Zno$UK$TSy}G}gd7$56b!2;{wYAeTs`WajV{G;kmF!0w+gluauhY?@4Y;77i`1|m zx^&sTEWi0;`M)^*rtA$bc=BpIL4XPL`qm?jn3_w2Y83 zxT&KyGQ5e=-_k|9+6G;2>F> zcWCiSA#c-cTAFLCCClm>p=NlpJ@dTq#Agi487FY!;R{EvWVY~5Q%M*!Cq?O!jp@XP=~RwHC$1pqA8}2j-G68Z5$n;ocNvEAJyia zoWU?5#5T~Nexr%(Tpd${%uJ(nwE_NSp1RQ}CWoe@0III3Q{cPoMxaA->%Jkm3&KY! z`%4KsEKGxP z;Ns9H+>ag=8hS=dvz2s**&(SB9TCZ*s8VaLE+cEIMVrK2-KL;VeLl!yO|DKUG&-om zc`V*%p=VxcqwzIMiPJN1bS4D^l7@I9-h+50C(}Aaey{p{Q8eqU>i%-c zOQVZ=Fnj}t@SnK=k0e?oIW+!;7s`bF#0>zSV}MC901T64JACN6E?($<-cbVx!KPQ@ z3=&2n?7{lEmrQ0J=EKG)}0jRQrHT+ zK!zYQlFr%&I-ubp20O0Vl}A8o=TI282~F#wwzs#nyuT%kC5;8(;FoIq1ik1p5L*pf zC8;@R16koc{2`q0sP^Wn*M}c8R?PmMwffD?eiIi>^I5lz;y1O{cnrhND2+8FYF01* z6G;z5M~WkZ9}4=YRaF4hKcpjFGlNlov6}=BI4^Db*bOKfox%qN@+Ic={D#Zc#BQWReqV`1~?-b9el7NaKk(-yr|9u(8#CEImdYJ?c7t(n7UH9@Bjy=owmhLXyMMqOG z6dbYXNl4Nndh(K)ifXs)``-lhb?N^aoWBuDPsQkRY5Z&1+H%ci6zWXy77w^pC_!l| z1-Au`Hm?o^oAR?MwT@e(?g_P&h5<^EO?|3jN`hESMNcZ{NSx?xA;TKaz?bO@U^_K; zz+e|vtM1OIwTf7aeiwW!VcAH6e+!hx^cdB|}Ll`_1}J zLpE`4r;m`{#P?Oy+{Zo4h16IXo9r!*L>HL^c@5AwmlfLcjBDWp$aS69(Gsi#L|Ex0vgy2^wngHspS5^=&;*T2@vlm{Y(}ThV^Hb1dlr+zv!{c6I zsaUu2N*o+4rK$#SS!MTa;*u)iv5x#*@3ElRFYk?;tq0#uS&jilcA*4DNWlS(o$9`E zQ}j-oSK@#MJBhX_DW(`DqX1iP&P_0odq4^iL)my0VKZ8E6tLGb8b zf-Gf8C1fTX_7iK#5fFiw%*~M853DqfMM-S5?8MYml$ME^(YkoDy15n3HIJ8`HMid3n0=m-r?dkzY&|UZ6)5y` zF96pefw@luL~{QG_^=;YUjb?Ldc_eyxA1i#x|($i*a7yOe_=(z3W-{RqchMKv6xvL z*?+s?i;%Ui)wae6T-fE7U{^yvj?o#Dk>I!ZFj~_fg#c@m?qWbi07BP|ftiQkFzBuh zZ)C$s&Jhj=V5f23(x*1UlK%`2%;EtRFd73TpJDdizP>4$X(6R(*=p6>38}&dlD7Yj zqy%CENhwQX{f~&^sWm-)~nr950Uv+6OlsWrSpzJQ^si;A(e zaTkcZL`h8!kMD-_EcJw+H3YW{Ox{TBOSoyIhoZ812S)eYx3t60^eq@8==gJA+e4ba zrj1R#Y3^%zG|RVLSkaJTRLoJuRb8;KIj)vm@AA=6cv&0=`$HGHMqTO|96{_oI;BA> z*H8X!p`+rAaCNU;K`pq2Tz-TZCyr+)c&3W~;Snq)a?Qe@beM1AxNW}WuUu%B=fAxpO#?l`iEuxV$`E_COw+re) zZAAQ*q_GGFaZDshV^MLpht{~=n@t636Oqv;NRoxRDLKG|7a8Dic}1=v+cSgjQG#LE zdyA~+Ojcg#JM%K_-#CguO@5Z}g(ax$DNCXzn%HHFlht-d)-J_EVWK$e;_1Xz<}K0uc9plg*nM>TQMWP`)KoF=Y`qi zZs&@5Nvj$%DEKv=og|HXNZJ55RJfuZBLTcfn=_%BCDw=AER^*Zv!I>KrLc~xKAyKb z^@;@`(h~kXN7gD>?L143p>(_aSgf|yB z#?4LQL2di=6bqqaPl3T!HZFU%Mpw}Db^BFowAV}O&j$B3^9xu)=<2vbN5rsh5CXg$ zh)39mSTpy{hbOz;*wE+(7-WGA2Ox4tCx6^+^1qLw z8qK+9ZjkVx>5l*tcuCM%f{I!x_g20Ko2NBzAkw?lD^Z55lGy;KA>5$NGoGE_MDYR7 zD*zMhs4z>W{7*C@#s@{|%6Kp5|8v}zKNkMW{;1@@?0w|HT$G5Q7WNB|69MFRKeoBy zZ+zc~d=r;cC)V&;7G>xrstm8WEmNs7!bPdhH?9kYKTU6daF-IklWHGF5R}?c|H&#I z)X_pFmvlDsCF(*;T-ZEm^;_sTjo;N8P#7pD=h&g(`ZWE6dSg?oG@^mbqc$r@(Qe?U zu{LL?h2&_>u@ne!WfKKg6*+MS1O#ZqXa>%s-x)9C5B~nB+kWu*=YRh5pW~4+<~$=i z&9Htsf9XdC0r8H`fPgK9Hq;n;7QHTnBZO7`DWb~C`J&mOQdmy*zMikVr@@@U4Oebz zy`YVS^DFwxH&%(I5i6 zJ%CdiX@&=s!vFrwJ!O;h*L&qqPl!Z%i#W^W^T2Ivnz@F&w zMAHa#$e+0FBzRjHS*E9Tv>>~S zyVtssvYeh13hL8n`78UV^Yfb0R>YML*%EFqd%Jh#qQ);@nh&*l9^})VlUtjE3`%1&7N+;#tiR0p6*hO6u*j+HYdTZho9a~h>jVX!v#r^Rf z!F$n`w~1uW-IFe&+l+l2)qZR?9Tq>9g9_~N6^5rr-y}G_z&*XXvvx&FcG7|k7kB(s zzrBn*tSP_w?8-;6a%$7Z(VGE_blj6F%EK#)wuGwZCQCDksnY`Zpx`Y71`q;q<~Vrf zuO#tT$pP~y$Pb=x2Six1-GqcW;9N9xHm0?y&Y_bz*RWvxUWs(nmt8!E5!{EM+F}ic z(nNjWtQhvyWAb-eR&bj@v_XpK=y@YbglaM*WRe>i2jv7 zUs>7S*+=%;Rn*TDRhKs7wGS^N=%tpi&?t6?ia4xT%pzr=cPLdfzvjp2iPNtTZ;b5~`;Cx`AHy z?20nbs6MB>gD66O=X-wvb6=lcy$G#XM4Yz^t?ecElx($7{FH_{$ij}Q9|F3gGddHt zG<*h#xn0(R2=-P(moGY~U@nbD7GrY+_O{_ZUo^J!hA>~^`2`pTg!U{#D*X)v#lbCC zX1JN}*MZR9@HHZ%M|R75E9uGB1RUdF=hTgcmp3C8AV&EbbQAwL4?$70>y1fb`r8(ipTil?Y+(YAC8`H?>>WL z5nyUM2)XiO2NOR1+l$SOeMK4fmQN?|zw4b{GJo2r<=#bxPMLOuAIJ|~cL?{}+_P)O zH6La&(tfnt*XKQ3g4#Xtaugar?E?k=q-@wZ|l&fRfw701O-H4Z%v3vA%``HHTBu%}f9i&o*kZWu5 z6c_JY#z~`62lJ(tU#fPe=5Nj!?o{ouOl+n|PddWuQs6F0;mqaCF8mtYSRnLaXA}?) zs5xr;^T3kSj?~^{0EKM144HN(V;K=QHs~me71Y+YHfvW1EP+l5QojPeca3`p@2)M> zdJdDsC#ZrTyOZgDSy`xM5=w3p+Tk;}Dy;55g&G7QIISV3koksg3nwdw0Kjl`cprS@ z@ZsU=<>fj8@D6teEyT2)DF=x+v}=8b`Zz$BIU?sWqrjH+qh%)cQ^24VSfZb6nxU8} zf}yFGiCE5Jj6%%~0>?X}*WxOV|()luNbS$0enmsm=|fQ}dKW>9=|zPCst^Ay z4M_9v;T{q{QMe5;)#5LQqtAZhePS}z5lXWvkLU#B!3d6__J6UCD)^{=GlM3ur*=<7Ws(-XfiI~D6>O#|1K~T&JgaCd3L(GfE5E1&Jfg62Slrdr66l{q zR03M8dk;c*ue;y)X`x3w`0v@@feXqdqL0e8!}#kAjaP@m)^+@1mR>VliG%l0)ACz* zPp%t%m(2h@jd>6bLrpM#o8I6PRq@^6gB+-;nXTze%~M z#8rfVY-K)j)*Xx0cE%*(cxV|0X$S4TR9(?woko7#6J0sfFLt-Y0RWS}grb+6kX&Cn z9HOaj!tK})%p5KI=Q}q^3y|j3SX=o}Fv!%{G&>${&yq39n6rJGW?;_5oq#*U zUL`M&Ze!Ul&Ls=fU38AYcM}y1;I#>q$j%R0(5&ShjRnWozDMrN@9`B9vse;1CeH2(-qvV`8!xERlTfU1ygsL^oU- zA&U>kS3@Q@VVkI6?&4!b zb_<4s!ij{pjdNqkYi>WJ;vAR~S8s`C6`5HyVWH5jE=Il!8-;+?7=G&fi+LtKwAqqd zu|;!#5Qa_98?jnub7x!d46D-1?%iOh`cj$3&d&h&MdZuz(y=%M78MWOB zna_Rp{5tG6+o6L`G+5;b5>i9+W+4gmJ_P=kt6Q7vEM*T`CWvd$wKHt}$oK*dN(X_r zOzdePC3{_U*qgm!K;0S*8Gv?(}OuAEt?VsCcaC<=P zZ2INGU^MZg!UsZpv}&dfDJWH3f{&)DQZegvR5gQ=?pjZnMMXbykNV1seN^F;5k2V3 zNAU1ux;*2Upt=AIDB_=(f=5W+9liI-+%cz!=5?`Mmxz7Er6MvFu2CWrx)xOfwqS>Dt(;xMTxpButkKp)RUGKcmP)lPsUv$aJ|MA1IvS|`&~q=S!h zd{*7qDOF|L_Y_rU79$h!R@o<=^UdW>=of3nuZ(OT+Rv)ay_p)ONkg7M<;lH~f5blp zQWp!oO;XV((yZ{XRw_=*3M?Rdpem=>tMejK5M74>Wxb)lBPmjpOA}#N%ezualB|Zo zARPS$Dc=?}TeAqc&g7aRztV3m5ogJCQZ1ec-x)2xcL~j~_)X%7;O3g8=ftak0~p&? z+BrOH;J`9tzv}H$;~I1Q&Ivb=5x~bN13jtVVBUvA0h%TqBBq?~1m-ko5RT|c<`jf`$ z^U(Ytf}z#z0|Ww@)4^c*7^O)h@hgRAd+;DUn71e5=6`p7(jw?d`LPy4p#bd6#-(7K6gs=_}fnckK*o{o~>t%RqUw3*(od)s8O(s33` zWFUjuf1+v%YXSno!UtX)Dj`!1UP;2fZ}zsIE3XC9cDX+;byq6ikU+ba4!A$DGOC$k z5^bI{!(x#>&&^f&`X(b~Y)*336z-Mx&@GiJj*v4dM3=jXsctt!JxkW>fe_b?a7uzr z*I-71BT2(ybFpJvcZ3sUcfi}16e7lM319-_|m;+zHE7joS9wTjO6a_#PkZVOR6DX{5O(ju3$|f+Ef7vTY z)yhQ>*=Zbxh!Hh$+cKR7pcIY+CM*iB@Q7!VVv}mr@qOUQGqy4)knm|%*(N89gQohp zIL&kjBx>cFBP$t45nCMV+nXU0BSEm!>EIXkf{{h);>`1qj$57wHcZqcWzD1o!4trk zJO|I0gK_dwMh_^&?6Z=?LN+l!oA6HbG%D=r4)5w$;%S%cW%9v_unmW!aa$Z}b)!_O zQ{SFA_2esTU06|gjCE2(O!_kQOT~QU{N&+hz3DqbZuaF^e={t>S`q{E%LoG(mO`v& zB)=`wg|^8Bu|f)CehGC55(Ka+hmDF&)`MZEr#lkyq{Zr_C?O|DB;*WKb%Jh<7+jg) z2KR*cF%e1Odx#2ytC)r3uX@J#{TuD~@S}lc0&J)l`1D4Nv+?#5wk_aO2_h`` z)Y84xRbkx9RVfv8{Zol3C>I$}_BQq)Ksugrs4TyBNpylKNGTgG&&-!viGB}P7W@j( zQ==Z*xz9s-HSpVycxCDJh(YwYik{X~u<0GKh(EFB?0xqX_ zpw4_wc_B%U3qDblhwvb;Lz${Mb3M{ug+f@+q+&4HPvD{vZ&?9=K`?-72wTvJaA0L7 zJi`ZeAIhKk>)7voG$TbRC5v;Qiy+d%+yFsfmy=%~!zuc8qyAl|hb2h%8y-cS3DC%x zkPwtY`QrHO!7=i5{^j)SfiPZ95V3R&A&a9IjaPI{X|uF|hkoL}C$3{>SEz<0)PU~Q zS7+y1G>>XvIqnW#RfXuJmI!MfaN*=F&6a`a%`efRS}u@J7DOXZiqyjGDsi$#(PVxY?Wg|GSBv8Rb9i{~@bHV~;o;|phhG9# z(qqyygl8{H=&usC3RK|0$`vos=MVg+&}m;&@x9P#(a5t6Fe2o_^BUDha0f-Q%Y*t0 z{PKMD*+#z8k_Oeif}k9k&~wBY+TCmuEWR!83HKL}##bdjdEcb#2zyZiu@JZ=m%VJ3 zBus>#)<3uzcbk7cT57)fY&rd7K|Jp`mcTUe>^GyPdw@g_|2YXSF)fd(nqa487cg=H zt3y!?5N{avc*jW~?%U2#vPQr}t5u0Tibnc?G(!t1EWmA~UqP-nJU~N~F;dI5HIfzK zOsv$>Rb-ZgkP*F<0eL-wziP?kB7+1{fd_ z7>->MXdxMTEy=N>Fm{t0L2aON8P+5k)i{t)tDA3__Qg|h`yG=${;jsm`9jvImEa15 zDKWfRHS%dhdI0qy%M?$J^BQn@6cN~*4n>=p1L>54v- zpwSY4qUe#nt(biBJ=c)@iN)sdNA(ZoOETSroZ&p>BtcpdD8W$A6vC)L2FYEhro3at z@}ASbFp3u;uYWqK-T!p-kn@Y*Xn&bU-i;qV8Ywwj@{riM&~f=`TopBU_SESoDPW-_ zCjUD^@`1@mkb`4(+5^uCp`v&tr?K9zOE|4ynmrkacX0H(@aCw6Kzt;oScjSAaH!@( zxc-hhx(H{u#HTJxVoGOO5MsS-ZWy;crm zHrndmKQ?BfrJe_c5F}Wt&o9-U!C7DOClyuKbGOl5>ow=dr2}D|0bBAgsKA@J`9Pig zH-bb|cb1bKQG{klXlNT~r(d)C?B!N=qbyP22t^cH;NyxF@)geof69VP30uCVprK7~ zhV+ZDvMtm5XRzH`mdw!JR7}#|a-R09Otn;mpn3;)67!3zY@k6Q&>lhY37No@K662& z$4U#@2Bdwg=%LmyEgOY=` z07EJaZw5zvkgoFfpe%Oup`a;R&}k5rMu2K=8UQQ=bonPAkx_Do+Fb^ZC0^jEP_z7x z#-kl&)(@Jsr8|e0zij=6|2*e^p7nn_tpC6N_y05BSbX&vY9p|z+^7$iK6|wIaABFL zbL^tTFEO#aunDU5>W*GgSlA$Ugjyzyp^euv?;yL@T@xZK5j7!_(at|G#tU+~-sqB* zGus&y2F5ep?7~slFpnMtqNQ56V9W}q$_-jZ=jz`K&il2!3ugMe{}X=yYezF2GY;;d z2~3F-NDyp5V%kQ)VF%(`=rQVEfeI&xx1y|}W^L4I4NuOkrEGMGMlgP0+xkhW2qI+D zzk&Z%iXnmgkw(EMPH06RA=o=*ieh~YR-Q@zU82JQo@SRSny0E|mD8xrKEjJdZGVcG zDWgz#YPq=PgoGIp3T8nX`d9>!6Na!^FR3uKzD48CEkP^XH3O8M4m}N2*j&0K* zJDt0YouQwrzYr6E zrpVVzN|;$#5^KR-<2_nqU&!bf)-Q9;A*x?dz`C%4CtRm5Bdo%PMx_nHlFH`-;zA*g zq#t%8!cfm6WgC=gqjOVOFfj70CyDhwrZkzWlJo~Pn$J& zG>~J*%}be@U;c48`a|Sv4)>P9zCh8A5NSTO1wTC8`)qOX5t^#{HY_!6#WP9y0S_lr zeVEe6=3s9VLZ>vB`sWN#O|JpA9b5+e+hX`qNgu_E${m{W!&*Lk#Mg&cCs2G4@qns2t^TP1QK@9TSf!xhabrW;fKjvNpB@g zxk?MGqJ;(8MY+p5&@03MT`j2bu$4xIYX7^b_%2;$ShkVGqxn>^u{tU`O_?QeDAOIE z%)W}203R0^Q`J$Zhge|yvcOCSbdoI|ayGjh&nnBF71m&xzmA>^#;J=oQ|N(-Ro;U0 z@}6_AWK?(^(d8e|BF~nO1Q50jdi%$a3UN@htwy9aXjIj^_vC`6V}8G*(PJ^-4eEd3 zB2rV-nskM=_d3jA7{kwW4Dmi(KzGRAfLshbMZ)aJlkRBm>V&arDDG`0(%gd5Esyu& z5pv4VirF~$#{=Al4^Ss=aY2j2Nxy)n%2OgMUFg+^sqSuk4Kwck(f028#%=}=tc}Pg z^1Ez)ZZZcZ!{2))8!zn+KG&-5lQyK{UAi{cfS4kLKT6Kz1aBhYewcy~y`t7Ke+x1n zqakCF@|*{fO-^Wuq7OWzaPbj4oW23n%a1-CHI@%ikMEK27_u4vyn;h6tSB;^dT_AZ z49_EnOue1%)DVHWakTk05`1kOJ>U55XlwKN#$IL<^jn@n{>B~1u|pt|GBqq#S|lgI z<0qqt9#>QX$#ObTMSz67`GIW_H0SA z$-Eqpl})12=jTb-r`AN) z7Hn4RmBsh)1qY%Cyft?}t-ajCY(taoXwB>0I=4;MF5)m42;HK<^k6R8Q5%ta-q z5T)oL^_Xzw!cp2}4MV*kNehL0ugqSX5JbU|79*e~%#jJd7%wHsRW)uGPlSdXT74A# zAr0UeL*cj>0K5iqchKu-0xkV-up->x8zr7M_`v=BrhPIgjTT6eM;;5L3?dcDv`W}D zum})1Dl=(W3d+(5V%r?cn5A*4S<7s!!eQI8ME_smV5!alr;D7u2;>CP;?D1l5Q>b~ zutQMw;nZ2YaBmWJtQv-}vH*o(zy$h0c@$K}m_HIkKHVYELNN#r%^%FR5U7 z2OpgDuG&0wFW2_)m&)BxpO$CEvS}e%E|ZZf9hECV5yzupXPp%LsUe)SFN%Ixew}7% z5fJh%N$-t%dwAW1oH)bKif_8DNRPpK1~?&7pG@4RNl-01kW>`x5327e}b(4Eb|q-U7UhLris?GGZkO*=r_x0Ch}yk4|;H)Ar*=m_*;rZGb{f!5=- ziW%L!21^EODrQx_dW@w(rA_Mt($5UwSl@uKy@o_P>npVvpl9etgT56)V6ZBOgQiK! z9>K4BIs$6dq4-G>u2j02Nl}JbJLf4^M509qz+*yNjqe|s%S;>RDoY83dQ>hE6z&XE zWhOulM3X!ahZZS8$qGgcu$LYxhZtR*oq;o&0fdP6;#pzJCC5-Cppmb6qRL0GBZKbi zZ+?Bl|MQR}M>$6*_Ru?QNH7QkWMha(!~-WxqynI$@J2v#TbSw)(*$^GHMvVzfOv9^ z%2iBf0N-C0e@e@xU~02fj0&Ysz|^;|=sS^pa&cLTN^+wiy?(AL42Jz(sz4=6olp=e zX*B~X@T9&+Z79_&;h?w1yk(l8eY2prhQ@_*qfRnaC)4uK97ce0zy!z_oPTz8FZ^k`M5r3eoC+Zij{h@#vc&C$~xMbW`f8GR6@#W z*!qy5RiZR%^JN{wCz=$9?~GXp>s!huM)aLX!#=`C0H^3&L=hzWS6mmFx(~4o^n+Qi zr_7y{5I2cX%ETIn2fFe$w+yH^q5TZ{q=0cH-eq(cFvPr zX@r#U5Ie>>RF%(i&O*ACeuMC8x*JR`JgTg8XQ!5^EzrkLRZC=FMI?o&fm71YvPMM|FP4Co+cTh?_N@1Z0R zL-3xYs-*f${={tI|B(`l9k&gd!;&7$SfCp-) zwUU(OBUCUhC4*G8zS!FxlKg)PMyxt9|+cR2Umk>SIGSoQ~-b}rnY3fX> zH`Ptjz0B^crNq_-++BYyJMBrhXDB;oO;O)%HHa})CoM`XIBWVL*1V?`SF1iU!6SS@ z0ccOT^)e7p-Z4qQ#~^AF{gMevehEIaX{^msD^F+40(ogqQYuehDPI?zZ-ZeS$D)Y$ zrOYGd>;~iF7x9_^G-!V^3Lx}@`g>KYNt$R-T2~W>R%VZ;jq2v5^FRbug~z-eRT&v| z0=H5ChW>Yyp**yoIh|i<7Hv35n#Ww@YNeuPLpa{~m70{X2cu216#L=drIITS4*8$y3J_2~V#El} zb9Cd!%AFz2=(9vc;N&H(=ev*D8EnYZ%RaSn)Aw4}RGm-{AR>jXo$3{ZCs&ogR0JG5 zwrHP$@JEyfFk`p$m2hUpL`3!kmn{_;hL6J4RB}hYIP8Iv!jI1$4F?cw^W2UR74ZpT zEJL9|BrDG*=iQx1rX#l<^?aVd6aR2KA-H88HZCj9Njx$Aclf-%#`jiJ&GlUxO)fckmYcJ{?()@|_rEMP=* z4V70V+k7V%E$nzL?aOvf6Vm(^W-_FCR4Of7d8;`^W4TCx#wo$Yh6NwY8P=M9ickoJ zLSc<`r_)~N;JYSRF4=-IAC#`V-cvj-HV+H8Xu9+$k!O2?y9dsSnqbWbr|DU{Dm|SD zXUI%1G@4;g07N2`m$>oEgwU)DF{M;(AC?$u1Ajc|a?iBvvXVQO$p;qwtJdAF+YnMGZO^ zUxIXlz}R$og{0YC>`RY8@i}4*_9hHY!NeqwZvV)9h^&!}pmd)%ow`CJ(Ip&CKAF*`io3`_MK=o!c;F2-?miS9%2E! zkT83xKZVI?&_0Wn4J353LlbQdfj&VF{wI09OCR zL_G*n9u9uK79{?W>k}tnVld)henc5Q?DGj6SBTmiD4_8+gCq^C^c4^26xr$ifVjDJXC$vRl{! zu7-s|ZUZJrc7N0}>a<>?o0Ag=X#^5xGU{BQ!#lMR!U8YPh7h(Cli}qmwWdi%x?^;8 z(Zb^TZ&(|r_I5CQEz>{mo}Ksb?-<2lkyAMS3Af&9^A|#f{nZ(yNK5lL$H(^4Bf#g? z9m7tYDTG|Cbn+esXc&hBFN!2{#4Y&4)|*Y;ND&akAV^M+hD~H}4i;%8SB*L;r>eu* z-2uLKl$KoJ{GJ?6m0eWnV73FLYm+|rOD*1ZX%9w#G@*Q)amhy$W@aQZ?H2_om2Q%K zo3gNk&SkquUZmYF{ZZB0orLwriZ;}T$}TtGSK*DHA) zcmR!s^k$ub#KZWwLxLArmB2)%3Uesjrz*!WF_m3@`eS&hSIAa z-OSn40m-ZQ83_*l#+TER-+rYXE1bl2=D#bq(m6oF1xLq50hqs;L?R}n||D^EzAIJAx z5K-Q}Z2ETG%<;WScc323O37V1KQT3eJ}teK%yk>?*HAN*Dl4lQy3Al7Q8W!qZpLTP z@4n<#grMU#kRZz*Axffwuwbw003saPh~xw%=Cok#IjM~FL>rRIxed>Pv@t9|9sD(D zx@2QWRWRR*e}lh%j2jp_5KU2$3)zmJiR}lKg4CqM=O)>4+?0`Qgo(*gm{S-nO9XRq zf?)Odg!PbW@%DEeOKA1R``SU7Xg^C2rB3Ws(tagvRS`M3FKD@v`u!Vucvp1}6Xd?B zi%Twot07-^pdg$;``wEi!jD`+BV10nkMuAkj2~}3Xg{DnqPtPN{EBYab5y{o;a+WY zzDvMW|5a}8MM)8ZScuo$D39v5Y-)~T2TV^Q4Ky=c=sVzO(n2~EJwH;#dZ4-ZGnRCB z5KoaMRTaeqKd0_Q^KJM_!Xc9(>S6Nis_+q}%~OpfOlq^nIM`k_7BX9BZphoem+SuvF{#VBw4wFMcV1jKIPf6D zpSbyjHxL$sOH`?WBT$fLQK)s!T*a&R4Ly*FK@gukB!i0P&d(qm$dcJly@4{OV~g>E$Ygy(M1zpXNr z;m3Z}9cC29;c1onn!-v|a7uUw&+ab5?wM&hul-hK2nDb0s%IU#6pOw`iflsHYtL@wf#h;qi1A1q zkXG9WvbF{xrxW~RtW@iXaW0XoGLj%JG#4LVBb`LO8+5`f)#r%!?Epjo-Ylv65r~GQ z;0I}dB+}|KFHUFaYzgAiVEMQ;lq5O&>&m^QyGwVI2M@x7d2cOG=1Uc86n3X8ZUAQo zGrbs;_m)62N^9jRQHTMklklI+J8HjB+SGtg|L>vs_h7{7JA13NB!M@W0d@XkTL>zV z|FyP-Qp)i!R>?bgkuZ-L@Nh@SkyMF;7Ssr=_;QB*sWfcaSg>x)mg~pH()KqTi!oD8 z*8|V6u06De&c*GWlFkP)FJL*BMK&Stss3bPo!6!N$*br;HjaN28;9{U^dAvs3Oo8f zjLzKLrn*M9EejQVpS!1Ya4F+7oNb~PG{lqh0X$gHbaVyzpT4FDOM~+vwZWF1FSv9& z9(uPUIPzsjcGaCRVq$!yogpA6^Wt>jE7|VCkO{WP{Sy`lL&X-?GEAJG~^Aq;Zl zq~x|00gVe-Qtx^Vs%{!|q0kX|PBQi&sD^()QloTP@`Lc1F;2cex<%qDE9cGjEyH>jG~SYlCW2v-(aSxw3asM>|~k^*_{g&t9q+XmR5)u}AEry!c&C&Fy+` z!GkhmG8)7g@()k5L$HZUxgd;PO5b*_IRR0nOP4O&P?kjGa@LYpLm;z#NYksT;%Dw6 zwcLtfP`W6Cgt`culr2f^3Z(#usdnF~ZpBWvfmxps*wK(^GuI?}j4+N8xzMXjcV$(Z zlJ-hb$luw1o01Y_J_E)q2k7~NsukP0_BfDovlyDGg8B&v;Toy~Apk`!>Dc9TZShZR zjr>7Vr2d}HSPzgNx9G`7gm6=uIs#09;iVo>DamO76kry+J@83lCW$$cquGbhR`sQ> zdilq;isV~4kv3P}BDfohpW3m_X?pAGc@nQY+USlEBLd|v%*5%Hk3(R$tzfWjLR*9R znb_nZmAp#3isvYb*8=X;c$c*|01p2#6}>@@1X`$WoGGjNM3tF}l3VWo7^|t!SzJiD z`-I^3Gb5DiUt~5l8REvqJ7WnX*<4wUEWQ3*bLqw|{R-+Vq%S^K7q*GVjcrZUSO zTGM0S$l#?ZI{tlgkg3^mk|9#KMN2A>UOW!na>HZ^pp}&VK1NAH6q8aaHysutiFe7wOAUak}s2!fqlRDx38EEtZ-e4xr&Fh~;VOvHuM=w`}Te~RrRL&v7%g;PE*;d5bdRt7W~ zKnftKXPcdV&ie$LcNQP4o&HcHA(g`VILRiX^Y z!3%w476U-8c>yAEUA6gnbYxkg1sL&AKH+41i=4^Y`)y71Yi0#KR0oSgxvjZ@`nUX< zGx}P~q=6Mo34Og7cYB9BTC`9@%iOH}(L4fF%+#6AG>D73(>|FLUS+0j_fEn3fe7u2 z04tcnV~o|pNMd0Gyp;4*Uow}%bY%tQ8;&-g@9nQ{Z5^#WTfdWky}h?T@jX6&v9pc> z5S3p~c#V%S^8Smx%8#eK-re5bKiXMc`*!u~u#D0rtnF@ajyt>C-)yYymkx)?tnRKo z-F#WSlu7UT`HTJSt?kwIqxBchc1ov_YeA|O_bX&u0x6qR1qboN7UonQ7iSSgy8G3o zD{kg|puK?=-(-lQBX%aai6WM|70@^KDpz{OKSJy}BG!vtZSG+Mc2|F>{W@WgQp*uj zW5J;kT~-E~C82qH9oVaMJvIB1p2Uck8}!>qIfSUeyOO7&-+_!@?prXM*Mf}12s1QC zQH(a6uPVaUX6r9w%AVXOgG>OAI@7DjmMQV_B%_w*gz2li#)-_$x#2WenQr3wb5EIwC67bSLdflu`y5!3D1NJmM1HppJYyx;}cs6T@XJuRiioU}o$&rqWt zt}gT12fSxYel=VyFq40IM~KDP^(UL6MkdwB{#{+ zN_`1FL9o$49e;w9jZNa&5&k1F3s=a}iNc}UKl0js??4a@ zKIHV>Dv2?}K+2p%y#N7J^dJWek>=3doD&FURmi{oTB_HAUW^Booq#l8Z9dYHFS^)Z?fPw^gV>?0p`= zibreTMuA)GRi}2rBtM8~?c;PI8Ixqo5f4O^bzvDeTW@j=Q0~A*VCSz`2pifs8=}zw zkG%-I(-E;JpjO72Pu*E)ZjeFeBwrF6J&z%whu^KM*?YEjRta*8=qq?+YQ!4X_(g9dRZ}oPH;wv&gV+0F2qRb z!>l@>QAgrdRuE>Z41i@yw@D!}6DuDQSx)2S<+9bZ>Hk7`VNVFQq&fjDpCB=Ks!!Ww zGNf0>i0-ZhZuE#!JV^T$BuA&VL@&iD-6Ni0{f!-1VwUv7O01pC>t+Ti^S{Mu7Y`1Y zMAG@d$=xOfgi0uwQ&+8$6WQm=d=O5Ia0Wr~D!EI~8b{9Z_vQC;G`7H>&&*W+pU3$H zicIV+p^!l33749Z$=ee56j?#oi~J`MPAsn}U+LKBe285M2P#m#l0$YI3J4N`E3h-n zMjd=Ky68Cj!AWVA9wif55&cu%Klq@k<2Xj{-UyJsWx}Bt?64=L8m19<&$k2ZAy|tJ zlxUH2SbzcGnLfq|2;MYFgYn>Xr=R^s;}$dBFsbBo2~18p57)SRCJ8zt+K z-ZDA{DWt)(jonr!4V2pYB8vBsG{xG3b&$g z2>K`7uu_n-b}ggD2$V|k-8vdwUz`q5$s8RDJ#pFnqoq=WP27FLc;tY_G!5*Ogb9_i zs>0<)|BLdAKs5C#g``(T@XGYsW8n%>DbEg+5M>-SxR2TtLnp$5M^f=D<1V-5Um!SY zFuWEQEVDPW#Cce(Qj|;pm3gnlL2*x{V4}eQ<_T<(0j#nQ^Oe)3;Ff}+k!4#sJmpy5 z=ZOmm3lzUU&9i|IiE$#y#yNyXmGx8<&u}{Q#>7vhM_f>5GSV~DWg=0j3!=+cvfW`Q znL4Rgd9gXCZN!Y?B^^l&C%{u(-cHXuOKPZPb#CFb*ODBd3t@mq2wxL96q_9`jg^(X zja4Y6kDjkS+khk6q$W>ZY;Emte7`SyJG$!O%wp0*c1r6#=~1lvA;l%*f}G9-`U5A& znobfwIejcJ5!Vp7F9T3q)8BJanms+J3oU&GfvsUVBDDDdN+JYOUwv1T~gNTds*7Ubz(i@KEed^ky4fLv3@Bd%M@^ovJUwA?%BJn^p4m zrFvH8TWoG0Zf=L|&vowQ9?7VM2?QPD4Y;Y?05dUqzq>F?-l{mWCtcPs!TR|FI4S?A zjVwYJo5cfOVI&iAw)S-U*~SjcMmQvOXy%-HEDeTda1+%w+gt0w_=S_jrp-aGy~KL8 zXml%fr*~?~ZD(b2`AA2=AT`Yge~wJrL&Yvpfyx~#li`(5xf2&30)#yH;U?VDaM0@= z!!45R4GXfDus0^;?ot^h5tzuz18AU{wp|SBkb%rr4$M+(#|gban;h%va+b2u>vZfu z%mmP9)S6{C0$lZWYyo3>U@$vCDS?F{D(!zGB(&%CCe zKSisnKGQ%dQ>@-(3acyTfxF}g}6QxNqWC$HDOgs4zH0AopWyfBK5I=D#s+BL^L z7&K}O=b_7ua6gPldok?smN*}eFISeAsi@|XR#yDB0Sgm?J96zuRUbeNRhsP$Jk|x| z`^X9um%?lyX$yD*YJp^~h%B9Zv5&~s;Cqt0m&Tg-%E1BVN8+c&RBC|=AnCRr*18`E zK#Sa>p*;a6z>Pep9rVybVCopg>4^_2>b;RfU~c-0F@+VKLA{VWX(dLClNJzt9o1jJ zE@$>yUNn&vpSB7V$*7*Z2Me7CxHYhT zPPL)pq|Y3kKSEiH(pO;D7aXX9C?aO3eJ+e;B$;6}4o6H5Ol>qp`yHx@ZBPXjH|#vs z{?gM@eV(&H%43L6ytpJ$VB6}WOJgwWD1`>VgFQ(y$#I=9zjAK?gbJ|w-h(9r;=0mT z1%7|$Wes^;BC6o8%#Q0Y3&yAcOR^XaTDv7YoXGpy%FZKEN*^4A>U6Jwm%8Oiqj;p8 zHEDzo6r(cz8ZzO?o*{uJ@S{=xM`ReagA;p;r6rbtbIDFHSn)f zDfl95)J-0e_}e}a5-hij(>Umy!tp85K4ckb!Av{m^5(H}*lm*paLLu>s^f)LLMk9psKkUyqD^+;^3#HH;Or5-Ih^NEoOAip zQS;N$r{GmHbob62T}+0JxRtvbTN|r;8%OjlDTplbc7Ne)sF8tM;qQu*Z{jeyht z^V?$h{fjRYzv~P4@M-MWa0LD61)dWHwFaLcF1CMvu-`hv5-im@*8;O_BXhl{-+7+jw3IQ{mc$_GF{%)1J-77XVa7ft+SisPv8Sr1)gm z3dBe6NEO{6YW0z@9XBKHM35|G3ah4*bM$~|d?J~%C^#SW2B_22Zqx@z);AhKGGlUp zESHqwh|8z%Nkf@(|B{$ZYOY1I1Sx>!-BYWOLz2&w3sP*%`fk^@G5+4(R_#rU;I?wrC5bWtj#0r&Pb?*w#X!=LjQP4Iz&z%q&D>ld znsfMfQ`2HF!!cijelo!YnVZ7EcS0%(TRNIyE|PKKP7THO05&A%dW&qW-O=kiG~9sw zRZ4>X4pZ6XuKH@y6Gi z&ue(HwYgTqQ+YV|Y;$vM^QYD4kI!EJ^Za%9>p#D}`*`(l8&6hOx7SwB{wHyh7hJ{{hD_rpKG?DW3t&UL@&oIKe$ zd$)D+(`T1|fA+`kzxwBQSD$acd-vs=?|%N`n;!=EmRrkDe`s%CefQ_i7rnQ8e|kCI zySw`Go7K-={OS9p1uBN{CfYz&uizM{gdN=_P>1l z_0!&;{{HzM5|ylfY(xVELynzI*ca$>^)E$KQVWw*AAn z`~C2<>(S-ozdzmn;`-iI`}9wpCr`dUIsW|R7aOgu?cw$yMm z-v063yHV%f-!7M5_n!Xqczt*D)84ypzUpp$_2kReb^r0nR~uLV954Urq@@& zZHImshoUrQ?=||x8C}_FZRB@+}j+S-F{ug)NUcK&|KVQDMym$CwX*~a_N~jc{X0t!&lbOI zf3bJkxwCh%x_@$S^Va1TD-XI$3-^wH8tmPh|MKNEwi_i*Xq?8Kwt;-g#p zD~CVruRMJB<;B~JwO^jyI(RrZSbI5pwZ3?|@#7blPmaErojH88w{-gQtpDJP)1O}4 zefsFjrGv~Fzk9jA zIDff3KmYyLU+Xehez8HgnvlGw1f4P3}-L3gAJHNd95t`@^z1hp}7k)gsdUgE6$?VMO%ujofDcy^EJWJb(25`|}^Z z+k3vW_2caMPxsdz4xZmyfAe(jZ1~;!!qe5xqn{of%nn|jZmmu1{V@FDi`D+yyZ0{N zZ47%42g8}mnIF&Y-P?H9`Ev94mv`On=Kbe`XLsK&yxU)SxBP6dfs15+`n7ZLvi<(Y zJI^Oh_S$#O*N>jQgo}9n*X3uI{U3i>SXdsuzF7bA;c)h1_~!kil?T6E%szT|bmzw} zhPU3HzFoch63I?xyXW8k^k(9;xA^?Y%G&w;i+6+X9z1*h?fdur7k8enojg6ceD(d? zwYA~0 zU)_84;M=$TtNXLxuPrXTxVQi4(dEm#>)*W@uC2bEU0yuvT>kjQ#p&|>hmTgeXWixV zhs*N^-#z;2^6l*E$yR@2DtNsuTNifFP88A z`1IiX$Az~qZk@b+zx3+r(RWW5`fpEvz5DI@;rzwn!R5EJ2R~lk-x%!AbSA(Cp1gkg zZsytZFWUFt+*$c`;q~6P4|>Bp>nq*+?UQ$R9eIDd*ueK5-G|ckO{3yHb!{tA zjQ02Uh+Fq5Npmo-jV%9}l%c6%B1UDn_>x$BJk_SE9#= zu1Ed@U+}PV2zLw8zfsp98cvZaS*!w{cicU|5gZ>ZV&WGTgcvr>>9YSL-LNS38t#dix1CuxN{idCRSs90 zNkDiU*)**7Q-EdODxL!y@Mh_PaaVHyEPA$+_zA@9V7o*2bKGjZiP&{roE%QYdB+@%bh{1!nNQI1!C#`v;gvb7F;{q@x5736=SlFSJ!mZr8E+TnB6oA3n zta3kz`$Imv(>MWhbiOy;?ae698B8AyPmYt>MMS%71A0gR`2qU?Uu++D!4bd?a5d8T zsaz6>#Vw~}$fpE3xg$%gs!CH!e8ae$_rx10%9WSF`S6OzZxYA8odg3KVEWncohFk8 zrYe7mb<5rtKX!j7A74&I9cqpC8cWf%FpyPLNCe1PokS%|63y zU~X(gyi`aWWh7Aa*kbGxCdP+9%{@nAoaJsBemHB%TM>A;)fj&&#tV(&w+XqUn{t@{ z3Af2ja8L|$WROE=yJ9rWnMZ1dBsJ|uzGIEMRw?5}7%w-i4Mi0tFQb3$L!R5Vj zln00!%*`dQTa(te_`fG=#+V>GCu4AVoCVq=Se1F+=I|%=+=9@r?SI5!y z7fg7NGp%OIZzcLccr4^b1xYFgjp6Hoxw%Mb>G^;V{pQi57>XCc_9ve59XlR&^6#%q zR0g$0Hta@jq_{3vXriDJ*7M0AFT4Rud4ph5+~c9)6&iU(p$_RO!kFR}(ug~xUJql# zHmM65#8W*X1EoVgZV-jNobb{|?2!zGjSzQmV|SN41e>7RZb1&PEA_u85}ZpuBy?XjFZ0l?Q%M=9Ect&jPOmjEKXT*?C zKFG0th{l{^B5D;1Q@Wv`3Ye_>7z7s?VRVHHOi z5{$=iC!y`u9b=U^eU328k0#q+d@okw0!%)c8Qo1Rutp6Jvx;m7G7Gyg=Yl6`9PqWU zqYbWrL*r~U)tr2(O5DH(uXRE>XiT13+an5eP=0x1NbW6{^Hm`KdVxiG_t8k#iU`$0 zme~b47N%GyvN2qoQ^Ebvz5ed7w`-?Lv<5~YEY6Oc6Bz@MAx6|8wg8ug=@#Bb3)X^ z5rNeX*UvEdS%(@qXW}Q>6!h|G2)5We?(85K88$1{){d&U;dyg-MXti>uCI;Fi}RtT zEGR%TRvnKr`+#o7J4~Wuu^|;83&b0bMD5J?`)v?997l3wt}x&6!-t*w^`2t=`i6bJ z#wi=ij4`xWxcaEl=Gnpf9saf2-AAMb5Bg?H+y_y3ZG@@+fCI}PJ56)cyF{%K=V%g+ z>M?A7V_cLwFw~BFeSkwvpnCzY2^1`0K}q#ssi{?m9RcuANFfS*@JT3JFR1^(z2Cd6 zIW|KnS$$w;6P2LrAW{QM1Tig*_8xrVeinDEBgIbP;r?@YZe-E;hwSW!F$1;0c=mTV z-B{x}V9EqQ;vS(kuQxf^Z};JP{`*Z9hxnjBiJ4p^rv6M>9_}Ln|27dS7Wm*GxkjCElVuBsGkJ|sU7ODK5f zfy`$;6ka?BqO#?184a6mU*B{nKStYBc-SocmGuw{$#b8l-@^uyQBk@ED&Z!1`f-$N z4$R9;d%LU$aupLQAMg7gJ@M;4`xK##VdR51$a{txE8i&@^c|umjo0V-RxaplU=X z-P2w_Oz6q={p;d!o+F(|6rMEHfRF$Wnc7DfHTWo_)HH5;Fx}wxllXQyf~hHo%0kSl z*Plk~6EjQKLZ;MJF@9|XOFjhTff*DDzjA75cI}UI&TFHC9cl;-+1UBD)9=Z-D!54w zFlHXJLU#-YPi7+kTG_J-A`YA@xndC~X(XPA(DH3H;1!A*jy@nhlYE?e=4^-0?5ggd z7sVz3R#}eNLGUtc4&&w)xg$8`3nm-eJ_2Szn^19-^7VLT5@)&os>F2(2yQ@*98(MHHt)QKT?5T2o6m9aWYF@m!Am0qtB! zd$=}RR7u;;18BFw_q-3(KSTXKUIoDLu6>iZbQ3@B^jC~WNte#1KY@u+17!yQ3CRkL zBE}r%c%zzB9>>*ECK=;@K!v*RPlF>!m>nXSX~rQF1-Tlyn{c`6S##fd^W|xbPqMym zd_KTh*~Bo9I>$Kph3P1kf$k4Cs+O0nM)Ni4)FioO(hJHII)grN4xuTZ|Fn20;inuB zx^C$fVNGCluzOG~4*G?b5gO%RX!ik)jPhT%!qF6LSq%)r%ZIK3U~;7vXTpoH{@AyE z)p0k`LzTc>X-gfnfp_XPV(Y3?h$pa5`&=ml?q|;?V`H4pEqEJXOrH<_A;JAMEX0Rs z+^Dvz3>Vk58CGy$aXHw9wgfVlCqfkt1?N!lk|*oQDTK45?u+MHI-u}gv**qtpII9#o8aF3z|6&HG1MWK}0kX;y}h$QIn z%i4^1%Q3(De{UkD>k%k8OHn^Qf&F)*7l@*fEBD$JA>8(wj94kEgh>4?QfR#lM%dO; z?ohrk^XgiH^pnd@&@yd!p!e>>lWbv~QZS0>#=k*T&ezMQNwMXSm-PhG97P|b=Sh$2 zW+VL5B&G+c$dqnqcQ;HQqNe7$OO-b3HUYqQ>GVXMcbU!;U34ZTMFZ5`V(cHQShR}V zFK!>hl4eo~n5W>Zg(BdIxR_H<4B{B=kP*k)gh*nF9@Ul}#tQHzRS7n=B)20a&nt(8wB@qvZ)LZem%aWwAV%^&AXNi% z#&iGAH~gDUsv;d#dq`qSYZKu&4Go&+I;RmF7=%Sf&nA+qm}OR)+;QtuR7)!D>JdpO zP+<%O^Cjjo@d?Fzs#5BdDYLZ>#d>)4O5M&aO!{M}qe{8*J$wK z(xfz&v3n;Xb`V&yE_p|n#+KB26Pz8X76IdY;J2to8s=8^YWgXK$59DT;$62gU~^AM zNaS;PWzt%XSfTDje&IO>-;xWW!L+$mTzVGS2pEVt$|M$eWI+UWZgk}SMf#dUJzf%V zA%?7Iy6G%#o*y0pD?D71@thVScrDkEm9Wr-8vqtOq=Wn`uyd=MlB6*Mtp{fXDF+q6 zwhDbr#xh(aUjf^^J>#(uxYi&+f5i}L{UE4^siopqkYB*eUjPcr%lqzHskclZ%2<(WEkb}<9N3w+^Xhh@JWHv<9SrKc?E{?Nhz)?#+%5#7-{(Opp4S*KrkT`)kt!$7^+mA5SX$z zsEEWaNS~12-LkXucdqW~zkh>&Beu zuQEm}L1yRNkvpu8@z0zS*xCcSWZlY{mBP9O9;B*_(N^7HTP6kn@Qf#?ygfZ4}f~9I@HAaq_6KmycZgqxX zu+kAXe|)@!w9b_#4=(8AUJEoymQ;n7&6OFLs-U6hbM83H_Ecr*WJ8*bGm=v7(8>YA z&Go$E2a&E=X1S7^2|onZc5w+Rwqd_+KwwWYJLnSf@Zm8CNO?(>y7(c`w-${&iW(-j zsvs2Oa_392&G9y}2rsG{j4}zRr^K02l@pU$j)iFx3VFvs*%BQCJrlAlS39SNdIrl# zKfX}hhi9N549X|o38YfRDlI=%LdXn4qm*R{9CF3maR*_mDb1>N=TvV7Q~?Wa@#igP zW2$ca2|Paq^O2UkFyBa>!r&Ak>&m7`uc{7yxKW1X$EE`{dw+!v;HFVG5SAqL{}9?; zB--s-YzgJL^y)P^kZ9T2+VIoXFRN+PwKyvBOCQS#Olak#4+jl41ML!o!sdLBD39 zKSqw^TtY1T?3~IZF+No@tm5|x$)>NA(G*iSF4o%~Bp<*; zoT^_{@Lev?vV2ztlTj_kPSv-xphc%se_K`_9IcM^{9N69L4u5^PcbQcrA)ao%t6R4 z1F8I`ZYi3coH)KJ)+?L)EC-s$)aUo$%I}>b7n=+-;WwhDUJJKy$ZEQzxZ~J^Ug0wu zZg0RMs>8a?T)M`jD=QzHGE!^`&r4gYz(JS^X?9gtGFNVf`09SNh<1||ubUO{fD*7; zaSArb@O+?csW5?kD(M+JoAL=K%u;t>)Z4I-jF@By34f`E8a^huu@44o8AC~Jx>eGK z$&agB6z)i5!`{ceKrQ&dEYGl8n1YZMHMGQp&bOKD3wsFip4Woh*y-a$zQCc&B||~g zYA8`J?IAkB=!gS^WEG6ZJ3TDmQy_#XJT!|jV6Qn+YXR~Iqh zpEKShv?^exsuNS4#-WlGI{^c2b(yh^+TM2F+r?Qr5mNQWbTF6X`P$?_bWId(Lpw3lSOAE19NkvMPwp(HU%v!zG zxYziKiVhuQ=rZM+cpa65Mn%;ez{;i){=yi{aztU~DA|ixymOBn&8hvNPTzbSt7qLvz^^4OHK`5ON_v4!F@o$OlthwoW(!C|n*FIE z?g9s`(tAFT<9$MNS?M$W^*{JF8}Ut%Qi)VEID}m% z)Y$k+$|wdvin7rarZYn{deCQO7w8srq?=*IHX?OZPj9BCBy*f_2$|@l9Hs|gQ3Hvy zoqTTooM;pffG2H4X^XHm$NW^f1{!#}P3KJyf>Mt?J{Fc)%bJUjh%+LDK?g6M@b-9aFdPXi z?FjK;AU6Qli;fAHL?$TT85JcJ9YZQqk1FF&25C46>I7@=Z#T*X&UE}dC~0xaW3a&w zv|Hpt4Ct6wV z%$T|*0Uv_WV8Y0$Y7|H;32TMPGYv8JMvszAJ;n~gptBfiBTOPx!38Gvo4VMUoPsAf z2;p5yPsU3`Gmo>{M;@-nW+(4%24wtz3|pB;yvfi zZrR>ZcfB@yiCm7hn*_*-A7C!x0zE`!#4B_6j!MUSaUMAVL4C5&1A8N+`~odvb6$3q z**bZl)-OG!2uY5oFY$J7Q+jxpYuoq+KMq@z1iL0*b1OSBja>ijzg{#<9xZ*n)e5+>wk>!Gq8X&n+|Y0Bk({ zWE0JB91wo-WG!_{cEx;L8TWZoHXkB4{-$q_m3!Q&y>7m zQd4UiC`bj9wqbG^r=O(Bsy3FQqXrzlsu73~LdzItVvW_Yu{43<9kshX1 z9W|xP<}{gtun*0^<{~(i6tIONI&BGRJ!L{rP%DQ!z~u`2 zN&7mj;RMq5=YyYP)EO^oaYGY+AE^3{O2NXWw57mQe!?|!qF3h z#K*9Vg^z=C32jwE$q!X9^8K|Ca;1jV5mxGm1wv88fC-GZg0L&RN!LAXVNcl&mSACc{2y9$_*Y3AUkDA(k7kirU%eEq#9gWVthR(p|{x8Wg9rE&sFB0WI=8Y>ivHWP$h>A ztUQ4>5fud#1Vr1Y%jC2o=~_D61@YLwi2dkI)T}O@DWW>Eez5{#@j~b6aw~MBb3ppO zMo9~5!C#}OQ2{$gv^7IiE>y3Eg6BKU9)=~u^v~e8B~u!=fub_PwY-q9@(}Nwg!+vz z34)6AN&!jqfhyGksu?t~0L`G&?&E61s3=)0V21%HRhwj+UhN(@Ac*%_5Aw=^?C(ix^W}n;)%t}b|4#J@A8*SVqAyvPH0BcibC-wm_s* z{RCP%U*zV~671L6Q$fwV7W-oM43cDCKp9;RX?`#9X<;Cm?JL76Flv2?d!o{Q%1~u( zD7NHbto&9n6s96^h9v2eA*O$^=_!D|~79#sEFLnR$T8T6erh&T#17K%aC_*RQ? z1N*dn7t0KLIFdC2vXO@nQ8Q>p*muPhh&LOpwat}A`wTe0=m2I_wY`ly@GZx_;_vNQ z`Yb}vb!~r8W8~F|oG}JrkobyqgA#)=4zKIAf@G3hC2NxQB{7vz9cR>pN_3TJjy2#% z1^z@6cgf>QTf_uib&laK%ehMwm5FT9J`n@=MBDFZ-P!#@Nbu zE{IAu1-d~_T-Y#l($Ujgxq6a`4iu>#gm6P=(d5f~57AMdv}Ah&fi+?oYo%B2&>B%q zT-kApMG&94D#9)cXPeBpEk@^S}P{e=%0#Ey^G;RE zkKqQSEvo_lCPOU0t&uSpiGflWDFNXe`j81!vO$UY_)qRE%HB1uBgC(u3vmhIIbBeA%)!8mh%KFsTaCNu zGL@&bEHZMcRnC8l7#>W0#OP3D3>ct*8k#U{nvl~f5I>F zMu7)C_+-7KaaRHJQ&8*fLca$2IgV-wPpwkscbT@{n%qG#7^G6B3qkiwzbVT~OJEqE zkl)bhonfAuTf>-87j*r}nUG~Nv1Q|fwS`2^5SSFYCj%h|EbyEn z-OX?^!#E=DLkw&bT|l_b{B#=)1&ae_dNo=fl>;L12vQUD{d zC;Yl*@g^Oi@F=zkO%z)Ol4>{XqK_RK1D%JbA}9QMu#{b?$oP}NFC-iCyrb^n(J}rn zIljzSUXF&LC@7Bxs?kecl5X)lEn|R*CYU*9uh~5peIctLT&^N6S$Ly!fVx5?3)_cF zQ6(e6;!QTIvzCIEr|j~Mcp!j%D59;T8Zd|LWn6Ht)R#8b#NpngBs;7bTIh>=vYPyP zZf<}W-~r4ybXP`3sKLb-Iu#%SsZ_;5$*5zAVoX%^^cFN8SmK>}U0jx{sL>Sxgs2JT zS3$$3B~fmO{G}@Ak~jux{d0=%ewV-Xr%r1P0O6{szNUgMwAIxHVXD$}lsQ(j!B ziK~cm{RQmD+!1(?6LogrPR}CA`jv11ijq_^t9A#TWMX~O(&D2W4LX})e+!jbls;M^ z3~C4<2)l)mwAaOkH2rJgiz*bRi)i9E9HHu-oSz_=Pm7B~;>|Iy%^F=X)Ofz&K=&d}x9FXE9?FsdQ>6P0 zrHZ)HQgg~JuOc!NWgAN449C!M3CZ)3h{vMHNxx#c%LFbAQdet5`7qr^*MQv?8hb4U z3lRhbKg%}}7o+qL9U1unj__Vn1}d6-CgR9BL4Qnq_$4D_-3<{$vR*teRvpb5aa{#< zKwWb=#1gXMax)MYV=NxEu`f!T^oIqgoI-Yz;!79%gZMZ`g;)E7#r7GO{q@z>l}YX_ zouA;MD!sQtf9t3NKwi)mtKX0E^c>gIc1jnTSgVD?8tztX_!C@yvMDE)cJv>~23Ndw zLhmr?_mJ*pu>8KWkBwIVEr0Bi2{}cD;WAO3-Xwi65H4-+uu?_cXe|G*vbDRowzRyv zys@#iv55r=w@vZe%NOe}H=ivp?Jho>fAMsgrEqAVSnAdC#^%b}i=+@@h^t~7Q4#4{ z>#w8G<6*q+2Y$`e-PYX1`2^$)r~S_C-I8?@S)2W4W#7Ob>-GuHp#tW)*BeO0`Ni)S z{NDxClNF|1)*^-569k6P;~Tkw2~N0%LukZ?a4X*NA-LGHz!7 zlxZ{T2HP!RyjkA;kVt-F4_@hr14GJRm_i)cT>vCB=)(PBATD`HnM;HEBl9Hc^n>Bc z$te75j(PBO?^YP}r^%E^A0!l!Bx7#gOJMicr;9|+*b^*`yuP!oAVe^XZn@Y>)qz1S z>OylP2CW>Ul`@XGx%G|Z-Oa7}t(Tj-i_e#WJ~qKS4{?DEz*X~;60_PVe~y(fIO-fI zr_XDEn;ToK_)706wx|8lKP%{bbMjEREg(qrWMjbvQM@P&gFaNz9&*knnRltlNM{_! zf$)E8c%HeBT?zo5(O;g;yHFI=rQRtLA03;3@I*1Dfr&D65M&>x`rt$=`XqPy*p{)W z?0A@M767OE*HTw*-s@^@tH5B0w5%${Nq+*cCB&%`yM#~m`CV?a!qyOl5}m2WtW%Cd zb^cmHdBcC(WmWh`94$p5#!kh7GDIvX86Na9l~E51Vr! zd-J6l07gB6rc$(FIJ&8g(64_@6H9q8U}(T{?&bq@?W-}>R_eN|57jQo5pLK=O7b8g(KXWO9Y5pt} za#0{JOVcUc1^WorNt81UZ#9k-$2Yj$^SYtn%emr}sAhdaTU z$ze<*XD5BekL8qYvR@8$h><4&R(9dRBxx}=QlMX_5+i`>squ)}Fgae!XB|nCvsCwc zecYydbnV=EyM@{F)(xirH;s(?i9{i^P?*Lsm#iZ^Or-I@#zni|6&cA3_C_x_Z=*12 zB?$i1v{8jtmi{V+INBnCG%{}gb8^g^LA$<;vJw;;3|nyFQ`Mk1u`+S+G2YTeb2;opGks?r`)m;3C-K#F zPET?6=uN%z{ljxol|>_uHOk;cEOoy?FV?6 z4;Zd3ci3S!uFF$3meyf{MtkBYhe);w9xizmM$)5gMbqIYq(>98A7`9Lw){Lv1I351cjRf*y-iU^Ctm(wHZ= zbUICjdve1yrT%4ZZe??Keq&?)$Hwo9>iA`CeG6XM`BiMWt}D_9>YTiSdVGnfO!8jw zs77=tWVPPP37+Ay$V_r@c$ude1@*Z{Ly!u2zJVA@o1lG22>i!<1W7z@c|78~aX0YB-0 zaypjqo3$x6z*IXYn8zOP8s$$ZQa z@mYTV2#s>NV*ojG@CTdNR3cX5Z5MYU zz%LUZ2O2HkZTgB;5!)@I5Q7%aX-P5~ix%IG2PPD#*PXB8zpA-h&t}-yb-}OfuRnC+ zjR+WFK^n8iIPsi{mQTFWqH1d~%q(dFYNM5rOJ#~vXh7N&a^t3ZstFuRB)Z)Ug~c$j zCrCUCwf2iN2%w&ON-iI2&QPa~bqwUeE(H`^G&V>1U4lt@@Nr&OM5<1q6uPuXjz9+6 zNj+N#)-f!hkmZ30M&j}alK6%7b`Mw%UgIDulGo<^cm<6zHj4DheP%}}yu)!8VGP(z z;{-Frv~mlALrXY}Z`?&Wfa8eN-GlWMEvHFo`*+#Zw79Lvs*;A{*ewLi>uGW{J4K^_ zddPgB3-#V8-|+F`)C~DpY&Z9Y#K_Ky0}e?gUb$I!iLh?5Rb%P259Nrt|Htn1o znhCU3tD~+~3k&5o&9TfhvxIIOW=eA7Gzr=rN%P zG}esWH)E%A1vN=)O%;G*O9{ouZ2he<%h6V#EvageY@PP7)nq{0!$)FLmWKz~mCtI5 zS)l#H5R1JQ8z0);yGrp3yj3H(dTtdnb-g3KSAoh3=m^(H8Ya+A~~hi{OzhhMfGjFakv({V?NHmDTDXp8Ao0k3AroZ9D#h z+!=q=*wn~iOy4IpH8pZInvMfc*9#Adgdz_bFjM+5Y@UVyv|gr)fS?4x4ft4iAx)#b zk2IGDG$8ODj7&limdK~k$LbWx+)xUg{9cFoLd2zT5 z`QSD;S9l+t>9Alrh!-?f2JE#@XcPO;gX0euJXKB9m<|JLEt?-iJyrP&U`9%5$`Rn?P5bmn|gyIu5#Y2iP3>TEtfQo)L^oNIrzecfy`;* zd*F_z8>*oK2KLVf!`{i1jSOsM3@DeD5n;19zqPzH*I?poG;=0mN!Jx}V%K)(Su&U> z@mkv4eAk5&ELLgYxUo@aC5ZtJ<)z~cLK`RAl)TAKfh=+zYlJj~{l)|C=mh4;Yfhbc zRA^TKEcIl4VV)3p*YKzpvOsB}7K^4qje$bzfjN$ynY#Xk0oQO3#DN^a z<#E>GuIN6b&lnxeACO}YdV;{TE|Ge9grH-(q=3x8(oLd;Z`Gsq9S2x|V@E0u5E$6+ zVEPb;cubn46Os#pd=fK>NDSmXg5{}mE1X^y9m_a;)!_8tlRD}C+L?+}3FI_+3`ukr z6-$NaNxo#Il#~z7nWR2{b8=SQsb`>Jr$`~yMX+G^LV`DO_I^aa^Ol{cAq{{>i|bW% zf#ddIfSfkMEvFkR^fu<~{GU@(6=VHpHGu82{qr0n*EycaHs;qC&qZ&+rJWtUhjs?y zW=b!AH<+tC>>lEFD5S`7zX8Ox-F65his#)3m;{{0H{cMGQp0z3{2q?9i(zE)9JJ#Z$(of>muH-H=7AE0`Sw#xQJ zkCuEe;0DZOfqx=flYVjkgT!3Z25Nov)mO{#m~Vj|)q2#X+$&GsLA4sDCt}NGA-Pf= z`Opg|_S>hrF~ZT=rxx;8q#GyJ7Q6z3 z^Zlc_>i_}~;=Y;wjJ0+bOT9O9a%GQ=Z6vq;*)TjY@gp{9PTDvqOj&XcE&+Jj5FGvP z;9aBj_nUwT3G8qX8SsxDT>`xDJ%o6kb@q{6X5Rq%IKcm202o*{;lj?1aSx zr7oMe3F|+;Vk|mbY0@?RRb>`Ge0=!njj3n128PJ* zb$-m`1A0AB~I0{Sk` zfPYOjZQTS?hy@F+W@Ry;HZvYLx^fRShDJpvbtTykjDhOri)KPayQLz@{8^|?{|X@j zReNLZ^m2{vm4y>l4AiYkFRDtj&Ly@ZlbJQ`Pz!K&{j+r$97)bvQd6xMRB^o z%`&XMnuSLFSMFVG!)wsNv8nrCRoKFMb~l38AZgN0O#qy9YBDeu zX*fP_a}3cEZRNCOMdv{WX-k}1`}9(Lz9QqYH?Q4KPzA8K(3n|9<0zW;9Fqn?OBGE% zw6c`w`c*BK{Tz-7H5AuF=z!eQA!cG#JC)X`H)W)yr}!&KQHhOL5y3VL)2+2u#ptW* z(rTGih`z8+YUFCBFi0e{GC|40#ZHw^OJ;UNrAm2(qBe+?sCc+-ssEDqu024I=vm9agDq4gQ6|FCdt+o>BvJ}0w+0a>aYZYq$P?W? zaJ?vrFPeh)Gt_Iq0HV*EI}-!-58-@VRsv;Dx~DKuU_Xkg=>j*L6SO#+f;g+wR&ZON z%O7FkdAUsAkgNR29aw>vDfC}4`E}@+6BYNTP4UppX?qYoqoRdv7|>)oxWOjqE&|O~ zn@J>qP79EbrI3A592mWX6M&+RX5)&nsfqG|wpV*NeHcHQHfLB6vW!D2_fm|Usv4Us zpaQn)2{IZ}niT5@I2c~(M;+0T?21y91(QiPN?XleY}=3oFzF8{Ngc>{5|az!mPuc3 zO^bPm8xBGoI5Z);%R~bRrxYxtTCGEH133KQr9vxYT397Gae#@Z6%6AVcKF^2;$UE4 znHfpICJuUB?iC0Dq^eUNblq2qO|O!;?IEF378Ax+T!z*nu(Q#-w1_(&ADh6f8&(A8 z4$)%>4TZQ6@(^{~_Ev>b~uD_ z)k#ia{ESp}wVuLrc9NuS(8MW8buMsj?0kWb2e~RBIFkv3vQ~&;HRWIT zn=J{RP0gP=0`LS5*j^vWgy>3gp}DiJOLu2g^OZnGFd+{18|Ha1T;CR}e0*F<=vH!c zsF`G4GDim?s}H~s#o)stJ(4OxbNGO6sO9!fudARIaXFks!$T}S#75Zx255_N?*PPH z%sPT9u$+gm;<1jxSx2?mY7>Hhy-O0K$}kb{7m_`?fW-q%C|u}HQ$q%kxsh`15d~<9 z*ooPHHwC}538hinOSZRo(#ASeIH-|OFbTEc076w9K~i`#L*EnEl{r{6T4V47RYU4LuTD*-hDwfR|$)oe8BXy%n|u=jq<|Ij-1A zu$}0j?qvHGl|bkQ$QUEs=nT){KX1b=c{=rLrwZ;H z(c(sZX*uA?g%6e`<;!!6p902@OsQRW8x8M8);Vu_;5>5VkB5 z^jqB$P;G?ucR?CBNYY{yd8fv0E`yajW2Ka2)Sv|=+qm$1+jsQla`PU??E0N!=7akH z>e-FgukQ(I3Y8@R_wJ@-GNxDl7MdvYVB;()nBP1oKEs->U)g7f+>xrm9pvD*HgeGl zXXmr!#qW0KS698&b!9+kxyI$AO*@|o(5#Z%T~kRIsZ8czQ>n5Neq#_Q$Vd!78%ga} zVOy5uICypjAb!k%x~a$Z6N)3L0Rj8K;a$iv|g}cQvEbrS79Vr3NpsfVLnP1tf9Ox zo07Y>g6%L=;dbM1VTS(}Rxo-<*#RJsQ(btS^Nqmly#l#IQX1>eSQ1{ETwo&ze;aP1 zhm$sLfAsP(ovM#IQzNtE0YJ1At7A82l zgWC-an`Q)*gw{BeNzWq1^1)a4V|5>IB53JoI6NE7&CI}A>Qpw}>mSY>^!8`&+(*zW z(Kvkq>k=xChdnaz3ZhIu=1~+_6|!-hBA^-yNtYuTc}wV&&IXJGr>h*B$tpe_K*0rq zmXooIYbQ+@q25hXrEMNWj*3kQ8G9}P-n8-Hk2f-atOe_CyE_#rj1)*L$p2QzrSCOR zrc;j$QkCTtpEuScS4wD63nI$X3G0 zkm}|ZT0mLOyiHkY2GCu9q@%q>_4&AZe0r)Qza z!*vIj`@+Wji^XR!@>b?Icau_~z{c|G^8DuV?h;N)u=?zsyAP-CJea!suyNeZ4&MQ(9WzlFU4iv6RgKY+N{IOL}{ z&id}g&zh;L3&7O1-O_((o6w7p0HQAlhyJQ-3Sej(tuhp&YXS(eRT#wjk6L^5~7hV;wsoH*7|txSF~R-hKlA#j3Z4&mb^U@h6#A*a_?T^h z(o4dwReIBq0D+7(CnG7bZH}e%a>Qs#{Z)*ovc0jkMInMR^+(z2j{c+Je0A!NFHW9& zN4CuYFLMY%K?UVuhT;|P#9#e2_X-E1j_rrEiH&DO;FrbUWjyXiNCD)PuxvGttH4tghT(5L`x zM5Y8d4S#~r)?RnGcMjEOIEB=|$Pzkr)Ef+GLrWcN4cOr>+tkqL)rWu(dhE1Mk#5uC z`)!g&ilu$ryD~gV-`F}B(J4wCxA&&1hk!3(2smjCATUcuI#4d_lQSIZ!%8*&Aw9d7 zY@*4F=CQ4bBaw`Rk&jK}BWDf&6c*bw-s*jO`Zo;88uqf`rUMg8Buqv$YwQitx0C=f z(H!FPcJ17~JA~l8uXL~M;2Zd-R1^0W2qcqE?+ymXyVP_J`-m-)c67S9(@P-jhQXjU zhZlR;on7j}ln8IQK*j0-=;f-)@zeK2b}9{$rocIuPcORt-YElnlMnO=bPk?@ydri8 zXO$McySq2;eLD<4&B3A<>s)PAlO(sZN7SV(-d*lQ#)4NSPl@lXXY z{+K?K8y263y?4Au9+!232Ty_2y8Aq7CZJ=k{Ek2aZxWqa?zVKDY|DF0hm6%-lI0R` zPI4$fad30|Ld`y{nS5<1 z@TQb5e2Wm+DS94CaEGVH2`IM8pkP7gh3lZg24qX&u9+df zh1vTB0r~z2SE=C;OvJLyLh(UX#^0d+S4^c|GD@Gns8@EHqN3^Ay5%aQdtc81J&95` z7HP4KD%A!;c*9YSHY-G0+9elP_)}CF&{T_z7JOMQQ3Vm-pGa#7D6jkWc`Qo>LwsZsNG> z`r795#xD1FAOMC)=#BJRE&fE{haf}pE;e^1q+Wv5P|;>zG$4(Ngp!J2tNg&%#Jn}gwlz=?GU zbHO(%!X$p2I6?n5kg5AO`-j$Wh&yVqqtR;4{KwAV z@2%}SQ(sR1eCsBEe>2zGes^;C^G*EfezSM;o15R@_2di_r&M%^m(o;cARKJefq{t| zi*nF`0cvu|o9qk&n>h1>aMjGc5I}DHR?+DnH#Ej{l3oT9n1Qf;7YVIZ*$@%zuV+OZ|Je+uVzz(|~Y2y^8$=?o{}m!-bo z*y*mG24Zhjgkp$hcR-gB89h^kUzduI2>8Y^(%-b2|MnlPoyliw&zFDyA2(Ywzfa!$ zH_(DESgdI*<5rVjAt#X1)!)1XqgdZqd$O{M555*DDkN=k#n1qXjZB-id3P2-$ST%$ z!c1w$!=a#q08P~~p2)`*R&dKHnA-f7!d^jYfQmEnS+pI5V1xAllYxn{ns0F04NQ-G zV)PoYGn)*p28_^T8mV7w4)iWx{+MnozejEtp08jZx3Qggd~9^@h|;2A4vT&?UGK)d zZEOW59C5L#-pwo{@)AEv@=1Mvn2bs)#B zIuvTCcf>5GQ}W50HrJo29-~fJOn|1P+T=}Hqs+-emt@d9qTsME5?gL{pnryffB?S*m~8aH?;kphYCn~K z4x6GS)zW|a4=Cd^zi+pve*Jk){!xV;+Y>kc5XD?-CQ?nJ@KYq%bhtlU^e|{+v0OBC zZR;YyS5O%+CW*}YXVfISd#oo%!5A|mlS~3ojE?f0QsG^^F z;HnCt>)=Hl;qs_&CwfQJ^C6?FT{R%^qb~f+hzei0S`>o9&Pt!xWyPfUOez9GkJ~9k zuu4VVIs#J;sS+di z{`)ve$(44l>AI0Q7#n#aXSv_TkEhUvampD{e zeTp}DKaf#^o#Ui`Kg=KUFYxYE1m&JGi=b@%2IVA4#3zGs_gNVhjEJfri9`PzBOKQv zPr!Ti56n)>i>j1d*d@a{)jvhi!L$i3OX1C=vla|e=Le{)uA~`6i}gbBvZOyaFsP9E zL^dd_A7{j)x~Zr+vZwVvQ;!bgV8-bruN2#_8p^8P{*Ik$V5)K;rCN%xQpM0uaaOQe zUtlIQ8`1|+165d$EZ+GRDa7TkCkQ?$L>h%}h>7bWMRgk?1BD_IZ?uW3r~4jdi;2>Z zBUVo-ts%!VG{15Be#PlhDbTeHIlQLi6<)=COQ*XM9@9;5Lcnps(~G|`6|p`Rl(1+~ zR#c5*8N9ZBO+schlKRS~%NlE#77WG}o7lV^`-7(t^Quy(IIkmD8yYpZ5U8qC(%~Rm z&8!+&yi?u2X+c3LX#Fi}MwGB9%71i1$UzyfAtFV&qc*UeYud^igF0<377NLCu9<|` z+`%Ga`yz4dEySbr&iCjj?AB=ixR49m&)t2qD`kUL6i2ZLRdRHQcqHJc<<} zh13!T=j0l^kZ{CmuLTrjsiWDfWa|VtUtzjd;ydg!=&K5X&svTnQr$f@3=)!86Gupq zd{DCmYC}$(1qHqkX7Iat1tg|`4&&B4Ws3`hMoatd)VV3pFR zSz10A%Oui-(j-uS$5{_Icd|6U;1xbNd)U?%xw#TWn>5TI1^jRla?1hLxB1>oh4AQ| zsgw`BgzuF7e7h~6C9BY<)mN=dJ6f|veeKF~On<6H=k3Lrt6y*bQgfxMna_jJKsGzJ zuHqbVP5A8Oi%#Z9mQ208#v&+b_T#M`RwMQq^@@K|TcSq7Rg}!c1;2y;vn8iIF)U>W zRjaG9=zE>f1K00Dh6_8HJVxx58=ntwJ%Ku0yme6>YC0gv3#XUcM z&kg+9xq)8cb=tXs{Yrg>T2_%qy4~o)mX9WHqVe@Sz7?;tv7juVRv@*(74lA%MA46zQ=}^*N_)du;NoLjPmLqoG4iaSnajoY55pMMr zmM*uIYde8~VN3suJIu2r)-Ht!{ikS*Hp|4CyodgT zKT(OnV%8u_Z7ccE?ctR#BgBBn@yz4iA-A2HkXyU*XQI)Vk)Jbo)R%m;U-HF^`rF+J zRGC2wtk~F*zyBG2>9hR3i%Wto#@{V=aFe~^>%hGCTd@ICWWI1(nYpRH%BjT%LfV<1yB;FQ<7wCfeEWNN(U=*tq*tqNO{qR%R3qGZxDKq;%*JD;=+wV3d<-L&*OO~yM-Fw_}AC&)(V#ghWqTb<{& zCkW&Ocj)!6KmZKrk;!YoFAb6!S)23TVG75J>Y|T#_K$jv8_;DD39!fnh$9nnwfo=| zIE*|q?Dvpi5!on_iwE0l+*j2ersU$fDZvRY<%DJ{_mwh@6;ii1cvma7ZE?UCJk&4o z)tZ*4RYtO z*hLnC!3JhY1g6c;xD9E)~qUIpGZecV~ z64hwL%GA3h;IxNP`i}`&6edRJ=6uniJ~P$22?M3^b+7*pM`VH5*wPDq4VuFj>rx1L zvUhyI3i<_g(Cz-ED82hfI9~xDR%j93l+u?i%Axav#^UQGQYeo$cncm@ngnUg%`Gi2 zynJfpM#Wi_7;d4W>n3E2YQFj$!8h?DTdj&OwTk;!q@%nQtA*6&2j+ki&0p%WV??4O zcE8a=ZYt=x71zJuzy_I>D9_3hbwcXs0c^?*%d$Mg$L8U2Z?BDm(PTj>uOA?=`>U^Z(opkpU$z=R3Q zRXv-fi3ZlbRW4)SMU>c`lidM4sqYa%q$3Wx!=IqJxX2QE!!j|f8e#imf>|!5A~<2> za?@oSoB24`VbJc-P;tm97_A59H%><-oFbs?186*NMx=FtbllH0(F9vP1;c3I3XRt1 z$bHAWy?lVA`kYxX04HQKuhcX7lD&_3caawoaTv`3>Ov+%A0{hGKJ;o-^i<88is*BoXF+@Ff zMtFA#svv};hpR$(H&DL^RpA6F9*JF~k?u<~+0g0KJ3nI*1x^PXkkD&-r-x`K$wQsQ zH(78r3BFz%rmp-M);vQGvjYzE3#-e!KL`ikeLcVNLN(BmNC+GTVQKK;c9{NJ$OR-T zWT7KcZj$H?j;d zARklQa5V9rd6m;4C|H`6d*$;zD=v4JX5_F#!Ik5QmmY{5f>@n~10-DJak~Pgy83C5 zVs-`CWW>hag5zC4%;sN;L6SR$M{X~i?_OEKpw0z-CVHvdBP3e68xZ56pF33P5+-3%dK?YW#7L%|kyT^ulf7uL& z{=_La*#aM8f4D^@0(tYp+NJ{wq-WS7a#S5zqF)gCh>@WsR75$N8aNe)D>~4MdnDA$ zP_W`j_Dax@X1G=_HL_)r%(%`!1bJy>8T+o53zFshxp=t1?2zUf>hUyOOu(KxhlI_^ z7qG(_Ig4%oaJzB3`FsH5YGJ;7M#_5wW(j&d-8<(!6XHSg+B;#Bz{>j)K|nr94x0^f z+YJ&c0K!`E^dQEhodE!E!yk8t2iXJ0lRI20U1;KJx=ej9Ny3z)4_pt*3%xQF&d3xuf!IxC>s;Red>CR77ZTVeTY=L>WaYA#BPM;iE`1 zsi0^K6=coDePQSCGCkNDLxh4jttWBC<|Zta4z3v@cVWVV#@{XP0p|r7E)L${1N=wt zcHf}TE)rrRF~B6|1h3H_+mdEmO-Up`J|6wmm_au16?U_eo^DR?#-0ypI!U7PsX8cY zQe#10&COJIVN4A%1)ukTK626+5C!6bX~RSOm@Ha~C+#cV0KwZ4_S@9-UE!$@+Xr8O4|8R$LSgxH8zWPg%_+smlwxFx7?Ib+$W*|{)TNq%Q@LHEKtNL( zIq3z~dHqB|yuxH$q7&Gu;=f?9=3?FyiqOh zvVx787|K7O>0k?S=Xe-$z47Wd!U&&A-YZ#DU{BZVT@a>#7#W#%Utk)Osw)LHgf3ti z4qJDX9eQt!_oxW{#MwN>%Q;tNKk{Vl7aMZE>#U2;DQ zQC$Fr)#lh-Y{u@S`bUy|q6RjE&S}Fsk{FJZPeaf{irbz{eF3ddOiM`>V1(ck3wSw6 zb@f4@yf+h@pRScU{-s6*J@cx%%|}}owRN#`UsH1v7AB0K zy76u(TdS$ObbfM18O=zRnj)KsP&%(`eO|Lj*Hm5VwhvFSfm8DtihtJu+bg>;~dry++R zbH_-8H_gpe-8n>#ZymU6aH6R0F1p3_DG?nW^~F~Py%;J1{>Brzw&1t-to zib?Or2}^Y#uws>|$Jnj5un$hTl#XE_?tgM|o_sFc{Jn%5l8#}{z?VXej=Scj^XVme zbn`ITI|x9G{qbQ)LQ$><WuSi=+&I`nM;D!O!8xVbN z-D;{Qf5I$wdujG3#3gAaC*GhSJ&Ny}0Y%m{b&<*VReTlZwK1V^Wlz zm!~*$DCzznkk328*Vk%#m`i{G4h3+g5YQ6d!yW2o9aey`Pvi@c&KqxVF9UZ%9Xxey zMHDBniPs-kp@PK8l2JH?T@%{D@}GWKIvCiilKw=Zo-~2tZQLKp)0%=ge$~Nh*olyq zQ$ZGg(%YjhWv*NtqO`#lRm<=mnty^HOsv>;RU+_X&Ir^;>pu~;iC~Hy4Ji!I+?4VJ z8PA3%$A!-!R=n^YoWdCjoF@|h9n#pL?arb2-WQ>j^#d+i6Zh_L^QXB-dmh0t_mJco zkFeGGFe?v-@Cu&t@t!mpc;ruqZNB(IUhH4BrSe00Lav)3 zO9Rcdz%BrkYGCi@z}`=Ry`KYnzX0t04A}ccVDD$Z-X9-W6{!hA6{bDxonV;SDYiVI zA?;%h@G4$*LKkyEp-N+7mD=?x&)EJ=ss^qMq{Yp>AlG*CEFxHRv2`#62may(n5{I9(@;}PO%{t=zBeR2CVqP7+)itL1+O@w1cz70ov&|TbSU0| z9l%H>OUCdIx)40$FwVdkqdkytO>$Y?HZ#+?c4cmZFCBF3v^YDTM(?=Kss}xPdd$iQ zw+4vu2wrv1#bs96Gt=qmT12+3Y9MOb84n0sK55?}le6U2o(z~O6oo9ZTQX~ z6Tl~a_UCEbkxOEE%qhTssgVmawNq3u}<(N7`9|>|nm7 z+YQJ;1felh4I~S9D*~aGmKRqTJioiT!bql75b+%x0nEQ`;J(+j7x*Msy9EPf@(lOx zkU{r`Dtn>0Dge*hXO-{2KkrCVW?h0_vMOm`R+l=S*d2hdZr!UHyTrH9a0Oc< zaB%2NLTdy<=$kxd6dL}$Ji1D{fkEVmEENXGg??p7Vlivq6T0}yctzSG@3~mU@rxn= z#j|e~+42CwdhiCB#;h3uS+Az*tdK9a_jr^eDul=FGG%qJmomIN|{r<-;Wp9t#)7Uq_Ej;&Vn9 zaa=kC*xE$=9I)}pIxuX84lsF${;ibQO4(wRByFh~8pmyg%Ka^=2;8x>457e>$SLEG#^w3|wqy+(l43CuGK}WHNse z=BO7`ATx!RrkBmO%<1US3EWm3T)&2Aau6Hm3}~ySuGoKp2Nm!hbfLl+R4BkLGTEi> zAB2dTooV}UV9L0hX>C_?Th)tfWTC-gVP(5W(UV$oVMnCMbK1kTH*bV*{z56H56SAP z8rAk>QdT-D2|O`muW31Hr12O6nmjyh8N5dV?u$!JFk8(rc;axQ)5fM)#SV{SSL$z% zXc~KEcYhv%*3xL%?Wzi+Pu{O-Fx6;AxC2k1ff>TIM8p7MPE2udtwD3Jk zE+yf3gvf*fvbHK#mjJcZ^V#nSeI+oC!aNp=mT%l@+!YANA5ck&`BcM!Mmk)@6bqv3 zP^Ku(DB-4INmQu%iKt{zilk&Aj=)-F1N{y|F9c-V=lTI_$gl89`##6D#Jqp2ijFFK zM}oO@CGc_l%5WGeu8q~fh(@OEvHKR@llC5??|f|IH`X02i?~m*L35)T9zAfN7zy@6 zh&>#sf?ucV;j)L$MYnf8Sc)rrJYkQ^m~z||72Y}9FsD2&ck`JYp>R35DHmB`{adTF zU#1?L5Z&tO=O9&ST9^h~Tg9}xv`%!iZi+>?aDEsa#}hZU`d8eJ;dY3qY{3XI;BYWW zte{{FL0dV+zD4i+@Q9h=MT=})LOMHEaV|@nf+u#+xG`ni0fy_SSo_fe`kFNR^J}Le@6{NURk1o|0N| zPbl`&Vg1uK?_My{%U-V{&Anu%5MLZNguZ2{_f~wF-b_g+d!y zo0V)6h$0nmo)A{jDozg)E!}REU+w{1=If?AgdhrK>XJ|KL;XsE=J)1FiEY`_A?mo} z1a_@~ty*G%CH6`sNaMzolL^N?$nd%Q#?`hw%>NAa?V2(Rs?@;e2cM0Q271MA1nAUX zAU@v)hA}EUuYA9qti=#kuGjqfYq}(LkZBc%Kk0Squ{f|cHFz4?l&#eedt9O9Fy2vv z2W&nDt#vwb#EoO#Smt8@VvZ4xh;>~xb7#tl0O-m2VECM;-{o|gk)hC=l}Vi7py+6I zDEqxUY7cEU5Z)52BGYG)0Uo%P_2YYhrw3g|S#WlRb`aC4Z}eQeH75+tq0!qmLqSqA zu;jLhm#QRkY}KvzyFFz3yUK93ZpOW5I}eOHk-dUm8CPf`8|_BS!_R&3iiv}11-IV1 zEMIK&Sf^vmo@jAF&@i`{hc{XqBtit96(`Mj0sY8lW+Wih4yQ$dF#Dfw2hJth&8f!a zF1Iz(c~@-V-h=^Cdzl=OSuAV*#kRW_9|Lg;I|&#~zuU^Ud?q z&Q|Xk{OAxNKA5%s2TfL(l~TJ4Xw;dKBin)CG}qN+gw?Pk;usME6*WQdlS<*Tr&XEs zuBrBjTwG2bd1i=V#}f$f5MuF0{;wt$k_bobY|96&jH9f;kFkc=uM+}cYKweA9qj-o zlB|=2?55d6%7sl<)GiROk_ZBfmMk8j8{+iwxepF-Hfj5B)!#tN0a8#K8?&}jwxw%0alhGTBbUUUEf$ zaxW>jS6@zahR`Oo%+Es2L`F>A{Ra-F>-~kFO{T@GN?T#0u+MTtC8Je#-B$#HC8|72 zx=ngD-X056ac)bzl5qfsQQ{P2SjCyc=%PD;9GH3;v{(FyVf{uK*oU7%c%Go#+j#|N z3{HuJh}E+ELjHC(OTIMOUKwm+L6$0|b1bp0QbT7u?+H5cUQHvaPAgel*NEzcc}o)d zmO+@Hy#V?*jj6lH5QCjoE>g^DBv$4#fCHRF-Ibs$n+tW_+XE(+pLx>G%B3yVav`mi z`OPuK?7xn0Y4cePtq=fFic6JP;lDyk@B_HImngaZeGjCNfekd+pSar`bhALFWj-@N z#+iMjEiQk-iC#cP(GsPImKGTRmO)0ttQ_2#U~U7>nNT_(q|lbMfg&<3+F~L>05LLG z20M_EH#zH-^&v>E7}XgvWI(Y>6`+*SI`g;i6SMuur(7?iy>AvZ5)pmXWZof08%*v& zr&?7fe6*E9d&q%{6(xlXYizVmvow*yLaQH4q$!9C%Hzl$GHm-(;jJtggcox{i@EhVwD|r^Ow+_L&$ZqSF}@ zdI4$GlgtQRJf$GfGKw~oz4U1n)ytJVU?(Uvm?g7J@mBZDl7-~e>(sF^D9Wd{N@eni zC8IMH%wa5GF(WKe3PDxEbNESr>0m~%r86ZBPJKpqRV>cS!ZCo=W4K~p;Xe3ie-wa5 zcr?kln`? ztH4Kx8|J78v+~n`PMea?@^Y-{$aD+{G6QN;s*a+-%YFh1{zSvCq3}$V_c9#ep9m@Y zGam3&2uE;b{83-^)6OX`USj}3h)CuG1aIVELHKA*a<}UvUVVzd-VtPpo$T;qsXe52 z`?j}d%!xE#6%1Qnk?d!pk-LNV&bWj3O-AOwg{+EjgEg$0s7*K83eD72ebye}E)M`d z39n-tot>X@owS-=EQTYvB$(yNPpUR=(?_9_&|!k#hs#7xg84FFQ`=2xK;6omEdD9N zj>CI6``?nOo9tuG2#L3n_oFP8WkmmpL#`ZIcQ6m58u^6t=mUgALnFwxZ^JmGok}Qo zcZa@}CYZvey5_d=jmPc)XOkp99~lW$Av+j^+}Tu#T2P4+zE>C*mA^{$1#iQCpLNL) z+}|af6Lcj&;Ej`q3?O;mOoN~8JD~f=^qg=C2E_gvF}cis5~} zZjEbmvB;ky38ni-#?r&?RPd*Tbvxc_O_rf`&s7*8fgGp=g+f~8cf~>+r}nqKy)9gL zl-5RWcGGPGw#*<(at4B|ANYcYL-BmKc%ldCYi(UaKw$?aI7G9W z%x5cr{BAblvP*KJMPV0)KsvLEpk2@3d@tP1K zI(bFPu%V_)LGwuiZ7NZd*fVO7)Q?jGInH^4L5n7;kBR#5-b&PKz`(v^wxpmtAkc~; zc)@d;s}rUkh1m`=Gu8W3Uj78m6jfDRBN5lg|Ne>7`3l@2%+9CRDQa=_FRWJt2KJ#m zH6Z-c>lA;nB3qnBU#Uh&l1b+SsYr5iw2!T2hS-H;ak?`Qz9^lC@vIm+|ow zn1fDh%HF^Ut|mq{0v6C0BYDyalavI6q-K*fns+<0tu5JUF$`8TmBBZ%r7Hcx(%pN# zqU1`&Qe?1A32MtIKOfLXD7bk8)NC2K+nlbPhI0EzTy~AAnBnP9YtC3TA7BGU#iAli zwcze6?4%TzD>kF6)4qqFp#E2l+L@Ldxf&2!kdH6@d0(NEh^^`VuWm=Ja%|-DFRdS?8KjSaKsYKB_b6& z@m+n(TDa36r)+SE#KL2M<9S)O=ASP^ z0x&v2wAhBdUVFjUh($==;zgm@`o`L;l_i#(%}VZWEU&N5FY?2^`~z+zz{&LGC49Nh zB^SkN-)9g>fbcc!O(sR~Q}X@@cIwKgSFpAT{j7ngqnlW`T7D1nfq^151>5_)i;>Rg z%@_xs=?d@;XqfqvqCrpKC}pCD3F5~3y5D|6_E@F1Q$W@1*T)# zMJ1|Zn@RLw!U6LDecAjAP|M*|9GSv)3N{rb>_DCxUwu6)e!qwr94f|sj*g}hMm-iF zM-Aj4jC4Z2k0eD}Er296f-!|t1y@EjRRR$0u^h*mWWfp4(dCy_xL=lL9j6y~zXLEZyxIW0C?o{d6BDla8CfH3JzmB!D3 zfn*Rqof|}2Its%cD2*`%HY;$s#0MbC3dvzyS2PcD6vtJfki@+t4-h+3DIq|S#%1p8 zrA!}%f;1Y}Dsh_@*>u+LY2$njh+SPMxZqAZnh%0R?}y|mi@+8XH=$_^eqKKPsOi^SPRk5VN9)?KPN8{kCTiFWE3%{SRo18bV&$f4u=X^w5QX2@Uu>4 zfQ!phc}UVr0?I#xsw`wEnPuZB380uk6B`(S;;SwD7F=?XJg%vz{rM!O#GmTSLKHTJ zBHU7r+aYA|lLErJ&M-<+xTOt+^GcX(DZ1U1e|8ZXeS%-VA|2b$g>SgR4D%_)tl_E0 zwjS(xp5tywm_Sv@66u5hy__xKT%05mwN?>_%69Fkj^ffXBgrl;m`pfmFaKf$n7+)Y zF#QnP@kK1cDN5(FRP%F65c*DAQOQg>rT>*g2bqPR;t~!eMn^$#i0tw(F-*Pia@?rR ztj+Whi)hg`%Lg%Bz_P<39wB^gI_52yHHq1WoNHA;p6}~t{gZmel@RTu|(|ReIM~h5_2smQP{uHHpKsPB40nX9Y zk~4|HHoi*c32&^}(&&@`){iB>b7=5yv6=j+Ac=#wPGkR0PGBYz7}X;#S~$hA2D9UX zcr9~-_j^}o?ZM!Qs9_Wm@;;~s*;me89&WP_+w*+bd7YhMRKOA)t=M}Y5} z%T8@)P~PP2xjwv%m71_H;VW8`uLm}m1%dly)fSdn%g5ycyxcNiwFW^a7T&vMQ| z*m43o?1g+>`#yuCW)|6D?UZO{VVqJekxF&nC^nvz;T*>>|nAar6nBG+C&#!ZSky&M;L2%qft}Hxvxm;(dDHO}JV( zATm2`7he1EOlRayWuZV|`4Y6RI0HPO3To%3IM{4B_YXF`k-TaPp~(oPoDW|c(DjJH!Ad@;zZNn8QB`uxDVExGvZHfQZgI5_N=869aT#p%Bt%rxz zGgA5g*?afCwvy~z__Z;S(m!)+LEy8xg>G&n?`%ruFI-*U#nJyAyA*vm+HCctOysMwslG?r?t;=@z(W< zAR;(+I6C1aF%9d~JdcA%9ue7m$+xsml&Fv!bT>Dnb8pi#*R?t(T-~SPHC%$IkeMP% zmYm)AqT1NeQFZD3R{GiOTeN4=McC#;qa>BWLOgv;sp&Udy53(vH>-K4bJte#} z?J2SU!od+ZvQCFTqTevQRCYv~@cY5wN-~5ea$ARNP4Kz2e`W*9mnY-VVHfvSTb8H- z!-bM|f%Hz~$r-$alkKK3sl%_;4FJ$ZF8~zipg61vhOC?MOin!_N+}c0IZx<7e2EY| zS_U!Wcncku!bfbS$_oNgXC1W+*6NbYoTG5O8aC9r3CgpNj~UpE?6?c3gt77Gz9BbU+dzPL(!b zytg7S^)|pYL^OFTIDPvKFDBCL<<@(jY9hY%ZpwWEG8bJmC%6<|FYKjQ=Hk6}xmw%l z&&XF1*=*?vlQCk+mk^@-hmG=*^P1qgkE=(>l4|dQfleoT`sozqt$6T6vKG=8^kA}S zk(87MyyF2|QBTcfz>+rLt@^!d7iBjLCE}o5ajwqfpTrrZEHAFpI?_98aI-j$0qO}n ziFD=Y=dg#U<22_R8ub4Dm+ywN`OVJHKU^ZS5y%iO?I6@|10U`saYD)05Fqr%Q=YMJ zs3bxSYF7ot8Cnz;WSt0WSiPK{VIubrM;-j@A>x6{j13NmbBLnGil>$J`}w?x0k6le zS)A84Aw>*WeD82!3siAIAi0nZHSN0JefPWXkAAnk$Llz>K7`3nnHMgSy+JXP4qJt5 z55raUb~gm#FMWn&WU4F)irZC4yrK-Z?ONE5=5U}~1bwbYIN^dCvINJ+A+GVpWwvs` zALBoqkB!p&ej-hMqmY~03A@JJ)@7%!nx+;ELG)#a_?H1T!%|cc;@vD(TLXV$Gwr&| z%9cIo{E}EC7yqImn&ZAt;L{PWBV~<2JdxwV;Jy;q`Z&QImI3rw%vyp!)*9!DA9W>f zH~1aI5x~3Np%N2}atWJkrQQopJTS;5n8#Qf%D&?&^%H?%nBJY>A3P4S?hWk7jyflJ zBllp$ge{JD*qzcXfl-02VQkyy<+tNv_|CS27>KRoWv?IU$!{W~R?+G5li~5T@`u*V z!NJ=ZE^Gpw^B{X=djj|w{JQB}U!6_*CyK=b7`wBJbX7bCK4*t;%)b8~4pCf9E+gfi z!_?_0$`CvYwvrk@b`*=8^f9fw;3dA7Lg@1m>1P;y0rZJg%R+W6U=#=kj6?jHhAB60 zm@>0_aGrVoS@%!BKX}^vkACluk9z-fwDGX__RH0uSPG$nV~ID^jNb#RhQPYv3~_dv zz5wHJBo*5qbnau*(^cShDZVE^MiE_5m;~c}aDI;KPopF-J4aT3E3xZ9jbe_f3jJ#q zVEp#gbL<4TW#LyNO4RshFzn^+J*a!;-n3mR8o5<&MDvulA(K+Yo{`;Z!tQYhnHg49q!RH+vgjh*wu@B=FX-i?k2 zI8MK3FT+B#SALV|w;oH;!DxwsXoEClIE8$G|JNYY`x{5Pk40KK~J4M+7g{5;y)OnA!^)R(%xhN;TC zGWONt7gIOy;ma?DJ?iqJdm*f@*fp#w1Y1SSh?&K$)5``8;Jk0pknyfeKmlgxlp`9Y zbzhEyu_BU$N5|YQu=%)B2~jg4uav3V1l90#UJ&UyLDs^;6^QxhJE}%%DG$?K?`9Y< z?B0{*8NFHD;PWJX-3~k8w;5oi1?le=9O-}-GK>SVJR5}fU5^-rx;fkx8B=pp7||Ug z;X*%zW5sXbfdXm#+Hp9<<=Ch_3~!fgmO|BB8i zsD3O9Cc*8=q>mKVTBZk)82hS8(r*$O9(2(VDi?(K{w79g!fj_ z(vYD1#DKRzoHQ9we3E!7(>Q^cgJf&5WdeBCqK_rT<^%{Vz!xx>xglI zLsoZq6`XomY{6hykZ{qE&79&3IAQAgF`hw80>=1B6ym=qv>Gf}5y!P0x#LV$y2)1> z%22I^^(?nrCst>0N2{+gIPB<^F3l!n<&QH@w^1{{GeP_}MS~2(P^IGk{yKUOs=Zv-KJc z{=V8^Z~One+1cCPM}vQ;HhA^z{;L<;ucgI5W-V-5+t0#@!?f)`Wo>;v`$&xN^cY|XVE=aK_4A$W7tfAf z|NAaM7RC=R{8s2rSZR3mY-j)Jw=Z-|fHIXMh`yW-2e{5WeW->J%c2@eG;lSQGiJJh ztmAk10i?w|>9$Dta%_QjNqdYVMl3!Ol_`iVHp4?wwT1iejwwk@p&xy80p80A{2**8yY@T<<)Ay(Rg*VGs0`ijd}V zbx`70y6VhiSLCbcDF8VYqG7C;A$aA3A~h`ZR%>^rhgdb%|G2W|(4smKjc&EfWBDzZKS)|^ zIMV;xJNV(&v1!d=THqW;sS#s%SzRYCVwE_aUTJF_=-$sWb}PHeN<7y&)h?zMa=&9f zO{fN8x~yCY2+@!xky!F~;!`Ubx8!m#0*^0g?nP5Le9Oec#igC0EJ-(Map&Y&<6?|- zQC|BQ!ZTfr%AnvsY%n>p9PwKvjIOGn1@}@|Eg)TfnJ8_c{rw#a`r^>rB~Gj$#t$#I zrWkRx>-SqaQVlQmBW^8*QI_w<8LVrA(M5{--T#{>=Y)HhM_-d(79`g*e z%VPiZam6!UAAE9 zdgYAi9FFBj)AQjeaz`9hF?(xh+RGb>;q9%#>=*FSUNF=v((rIIGNI9 zK1|loOEP;v%*$sgF=J-JiyC}Ff=1hXC@E$#Tg$zZrV>&)9iD9DY5`EF*ys6 z!H+qR39>V#n|xA5ZV@JfE-|Y+O%OWof6x>xtXQ-4+7g8Pe2M0`%;!T;&#K&iD>$i< zMctC+l5JC&jIyMRin%Er0F1ezpPigiW96f<9yDb#m9t9;6z3TmvZ9Lm=gLd)gas`y z(LPJ%bBEnvqFnphU5Y8FfF5?QD*dKVeMYj?MVDRr|{_LUO)naeG1XzV~Jru?cXZVuY`gzMA%E8cfjzyx;WeyEDlgCkaSQvLUh}#~u)+C)=Zl zAjJ@vljrcJ;aLa(%Ci^%WP#!o$|AJCz1cxDE@5KG=$s9Y0VzZTh>l8fD|G1p`bnqz z=HeD%Z2?t>Ne|K;;R15*(41~M_c#DDV_VRm?Ff1?2jD`Bf72t=flCrpMP)tD_u4tX1AldX6ay9EAlb$0XuNK_tuLOBSxnI*>2@SRszTqU=xq;@S1p z#qb!ooVdaTTG>Zl-Rj7g`&ILESs%2+K_8W9=+|rxK!17jIa!tPLJr$|VqwZ!!^qTB zA_c*=)aNN1w(r%#Ei#LV$myP?2(lQ3tVPVM4w9A2BPFASh~bFRk}Jp8XsbZTV}97$ z#y3ni7O+)jpKYD$%x&1Dt2Kdq*h6m| zwe9N+Uiq+rFtg66g`rQOj!O7cWpNU!Y=Bb8s-$vu$=#|KlJvKdd|v&YA!ov^7S87L zwvSxKq~Ecy2s?v33a@PyaTw(elt*8XoOr&UhJ+I07So<1Nh+T18F$~lg~Erwj;{JAHKF;S^N;Yr zLeYb^cYm{!jiEb2jN)H{y0K3Utnm~#Uy7K_)+2E@X7cLC8#u3m&QU-)c7aFs&^2j$ zStZ><3%1>jRGN)00Ax3RA%j}VH<|HW*xY-ea_?cYN;`Fokb?}kf+5N6+*FETFC@y} z)figEO{Ndhg_=E(4a}avfX;QuH0YhR3Qq$fZ|%|2Eice7zvpS=tgcKVU^a3 z?;)j0H#+f5r8r6_Yj3Rjm^0cG6R_?m1Y5LqUT@NEA`VmY&F-bUSMJk|NyF^PRU-oNRF3RZt)1%k^MV zyPV6hHJy?Y1O0Ca4H)ujPlmb)Zu{yzW72<{mK7)0Lxnm{hG_ht!xH35h59=}HBO?~ zxkgDq@Fv+T5xoyYsY5}z!ACw!79h%*Kab=lXkmP3f-m3>q%rCZ0T;?yokIm>0?&Lt z8DARDsce983n7t@>!@*|*#S%+G-&bsG)gt65d)+Bt< z8H`K2SAii)Q8W-Go(@sz!ryZZE-0Agk|!+iGwJTNWG*lk2rgpJnlVB`FX2PwP@?f@ zLXOO(whs*TA|F;z0XESpu9VtrxuO_LDTBop_7Lp6L zba;kaGzL*?`h!Nm!v2uJ|ME2C>d}(aQ*Zu>bR(G11QYiYaY8T05g};TRNTzD4l2-* z)0Xg-45^^9{Vdc$O&vW%oR*KoiLz^pX45u`>kZ!pBr;Ep(AofpPYI0s2+Tc!0Bv@+DloXh={RHZ;$*gE<4)iALy&Ex<1HC!Q zXEOMEMh4#T4zI%$aSBE&tUQPS(u4DA*#JdI3b#2*(=Dl*E*9%~Czchr zCvDhIF=L{ShZ;-{{orN1_O1Yui=uhfNnY{0^-7z1sl`tbpGV zI4yd9oYH5(iz@OxI)M)C(D_@jcJ5YbZbG}JH7m64Wa9v7wF0eA?ob3++a3xmmR6t8 zWiGr^yUpnw{J#lG!2=588joyv7QHL@6cber@B#G#aNraTm3*uo z#3tOZ&?f_U=4yA%J=J+MN-iyidle3PP>U-L^wR)<9`5>_^}8`%WQBOR?KxwZSSkIv zsVdE%4eJ{mUQ(A5H$Fe5Wy1f4{ox+)X%b`%@6ti|Vfs6oe}T&sM-5YdvT}v=`-W#( zn}p6%H=QGa;ELf@*6_DvO?c}_r*0is9zXcy#&suJ@`;=JSf?sfco&gn>G_-Obvx?w zX(u1ciYnX68M|=PNy!E(3;+5X98*a==PS(aoq~b}xOkDWe=s?GHntGNL!i+IRv z5|nrB7NU+wFR@?WX^RKzr~kY%9Z!PGRoW#;G8udvj&Lwom5G8({kRo}uWXTLz9cI~ zaVp)z3_hriSjpr-&^pbE$Uju@RlZ`HjRngk?Is&&UKUL;GY*hmQz=svp;lQy=Ly2v zxJ`F_>QpDNZS68&i*7eI2bqH_C*vsC62NJjmif!3RR+_KvTBJ*ml&{zyu_doIZftG zY^n=}0CYOwPmBO$0(`+|;@Z8pG~~Dkl`VH1jp5sD(+#jW%(=Y*?gT^gwZZ3qc4Q7N zR|!{Y4yUA>p+tLcD=>eSYg*vCr&E|n#QsoD3!Vgv5$+c%m`WU~t|vpL@ppi>zvp#IIIe|4Ti^JCZo z>TE$#@zqx03$1Gak+ug#Pr7|MJ{chHivK0Fg^BZrn9*#+i^#KNc-ku}&;-GQR%DJR z{a9wklMczxuiQ`0)NvH>(9!ao&16Lfo?Vdf=xPHj-W66^lVGLH&i1nURqqLs!?ZSl zEMGS+177l@L>=feSA8bFzZlAT)>cmj$3vvs*l(=;0ynL&U@%|ssG7vUXvWA9QTYlp z?-d_H&NYp_>H@@2HQgfAJX$>#k_`4y^-GMQ?RrmKK>V{usr`jFT$+%#LDi1DTF>XXijcv z&=yN34DQ`x9{yhKfj)0O9^t^ihx9|i+;A;&+G}<(a`M8jM!wL!#~#PIWg8x7B^*N1 z<0(mBSZ@YJIlfZ7&*0&)7_Bg33nZU&03&;}u}q~}iN4g{23E!#8rjXTZ4YAQfZCT> zu2B!>6%!7Gkp#r=J&BW!kV^5l#0jO|qywd{6bUf0Q&_R{1V+Sg6$J9AB$hzm(K-pP zzyuP!WyIi&L1*Lmw=CWG<{VBIK56G=vB?~6rt4fhGXS8*`I2?u79SUW^|o<7bw2sI znnbiP1>IhP!%(=X6}2i{3z}#4o97hcMu`!JC-Sf4&gsz^bu6KC0|{x1yo&!NBQzow z7Q$yD`fQ6cKY~sQpO$CZ?2_Bi6HcokW&w*O-2_(GseJgDiMc)noL18hfxiB)T$;?{ zt5>7%hG)2SVYc?nrAr}bLZ@77bPhCErzv8MseRq~MIq{SS6)!6p-&oQLHNhPG!!nv zO+<%~D5p@+G6kY^6&OumU;4=tq(V`@kw(&$E<)GO9t~;$b8INjIYZLBMxhDF5{TwR zOBL}1M#3u;{*@1FqRmyOWQsVJ8s``x>3gBez({@~HFpxoYY;$OLSqdD8xYPEbhySu zNc$xe<>c&aoJ^ubSTH-C!(C-(@4PzPdDV8vIZ3#Ve=gCunI)rqL3zx+__eS@kU=QX z`t9D+ms{T*z1TsK)~7G_6_fK<+rQgpO;&(EV>q7foH8N$7SSwWOTn#~KPG2^%PDQi zNBc?Ic>Y%Xve^e(Bp26CU~*C0PsSFk5kn#GE6`s4I7}5?;!Qd?bgJs@4%~x3=AYrZ zrO`2XPunc;Ew7hseSb0NPey~2T_%^eEX11paDJ1uNM|?%X4=1!NK@{=E2-=Oi4a0) z2Qn*;!)uB+C3jSx)@CnQ}i)0#vX<-q!+CtH$a|v z!i-W3K`S?O(#MEG*rM2VEDp*K9_k6i(2-w4c~814tX{dQkxltL8Y>(i(|cVg zwQCdJf>R1||4|%Vrx^g%d%{0?haSp9Mp>yAP)pm;>_LY%IHwIbX7d#NyzzN3wQOQC zO7@*93w!7hSY)|<;ZQUMp27&2MlT|+DFuM+*@>EXw8XM5L(*E@q+<2d6*y#A7rIba z1v=6zr(iVZld>!l&4Tr2qWmNiGfstpNmjecx7G^BnO;u0HNN z!nW$t=?gpHOK!KIR(zBb4C(0JYW_BG6GfLCIbzcnYHSN-#itE@HtF2HAYR1}yc;O# zhD8}+iG40l8lN<&p#t*wRXYk~ZE(n%(AEpWG&EZ&RiS zYt9;~$MJh$gwoEtJ+siV|*7Nmu|w|I|EqXwJo1G67Bu=5b8V zawL~852-XefE1O7;7vFcifzwi^-AG+~Lc zE67R>&$yN^9nv?Z&R)NK-sZrYbf31U;?%7Tcw^xA1j2Kh1ufUQn(4vA>L;H4F@!P4 zdMwvx=binhuW=a_8AWFIY;Hc={`Spp26lI#8ctav!g zr!j?LF`AvObz=$&s&WEPuW47nYS4PKa`);N8^1Wk9SR`6lPTs_3=2!brW6WGCidFq zCT;=$_SOD2rUy-@+?14|ro3$j*OYVy2{o8)j_!D~YI_5mEw5+`(W^{=Lqy6k(9O(&$fggFsW9Pn;}m=aZuc{Hs03Fg2| zrxMyOCc~_#&Zj3=FH|e90`;Ol^#Gj2-$d5Wqay395Z3{a;tH;grrZjOv)GliMwe6zNN5H#5h>Upc^)@C(3#^ZzEFKmzRz}4@J+g>FaB{# zBntIDiZI>$ae%ZA_=p*QRxw+wPkHAGt`G+zU~vG+3&naggrt5U^ecZ79(efi{08T5 zU3;-nA{c$8GC%KtX!%IoOyNnkx1}8T_WCS}fR+2NCpQ%7xTaRImVen5=N$#X{f#w9 z-1Ru+jr$!&1Z%AvR{>SNel%uIYviH_zFf|BG7p?gs&OagW#O<*?mD3vFs2!?)=IXgGQVgohh zNGZ@$mrUDg}S<{ zk=ilfK&S_Fiel@C2-uMb=U&vf(Y=0?YHA$QWU9}B@K!@Y&-;64@F{WMbfl1-gY8pZ z*}NRY>b)2rHa4XN^K2&0RFfMPot@ES5%}gH*A>iRti|*iFcmb=K4sIu1*hx zkdFA)-7fY!0V+4H6b2d9O8ZP;CLKwH!S6Dc#U_r8fGxg)aELoZuB~#44(Qkhl3VN} zCtEkK4FPwM`4=?<10zB(~YDwj=bF2YixwQ zj!hs$NwylMa|mfEL!$@mj0#tzJ_sra4pB11Uz~Rg!($rIL??{r>B?N4x99S0Y-5FX zO|%menHJXT>1x<`r6Z~vZ_WgBEAT`n=bHu`q-dr~82S5i_S4D(q zxWvyuIL?sxjlY;{huDrTz}uiL1LRg(uY$cKT-9xK-}FWiY##Pe_qjkVkBYgphi9erjN$6&gl>rnE{R22NmOh&_HCz+PO$< zst{&NJt_5dmW(AkI%ly&2tex4Sc{pjn(k13iWm-B5{$xs@V5;czBfNZL&dQ*M9rTh zxCXn2@w^akbIZm}YBrMKzwSdrW6at)5u^`AL(u>vO%nFk)K1Ao4XuBW{WkU=pB)f7 zKnagOv7bb}vW6lalb#swii86df@{@Jt&D{~5F%B{R|k|23^*tK2)U%m;X-gruirwp z9DD^<%Z=$u&ev>BZs=2YC! zcZIkUMY2O5VJ;bgYnNICGGTdZ-6Rvx5gRcEE+7y5C@5tps^DxOdo82M2>K`)!lHYP z#blu10tymho;M6nnWeqMW+cvWtd{1Ue&(hv9;(Dmn3Z1n`%HJ~g(??>o%r+Z`}q+m z6UN}kjb+T~9E&QEOdu){+o76`lDp3v*(s(;XodS_~#+7RXP#gZZx=s?-J zDi?qmfN1xc5ff)`WFSz}Xj2rzoSslv(kRcJkp+}vs4JMl$k30}H~#IzWSZfvy01~bVDP>HuUqHhgF&0EF)1ja1hpA=ccoBiu6yRD?r zMmkV5)}6?ZUEDY563k?EdNZ93F5N!@Rf?|4FXdHmN9~Tuu0cXI8`KSPekkxxWiUP~ zDKa{>xr_9s=y{>^vw$dy#0?!{Ez=rGC5B&TvMwrGBu}|E!xoCf$^-y1ebFXBZgJ|Q z6L>`yCW@u$E}<8yokD1i`}`phrVL4418D3dP7RF)2sIC6!pKk3VLlqc}#lkysUCvjt+hd?)5XW?%VGgG?H*Ng#7(q>;v z2#qHV`AH^<1LCd&wftM1V#wmrfaw)zb#T%EdIWAZVhybAxl5U?Z>OS?@ruQB5m{eh zrZwjp^yhTqfhEXFl0*FjY5tOEoqVi}Ou6#%P+uMA3Efr* z3OEhnp-vU;n%EQRSg=HgWT;c8!zKYujX$U4&7~*&jSfI3Q?M*plTo1%y-yKtRWJ|Wi62jrQ5u}=*8SDHi43thqnsuR#$bq?vAK*Z ztphJ`@VtiBAV{+gU5QsNo+M#lWU3TI zV`gSp*e7}Pt(@%f)M1GNp`kw2{1~N{gb3>+%jphq5T+-PJcqp=H^Cz4imMVUOTy$n zZCoUjd#W8)VcnK=8L z^T_Er!8DU07B<<#`6T2yhJ*`PD&^s-^w8NV3T;jMXW}ca-F>>^4S3nk>+>ORZnb;w zBxmAsj4a)Y;rjuuWIpa+Podl58u~u2vp!=a$;)?b;8LEAmHWG}W2d~+{0haYB+P{D z$;&<*__*r61CmBMCt^>7ACdUikz396#o%~=LI_Aiw5AjAC9Mm#@&B;DxCFX$KAT-l zHy=Jc!_86G?>6xMp|^>DKr=&HF-U%P-XFbZ-}f*vY$v(%C+`bZ9Ts}{W;VPy+!+nO z8(bib5Qu_*>8|an9UZR^oWFG;dhV{)c~|Ys-Dwfp!3hqQW)FgvjYR$AxS-kgqCWWF zfe6!YNPFpTEV+hAM!s6T`+G$?-caCo%7%r8<9kI@F}dMY$9Q`06K_V`_6y-1ta#h- zWhmqDMTqvVaW{J;3{E_`Gg!QyDySZXMmk_e$8h2RFPhP(Ub^Al` z62|ZPANs=!3EVMYhY9iT-e4pt-r)7j9q9CrkH?b}OB(~(PLNtX8eU#s_Sk+1nRtV= z>lyswXBel0zXzSK14|gpoE2=0Cua{&#>WpI{jxVj0R6JRae4B;8ht&H*!U%L$4>?` z%x?+?b~wUDAV5E_TO1@E9z(T%I75m$)-gTD(H~3-qiCO}*Av!;A^*C>x*RDt?u&{f z61KL#8H#446e!?3q|B*D%l*o-_tI}E0pMkDf=SQTTE{*q{j@w)t7KUWOX9rh&(6s% z0`uG4#5hkcF2tjo=zh`=>J;SWW2!)waw?B`eKHrTvb58cOYI^1fyGMrozClVNBwuo zIf)h_UogRu2#H(_+_Eae z%j{fQ?tZr`vhu}F=Muvc7kvH67MA-^Lz6Y*=^C*>yjBW1MZ+vU7cLln65^FsNh7x4 z7MgEEzdX24i{L))t;Q~@mGAr{Z(wXpuD6NlZeUB;)}FE!+wjQ6z{l*FG4#8W(U!e; znBGs5O6ugK0oA7``$+UMdMt7AAfiRMYHfa?XL0v#8CPscPBLd0=Kb67^Ki z-A|>soVEw$gsD4?BY{mH*UtFsLCS1^Ap65G{5<>!uaotlLGTn{H_NqwIix2o$Ul}ZCXCok zwgdpxxT{s}pf^V8Aq83Wi!@TJDW?3y6c}t7tQs}{UXKllqd`av7AI{2 zqZDuQbC;@NWLCQxede=~{xU}3AkB8Hbaj$F-18H&3xkJ3w@Bu2(WnOmMmf8(- zK)w}EsmOQ5a6z_3)!URs+!mka?1lR-65jY6#~Hxecn(}M*YN{9JTNr=o$xDq984M- zH%R(2J*|;voiw@aaAd}Ruqy7ER%~0LW56O{keQM!hIMv(*^ZN-W#}os} zzDwBznm$ONHRA)qlsI8M{vN2(?JVv^+}f1w2xf%`BB2$P!n3u=rc%fz+zV+@;)J!tC@O+MCl35% zdBy<7WB?G?RNO!~Y9hoy@DuPE^(n^<>!fCpzec{AH==5aNZx zguMu8^;>(YIB8$WK+YB_W=Zy?o|v>nR?xNDPaFDL{;*~w(^N?&cTO$3hZRyO`;LidfU+MfyE&`+|+#&p}3xYS-R3+wM2i1S%f z3Ja?O=+=`;x?41@CF64AkE4rm|3o5gSv+o6ncboNcV`5z>?v**f?kKP-Tuwx>3DQ= zxW#bd2*=+JpTk7Kbwm0eZ6q&w2T@rPR)Tx@AEbf+^AWPtK;P5J@j1ek&%&HSi0?UE z&+#$9hJU47?8H86zvLa5Pp35gGrpc(UDGj6mx8+e;7lJ+`jeYy!zA?)Qs#7cCW^*d z_XF-x8c%vB!%262qZ@;x-Hqur7qWJF@~gG$(eMw$leMlD*jY!U?$LwJKZ4hQ>mc$a zhj@MwOYz%7O`ce#kDC_)VrCu-49iORSGEcY5ZB@J0zBOgq78&E<3D``F6Jd};iB?S zQbC6&I0Iu6r@N%c^4|OD^<)6};CRk>aL zMqZqRK#U3m@HX3SSclKY7eM1JeaR?bHd%8NYR<3a#l()poj;r&0Yla_C7*e^Hi`uy z;{$5O%KaO|t^u2{XX`AN#DX>PBQFn?oIr>#MeK9oBFgy(#t41M9smt>%oIKognLlo zNg5c9bT&HOhh6;dJLDb2w>EsW4;HHq$kBkD2%6^5th74WzPy^V!UggTs1P zUd23Kppdc=R|mh?LuT`Tv>SXq+&Bm%E#{*!YU zXI&`vDCSH_o`Ahvl7r69A;`K2vj56{H4e#`7PbzFhyUXj!_nz@4bo{kIJ%x(1U@Vq zibEr_H+Tr(Avk;VX79z(?$g)b@hASX^YZztqwRltz5SB)vG5RG60Tfa&8&qseT;+` zc35g5!HEcdg;|t_LYT}R>yF^^L!ue*&ZDgO1f$24Ck>?%pOT7{-9Pi&<@&?$U!c@~qA2$II~WTO*EYUfU;SwaiOVVCe=jNM zggYQx6pz52yPg!$zZ!1D`8;H%*Hjp=e#L7ege7lSHwRzY13b1SzmO*~GH3pH3c zG7AOS7T4MlW}gKS(>fk*e0jLB=FkB!4>!*Kxc<$5UFcvtu|!F!Q&iCn4T$21;~?R# z0W5&iUo%vU1ula%Ii@PSdj%kpWUj^K(?0}vK`zZH#!^eVu;Bs zn73+MvT12w0%$MB$NdZaY&usY8T@~~wPdDxXQK;Rj>@LpOer&4_`c z_u3XwZc&=`lN5+WQUtwjx`=$94}jcmAbt$MK8`C_`!c^VR?`D`i=shsoIgOJ0DC-{G1HFi;W zV|sAqh^TUb)(X#r#z5C4nPotGKAwb7g&8~T5=JP)q9#&sIpIP|DWc3;g)qzK;!m7a{>h|kOL|3vZXdnMjH;Jh>0x&O+}vE z=}-!#VldeA%b?gw*LDRuor4O>9nzC=3l`Jy-?1)_fdr{HP|^@Ry13~|iB&uH)P$Hu z;3B%V?i)dy%=5+gYo@AQ#h@3tJBUL0NMv1gdXfMSzG$h&7no(pbLW0{`cbTdqwpJWQ zC`^B9Fxc~62tR4-BQ;wJ=>|%MW1CJ_>uYINTPK6dYiNe&{SPRA*wG$4IK#~WJ5zWF zhpgRTt{zSjIjt$Q;bd^{uRbSKc*rK@Hp)mKuX)#>(c+GVWp|}B@$xunnz!9nhs{xj zE`^1saCQ5#1suktq6XR?>u?MM|4(!TABLKNo{-Wf+yk!HQZ5InXSn7){Q4tm@xkHv zU+$Hl5yXTSd`j7EzGYU`bP?2hkz&|2B1^|*wV28i_|!2iP=}<*EP}Pqo5nb zy2u{rAdP*Zj;Xy0Y(J$`k)!VC43VJnET#p7WF){B(#m2lzKuML964kj2`T`+(xsm^ zK>9fjDHW`*yAe{Qgu4N~aBPiXxwZ8Td|umyO^Ah+rV}~Y9F+rN|C{yF)CeqBAYiv! zs^Fx<*{}~EV)K;qgu`{s z4#jLwAwzOK6dY5SQPIkD(!l1uGldZKJpGKn1<*cim@PGKQQ4jf=fFiNlB2?`Y{S$= z(_4nvy}X4+cf{0TH2Z_;rfIOmpi>Zg_iH%}{e&d1Sv2ro2*KB5K3_l7_GG0*)N}&< z>H96lCMWO=vO1;kkssuED9*^qS#C0Ga@EPez{bEk4`yGfW+3_@&q5x2&2x;fVV>L! zre8CaKz_XahPH>~B$V7t7)L^ncYrN0bd-O#E7^iZb(e(QE#72Ew$=* zW|JcX3@(svv(`O4{5#sD8~k5pC<5AZ$Xy7gdgl zCQ`EEI4LS$)Y4p`O?g`XdNy{GF(_WrpSd)Y>j~=k6T>sx27B7<9_a5F34u!WQI4_x zd~Q@l?Um>geij5?aI64#I zL+~N#s(3or^WP4v9Htl7XT#CquKZ^kNp3Tg%BZ%O4Ct z&8vn)3o&bU%&fHRN$yaFW~@gSL$HG-c497DX7GnL18zlxWwP=n^3Dx>D2{2R>EYRf zPO&L4uJVyEUAjvibYzD3j>`)V%N;>B^(hyipTwQYq8*6?Mzz>RGzD2ONc%!RVINGR zYfi45Z%~bOA~GL@SBzpjgjvqpJq{8Y($T@YZP2*UoKfVx2j?XEkm4lmOvEsUuNRByZ*bZjB*B( z05w5e$)2Lf$#hEHn@KjzS2CSFaY5N+)C7{)Sz$V$o=p#_Vv_gIuD)hJPwtnAHL6bG z>HV)CN{h}~(Cr(IpM$J+0oK{g@3cM(%tZOp4S@B{&V5VJl`X?TwaEpkE9y!SNOG3n z?^w}vBK30FjYs@c{d$_wOQhj+*`3>x&dgKi48=Tl3!h^S4c|;~I;Tz zL_rpg3!aR_hMRT})ZBn#VlM;?pyj5!8@V9dqjVl8S+=ejh6zL*!!|xQ#4C{udj(IM zCix9c-bg~64Q5aAEd5APa`haw(?*KOX&z$IQA|Jl?$!Ql8%NX#U%v=J%#z#%B*`ge zuhvq1XmfLSZ~N%^&Wr7%XRm&E`Qp{nXMmD|1ww}qO9sbq3Fj~5SF)GI8+b}hal(=- zGfJ#C8L&BL;y#WUh&XQVq9F(g9`gl5&>z2_$^~H!jf4pVy8Antx_pgJbh#>ZQ zi^>8XrrTBk#~fzvTM>aJ2Ip!Ro`N#`xF=pK&Esb8@N8o$kc&1HcpS{tth?Tmv3!ZkU9Sgwy%GZYir34mE6fO1o9v!X;%MSc)G)2jQ!E+tp!9b5Ga`|{!}!}=g1G- zHV4pO^CDiRF}=F7>vyp~CFc%}N)B$tPabI;EM>7B?8Cj`$g{N4R~8~?8uNqBuO5BH z|M>;~=a&q<$bJ|^2+x$e*@8;g&SS#x-Z|*af0yS~tIw?UNK2V<9r)|55g-xnj~_ky zB6{vBEZ){BSeoEeq56k2&k1=;9)@Fg5S8*kG3PnrKy}n62Z-M8D3;UP!C&CK7d0A* zV@R(#GK+j7v3UA^PB}>wy6pe}4!2J(5FLk*i^cP%2oFk(8A97haRVdsLWvGf7SU7M zUBPEa7t|w0j9zo*QsvI8T}qfy4E@mnC&$L45w8pSfJ%0$dKb6{P#6`z&W||cT9=#y zV}=Q^LON{@<7*LDMeA%`pJ<)_k(N*!;usSv?x8vlJ-!?+_?l1Lp>bltqYVYny3Tms z5h%kVcGr5w#iXwz+$|1EnTj94A$mha%U|rgGOcs_& zxD6PLZ~|;p8f7c&6tbbQXmt{GlJ6T$TvaT1yO>fEeklwonNz#zB-6_5Bl^yB^9qy7 zTI3d#PssN$MyJ%sajo1o*XcBgt>x{!zhZ%|hF2)ZMsCGik>vF=sY_V^0$Zwd9}3u} z`T

H#P6TNY%o>A|?!Qi`i4bS<49jT6ZhR3K+w1;>}nY`2@EI&jvK}xDS*T{dZ7v zuv9xaG&iV|njVWbfE>ixl8*K8PL4Z$`*eT%<Hn%q_hcRhbe|PPTxF>u;n(xyoF~*1R&ryCr?Rf6Cjbf_HCal zVVta)L;!Zxvflo2aE!oXRv7#_i)mfBWdQHL!}E7(?1#8g^IQ6N3*&KEy-^*^JXNXH zU)*~FR68Hy@X(vd#kV(G;=&f)7oWK94Sa#!#8JbHa9=%7&~HyBxKZu?*2S19XDHg8p?-A$ zx+$rKgVGSD7|l-Cy4pRC`=Sa2^GumHYUdqWm+SN7q$x$r@>GZcr1Xcw$0R!r=-8;} zR!<SFGAt&Xn`gEJq{kAKyjbv?og!?F>S}>;3pE> zyQz{e(z9_oy*PdkgHjD__y;`HBgR6aD6(+4AFjp%LAw}lFp&eEB;?_&hf|8vSg^hs zCz?a>SG>0mI+n4&>#P6bSPux^?(id|ZfYPb+IUkF8AKK}(S%h)7zBAEXE#OGsCS{s zE~4hA)ef}XdWRugABKuSQx(Tzw**6Q$^xguh*?3>QnHWJU&4j4+BwL=du338X3*)u zUp4IXet>G;8{vQ64S3GXP$%h@I>W65V;c+>T3vT6B2nvZ%pg9mAJD1<{PFPcmogY13}9inzXt^8ODf}^qNKXot}DDQw3CDec`XuDXj%1E}J<=KqS<#z#W&8 z8>8()HyKTm8%kh~_^$qGbls*n`Sw**C$nuh%}hbE2_cCGUhUEL2#NKWI@@5zHui|= z9T&)R*~Md1CeTQnzLSE<>4`GHl2FkN4j*!lka_#3gWvaie|*&Yr;WE##--ac z+lJ-e$l+uNi@9MG;d;<}IiM{_#%d^6`1JImPe$pqwYfQ*{(#yG&&eYZ`a zXsSyechrc1SrAIJq{F7O@E()yilCqmZn0Pcj8ZS+$~U9q?}uU+(u`Ce}{?n#h~`nK5i^u$WvhV&aVc=ZnYj{VBBa^YDuFq}x$o1o0W@5+=EYQaKV31hc*#A@ql!o4QbViWPqX%8`#y z)rZT1yJXVYH^&z}XB+7Dne&=#Xx@907-s$xCtqONCoxN*OC$j_AYD# z0%K2^T{`6JF&8#u-3Xa~*pB1(C^1rt8_8zHw|nsUqv+r>kPKGDCCO;%#OasC(4n$d zrLZt!L;=iu$@Hv$3dF0U_Z=_wQqL62JSvN z-2Za@r_1b9ZS%cUW^4A~d~1Fm-pYCjD4X6w;Bu(DbJm zbuH<=2~$lrmvAzvPc(+|`-FVj81Mbg7Skf=>7f9it4DM_x{Go>0(9ft6oaK31rq9E z<$=pnj0ExUp(p8k5RACs0=XQ$9&`_0M)$?^_kRK`z(szN#4zG$E-kzhecJ2I`e)NH z$U2oKpOCXv;#yT)a3Jtu;1giQy}NTc!G1qe4!8uk70*pPbGuknl68#5CNEn!Zqg#> zmRhkh?V)6oCW#E`=CfNLW&yWVDNrE0u=P~>GzFe_Ppp*_zHdZQif$y4PL;jpvt<~mm13dwe4G42uFI*=E$cTd}#>5S(5M$T* z89+2qXEBU$_`qOYp#Fs3ci+J^BaeCU{g+topY;6znG9$XJC%~$sIzl2w%5iFM|rBu9W_$4G~1g?6&q@Bvn;IzlgQMqUgKRK=u z)U%l&o@l6;=*H~bs1x1eWOzz)V|E#@IVSf+{jZG;=pH?B;iO9dV(J7<#Qv=?&o+Gs?V=i} z5vSm?r**N>sy#3t=F`POWDz8qpb@{xCr#^DOj?X;fFKQpwNZbKUREBPk~p$gx1iQ* zeP=#Pc$4UghF6BAOQ(n?&^jmzM$nOaSbBTbx~lyoww@3tC&v&4lcWQYb*z6U&g1I| zifE(88O|9TpYzxiZ<0q6vn`s+tvUtnA(dLDmH{UV9hQ(G8YJQ%6}$8V6_61uH89xD z$D!uKN`(r6rDj;(OPq#7K7St^%UWlv3&t`%@3!@%t1x^E3(PLG7}@koREc;hP<5(M zOp{TQ58;4HTX~vSMi>Rai~f@h)6;{zurjff;ElOVU?Wve;O^sNjBF(-Br*s2d^r8P z!^tT8v4Ht}woDR3q4kJH4fFnBM%onbu}8+9*1+(~r2e zi&;a8hjRVO<42xqWXJA&UbyHN?;_(XI@H-o#w>|tt^68Qr|>nYN3OgVRh(Cm%d{VW zipEGe=-mHspF-@z5OhgVcgoy>3%IcC#HeLT+DT3!X3S{%aY*-Q$|O*MMyt-~S=49B z3<9wp+&maP`0(K5L3aa;M(Xh}k}!Rsfdm9CzIpqNpHju{biYyW{V}|4o$gI{)7gIu z;tQ|tdFUhJPE z0cgYojeT+wKjF4KgTfH#J8$_7TAmC}`_~t<%@r;zK&co@kZ(<^i{0SpumLgb4s<}O zG)QVD4&+;;nv`!9p&D~Eq)vBOnF!vEYpBmRaO)$6hF;*& z0k6M8fk#2`4;Vm@(=k^G&R@f1(bpHOer@Qjhh3(fHR@V8{3L}PRl>hv$G`37A}a-yIiY0Ea0{xEadHd8)q%$&0_ zt3%{t5cKVqqRprgQceKTcn85{?$0^vQCi;{^aLn$#tGnXl**V`!bP|N^YvHJj}`K` z=C`S@ak}%U@P14f(M>8Qvnd&s?kNm^@~cHJ{^)l`*0#fw_l zLE{4uL#nsn4>7{Ht+3_vy7cx5L=~*LabT;x(=&(H=kS zChMS+Md9U|%KUwD?>08P>Bx)PG-*eS_qxZS27Us{47U8EcEH<8n@}f`w|DX(cV@Kq zB3q|#NmU;br-Eu3i?-G6{zY2_S+NR+eDMyZ+m=QJFkt3dn2#!;mqlZ=x5&`JfPon) zPnWon@~Xg8D+%&BF06osEkL7UxeauhOE)?1T0{>&7lu=eG%-MK?js{^2RQRy_B1r} z89*j>MuIca5wR||x!;3R>|H4k0r&(y=yIM$5DNZo#E+o!AsPCNr!dyw)Mj3F=Oaw| z$$*DFa8B%-^`scBC{c3-eQ=OW`>g2s#{t78R-T1Q+Gn4r&?x|!%Y$$#&zpf9gc0ev zF~BsaAR|+C@S!@pMjzEX1&%(CK-oX5DdjW-mY`dQzFN~EDywK|js?^hGK9HaHTIvt z$#vW}*4+pj093)>-(A0ZHoipgXSz2y8~o^Ip?W-+kCxw!^v$~H2kZ^EY*CJ5dHCPx zgJ(YAYT>{*yIl1sy+TUUXGSgt8E>*Ur z>JPb%akzv{mda8BI_E;9h`DB2JUy))nQcgGY%?To1Vk;RkMiQV#+c`nh#gjek=TKs zZayyoA+=h2z2WHi;`(F|L*SnUF75U6z_ni{kuL&P;d8oURH92p;;)|t81V?Pt53n9 z!Z&8vtY}t2o|;<)K%rJvPmqRP0mbTKfV<^{tHO_~Zd_hhyvvY=q+rj^A+o3ou<}fL zf0+4NaRHHB*1YPU>E*QkOB6WNUq+baPb=zNMqQ8d;VF_Et=9+11Vt?6TJ9rc{Zn45i~Mvj5t}5m8vIu1 zH~Ox#Jh(hs59%$8uAbxJK;|N#7lhj z@N!Wx-Cd10E_bH>lTY->p`ATR?0z1Od=&F8PyL9<@IySf@YIiiB&T@na_IO5+a39Y zA6uh5Omg-uBk( zS9||{w7Hi^nO8J=C+Lf$32*^t!6rO@hMGKn9I&K| zMcYL&XxCeFMcAUVKe7&l(^Gc>JfQm^P!pP*YK!=c>wm6^3gy2z2M@h)bW5TAdCPi)^FkUG9JIV>6((hAw~YQa`s15?BGRMNzZt3YENU2 z()zEY-phAqQ9%56ul1r1n?lb{&-@uF)ej&NFC)_puCw#A+ZA#ViGjY9rzUk;A#F;R zNXW8p^3-{j?H#f`LFJZZ|CcY|@IPfs-Y_wNY!%+?KYQ5{P}ZOAkX4Nx)>15Ks(Z36m+Z&en z-O|je1svLce{+q3YXg+l%ls7HXVG{UIr)vY;udg?v*<_P<;Jr%*}u6w9gl7fw|MMv zgaqS5D$-g;BGxlJ+<6r$CNA+^?1Gl3{L}g&yU?^N`Xk#XkCO|8P(^SHcVxdy-v?U- z+CQRYxTbdhHSFN+-@qjP_wQfqy!=;a?T;V*bNTZ7oxMF6%fIe?g?Ic{Ixfm)A(@PLgiL4$DNAbebw7>-Ei5oJykPnb10G`kj8 zCj7PTt)NH5U{UpE=*dQLC3{BKJmoYl%!mckM!kg*mt<+2qHV}U+^E!wVx2?WOf{8r zh#eHO;1%6Sp3F8*SJ;rKaq7N!TIEII{}Ks8&Hr#QK<7)n3K^x)Z!S?7={-`|pU4>} zKop*uRm=6?N7u8#883I34lYhNH%mN+1%XtV@yEf5H60F^Xr^hf^-ms@T`5&?;MGeh zyFnV2!f@ybVs3zXFWs$I-|xQK-`+dY)aAe5^htJ+sih4(%>@ za!5;>&0hlbAb{%aay=U-_yh^s(xq*qe>OwLr_`@c_$5z<%4vOw??escL7%B1+ zO06bSHhnbRiT`KcF=i!V0Gvcvq~jX1jr#9!uln_DKmx=328xYwVQ16(biEFb}Oti+S(GszuV;hL2Kvy=|eQG?qm zq^tBJ1?lT6Oi$d{B7xUf2vSyn-;(@Dp+#L#`6^&=FFSm*P(%c7lTnXF#l6hTGJ>@m zW{4V$sxi=dr##g;-_tN>hSx&5nq?!ouT_AyQEDA%fe62tYJI2zFOqd4HJqiZ^vn&m zje1sODG4X-JyG!1=n{;leP8RQuEu&lNqrQBw3Rv0V_>t%d4Mw{i@}qLi8Nls-1937 zoIT0$>OapVd&}HkP+44kOR$P{oC3fY={=!ppI zB1KxO{NaF&Fr48G$F!tJ&U7A~B)29^P+}Ce3NIhe$4<3s$)HQE1XVVbVw^BN;Aj|y zNv9`Sfzr0Y6p(dXgz5(43AwczykCfv5_QC|Ff75Wsb84U>Bi>jnVP8iTeDm9T*wD@ zrJI0s+p5NU69iO+C37UofD(Rs-n6b@~o7eRUhlFggVZOU-K zbME#ce5c*YWDs>F?ZilzfZIMeU|Kjb0=pqU*oj9{d6yw zoa}&P84Xg9EsyPRAk_v-WyCP&Ia=r%3T>Epz|ZAtg);uPT$aU$d0m|b-t2NUgK`c{ znro8X<3QC1?}j6}3-$v`COG)lg|Mu9)X57vl>`iti-Y0bLFOYf*KYu{c%Uupa2My2 zP>HSY1Z1%Aupz5+365~?`8c3MoO2EJ>xyksKmxTfZ|qw6Y4@Sg)5edNxXq;8(B|Rx zozVxB06poX8@kcindu!S@YKv$Hz(;-&N$j7GHTy!;l{wHQcjQnG-m(qdU$b?ylGBV zDFK8`$djk|(yndh2EQ4HvejJj3Q(S%9ifou&}1w^4$Xc34?aTfkXn1oBPeE$qJL$7Rj z(52gE7-=r1lwSN z<6!ehI1QdTVxc>19wljfLzGZ0!z%D_dk?(bTUn0iAmK|an3DI1LV@bL_$|b_WzJGT zm+r>9{!GJK*&28yrA&_ZP)b&1tcZNbZ{$FU+&@&zguKT=mqI1$QLr;kpQ4ffm%tj6 zoK094Re%qCPWuAH+155sw&m(N-4>w)`<9!jDPIu;_Hn@n8zQB`|A;Ap-_h_4w5RUq zlwk#3CUC5#C~?*zx{?Wdr(Td?P(iT`Q;ADJ7|K;&M}QD4(6tIuws5a*LWPv4icbkp z?ENCBba&Mlz|Cx+WE>1b`6kZ7=~$$2AY1EnAL*-?{X{jk=Lg*nT5HHMy)~;Tcm@lwf<^FhP!ei3_~I{2xDJ0TW#*@t?{z z{u>5@A`Sdg1`|byQnqX34N1dAD425y&X*cje2-!wALMo!ZZqB9duO(FjRYqg#LVtAHkr{`Q0YC zytUF?L(xX0GQ*zI1iec{HDZxIk?x$$IKDvGB?eQ28Ft@5++Oj9X^70Jkh9l`=Y_XM zHziYWKH!$E&O}&pLo3PV{J&RWc7nAK=WDahRJpW)$k^n8u#Cw7lUD-7Nsmc~tTgF8886$c_k^2@B0Q0g%pab8+ecdaU*vkS0Q%xY=yu0Y2XR1! z0P^K=wIt2q=vpmK`&yVIw>>gQHjVm8HmMp96}al2@JsgFHP|Wt(02j*trWSe{@2Os zwb?Vg!qszercB?(r?_^q; z0M=EP@FjgIfcY0k!-5-d-46GTx9lbsk*u+NudI!T|VOZespICVoF!6rp% zNU=Q^?zOaH-`rh}6Sp_Vj>cl_;@~C-f(T6IG~D9o7Yk8x_n{@u}Er` zXk)PnWsz;-L1Wg+q{vdtnJKnE-EA^4g%>n2@SX5bL4t>}f8nt%qAPZwG&Wi#y2$?p z@9#VrStyBx_L5))fI1aj5`XAp{J|OFKM6!WlYI!M5N*BWBjhkku@C193Ton2AjX6X z%t0Uhg5XWs3+$D*@K0^?90=RaH3BUE@?4`!^i*M0g)3j%f*UB?Sa~8s6a1)hjfM2z z7%lEvq$qAf3pWUCXwH7Z5x8&|b&LgR7u6U8m;47N;+oucjba+CBfJ4kd1Ew~Jv^D6 z!0nAbdNelIgI9+7QP6G7;h?*~)~Y?h|3m&~+Knz6Jss3|L|jJv9&VM%@Ey`DaLE&N zG4b3v`&ir_J}qSz|82OWNg06nc8oH(V=o4$V)+cu&S&IyW{Frjh`!~~=pY*|G#ML( zK+f@@pPK&Mpxf?pXLI+$m8gdjKJ^Pys|mG(2@wAn%#Hsn!_}T8>8xB4LWbPt)j)GcVZ$Y~&%tI9G!UoZINIVeNfQH&w8 zOJIRJz>B<{u3r@<0$!UMAEKLT*OnNVhE@3{?*{P+6uPX2o+$oRkZ~#B-;XG`WAZg+NwaSHWexC zd3}`+2XAD<%3cUy?j6t$TpUg^rqy!wAcTMMTuXHs%26-8Cc`wE^25KkY!KxZXK>%=+ozgf{*0D9Z2aa$fi^v|R`KQAt;7^$ zrYngCABj_T3K9YrU5!qMXK1J^<9q-vr&`Sb9vu(hAj!yCw4>~Mn?81jqiDBjR|3Xj zjTqLk8rMSsQ*m9EjIN(6Ek>9bFf`KykM*}4FmFMWWGdP8>2dHiCOkEH8sKFsQ7izP zDmVU452e|X5`GnRU;4`wBCT^FX|TYp*RWZHhr;F@irEd>UEi0^3Gx|`(sD9}=EzkJlDA%DcH~FtwNN)c zzch*D9PNyT-=Tc=9UUD4c7g}tOqBFpR^BL9P!=TNn)mvXrxRETA54QH09A{^n@~9{gA;l3Z1=m}4tG(E9Nt-s3Xb92 zm2B_^t|?hT-fc1uWZ&YUB;zJXWU#;2jyrcWMb3zW#~BGY=LO#sI;yy=V!g(<0Imq} z&COVPgd&;SlSKF9S5!We!CKS5lFVZE+}T5-r0oGBQ(S=%hfIDIM$SZ!e?59JP2rm5$l!7J( z@o7a2mVSgp1PVjnLYx)QfSEIi}L9G;3lxh>4#BI;M9Y~=73@*5qnYXE45uKAYFSm9$z4J zC4PY8iPc^$VcZ=Tg~OzIxoc|dmd+O@44IJ-s*c0egR@i^8_9L|5ut&zVKh5k!{MBV zIG>}tCM!FIQ&+WBz}`yW)^S2gbPOtfCU8sQ6sj7jCoS;~g!l&gUJ_UI8`-tSN?j7z za_3nY3t87|2(Ar62vH35f01 z{Mr%^b;29a$S@JrXnMG03`hGWH@xeQOx?C(0R~ehWv;pkrs}0iB7kM!MQA%8VL!!X zK)&EdDi`6FdDc|Bjc_pKH@O*`+sK0%a!srOkxCJgVJ{@NNVgT+z=CMD=Kl{#j157QYp0{1_z{@$U)AM!cY6cgpY_hV>?XRpNYt44iX1@xFLei9 zajBbLH9sPek_&zdz}6f;b<%`lZ+dG_b*g_aK$op9>(p%XQJ}V zX`GB8Jdw#(^8JUQcq|$xEV8k>tB;!_QWeL%Z_`0K3n;jWBh1xyQ3bUcm=^;>p?w?y z2D|KE#7XF=20Hx#$sjP192OVRNdw-fL5BEr}kofCaJy16p4~mY}sJWC&h6vTU(%)go=kh#XpK9kjACZfObT@)pL{@#(5kb!}gnLU{Fm_ znr(_8N%Bxl4dx|)@=MPSu;n}LXw7>}Vth6RD9&Fh`WFYi!M42eP+zuM-qA39lU&L;o- z=W{a(8Lhy}gWxRAy)dg`Zh<^)-`FE;t|SyJT^)izxi!r%4s4nk^tV!7F^@hKF5Oy) z6N*m~BYn>NS1I8IqQyT{Xp)orsQBDRj3smIc9JTH(Fq)!ZVx(YInj?inI(@GB1FPO zhGR01$Z6Ds&!6En!+iNea?`MolKyWMZQad53h#(M07>^TfF_tFRZEUuM(VhZ+AoS3^cYyj~BaC=L@farGgu#+ev>h?@|Np~t4 zQxYigz~m9uPii?%VUapS4i?tboJQ$=Nkk&LF(jf-?j@kF>*G2!+{=rsL}6 zhf(n|&H^`n(7Y@@RJWU?B9VcIm7V@PWr#LA@RCcA4!hRaaJcuF+V;#n#}=IeEilPa zvUXB%=!U{QhScJJUh#_r&=QEFZVS7(h#!k3qFyiVRKzA$rIReEU1(jnM4ne7!pXu~ zn5(0~;6&2wB|)8c6k^sWd-%#zmQbIDOPH;&f$*z6qOqDIy@ph!Te6WlBL zw&H6f?7}Hy^ZHanXZr)5J&k!{V34nL-y6r#><6K8=hmAuC6X_)M|iWp*+OEqF1P@81W@m zn=Ha=o%m*+2Z_6~dZ~q_fPCFusO%&sl>ah^-X;|nxH=VwrD6hvUg{=J8AFAgG?`_> z{S3XhJXx9^DB^Ic^(0fnDL$DR@0(wKa`Rc8qkEm}#>s&RaeC$K3@m<)Bva+A7~N43 z_QK5Nyqx)FM8LD`DU>3<0x~04??=6jQo9kDOYIqKyl()Mwj<=ST)>~O*8Liu%I+cm zSK9)Hbxx@zR&n{X?lYNERk87I1Q`|6gn1E_0?I|19-omG>xza|#oTQqqL?=BAchrG zpHp7`^e#+|LUt95J}Gvbv-f<$(G*cQDLkVA%JL;FELs{Q5qhE#mGN%WZx2Ci2&AhE zRCPy@(i7AonGL90k{8fJ-G2vP0tnTLhA8kGW-NSbudZh(_ym*V3|=@e6ZLHS@6tQQ zjjWlow{}8tKkuwWsbPj0?Se9YyCq2lz6#{v&IySi8Okjp8UVr~?|`<7Rw@s253hj? z`<5Nk!JeeCd$nw1dBY|;DnCy$vV>(33qd#C4n#`_1=A&F$YM7pGjgm<9amwcRUwCU z7$yTi!R3a1FRy-2498`lBnE5%{QQnh5o&fDG^6IL7FEedoHt-0OS5PxHaH1II;*56 zp#SFeJ5T77%6|qAvE&dHp4Q?L*~8*@(b^!p+^xL1JF@6W(t2-s(8Zvt9^QZZV|3+) z+Y>F9JiYgqb(E-%=K%*)p z5AXiA+5ZA;YUi)(ARlgsC@v8*V!;h!gX8NNPYXTAjUj&U@IQuE#`IL|D09Tq$OCW9 z9V6y36wC7H5LWvVY}V&(fhNC>SBbN1Rt6aq>S= zD(TtOt3it05)T3|nF+TMG%>0AAtZ|*&(P0VZ*O$6hQo$H0*gS2btOm_PK892gqIbj z>Ah<2oYvAu>Q=*XHJ(Ah=*b~D3K*FYFM1SlgN^FE!(9}fyM(m(_rt3zT4R;tYV%pM z46HoaxT)ho*tSAv54)saMPdr7bz9O+CI?Jk0Idxo`k#svRkXIE-UKfq;)JTEoCEhAvX$K%Q5 z`ieF=PWfI>*&00z?cxUw^pjl`>(OOoGf&QBnuV0uJb~#ffE`4Mo+b*rM*tk-xJ$Ct zo13_|aQokO_FwOBx^n3mxA&=;cDlGNq0_&B^WfwLJR0?ZryD#xC;NkIL@WL6yxe;A zveF+g%#Y}5w}2gTc#*!xs>7q%;(mYk^hI-;ezgXId6JsZS^HwTzF>;qZ-4)4@86q! zea}nGa6dEd8$l(Oi)16Ha3&dmTrjJbuYg+3zGMsR9)iB~ zXa}7a0qoW8_RD5>Tm4aY){#Tgpk={i-ifhbZhPBLpEdj9mJ&6LP?muF{`U62*87Pl{eupAcoo?R5F2AMv~&}_J_ zhr@!0>oE*)Quzb4Y=<)Kd6GaTfn6|t{x`%htQVvUa}nnE^ggsRH+h?r1J@F3xAcQ+ zRq46kJv9PH22o-Pt02+2xGS@S2+eG+vGSn-z3t24bhohsm0p_LwwROr&QL8wh{o@2#d{zv# zFPDWC55t1om0DexW;kN~-wsFp3BV3}{Nln*e_xsw4FZ;=ZpC6eorHRKrILb7DjBrh z@34D*)ifBLndpDj=^(vDFDQr(cy2B%D*TeYn`PcJ)ih~yF=obo_6Tn^Wj1Xl^50VX zjqwfWf!&|M1Ew62^D1d6WpoZZX140Y9&{2ULP`)WAll7A8vU$|%vtY*8n6EycwHl{ z))vLa{^v5;vvG})DLdbgeJB_F|Fie*eQ_N}`uN|=s~E8;GuQ%TuX9;qNx^_@vXWo{ zImrhUc3=jW2pBR0mb8xdwf~-{E`2-a%nV3Q^4;H(U2D!ceW|Xlu3J}E_sk=W&^Mxo zjPy}UX31REXvafcmnbXdF$tB=T-`BtDPmRbP-K9Ha^;cpv1vaA#|6m<3kG!}a|WM=2t-{MBF?xFquP{R^vIeXwT#Eo9uj67 z%B^c&A1Luq3ug1Q)pJKMVHb zs-BAINwEKXs4i$bvgBZ7CiEa!H9o`fh*iCJ!?aj!-g%FxCGk|)jTlFJo4em{?#k6# z=O~7|>ERYCJ|cMj<{X#F2xp>|?(OllvclE^ZV5wB2fDFk>+4uTh}I>u0@GDP$?LRH zb_vN$=*uK9en?N7&~8F=FkCFE+CF<71aWj0>fwKIjZ1P6YJ@Ct%sBGrzrk^av7_+L z+V0x^_AWN!u`9zuf4zmQL{awrk=&zG$_+QQSYVC zCbFd@XAig9P>_hZ*PXQ6)6@TffZkA zHfpeBSv3K%$`nygXDg$E3F`zd=5XhjOB_c~iQ!sa9Y^s}kVvS!fjA0(^D~dYZ^90= z9EF%sHCY;@|0eO4k`iZtCLT;ycbMv6b223lLZ7)0D^%&}8ojb@Wqs&<91X{>WYdLFkx#(f#5K3lD zNmRM|MG+{Ro>$(=0?j!%xHSW&J;7bIoEJGKHPAw2*9qXS*ncA2J{D(PAL1g3> zSbZeg1Yc<`FYdk8V$$(9O}|k2=UUBd&5`MbdSI(?1dRt+yAt{O4P9H7ZB-$2wLs>} zOiI((y)gG2Wt6o|k@RBb)Y>9)PY(8Ox)+-i=%~QvGHw0vIM>X52B4d3;+$ArXr13B z#6p0K&brmr9<*%|cVEA(OF`U!5xNI?+dK;d;Q+ype{KXp3<2xX)uPye)LCt z!ZJu#%GjfvCHtl8W7p@`cUHyn)_#e3f#4)A|4*rM?)3?jDn+TQ67{U=<3NNTLRJKydb^PE-I;Z1WiyP@nHPP&8}%L_Dw89`8ZoYm1?esT_Vxyi^1hU zHl=(hmpHvL`fzl?_p`O1){v%s2fq+aG2qAMlEzaFPVur93wW~BRQAa;b*wGAIHo>Vk0j1##YB?>G0E4R|L%GaR zqJEjS)S1JryMvqdqq#O@K5cydga}+>48)QL*x{v_YGOG!50gfPv@{S5OzreSTsfcy z8kvVU#>f{iaCf@QGXN>m|hl zZy66FFjAcSuci|mG4vsXW)j4KHtkjjqx+->s1Q~c&an6(C*FfDs#^FVo{4UmHF3V+ z;N;mEyjh}7ciQ`KQBb*%qnu!|N{n!K>4j6&VEDz*6KG<}XSL%~Yxp3r(gAQkKTV9+vd7?d|vyG}>|2b>V3#7bb-A=iW0C^WV#=>+b( zkb9$B-QHhr$2YAHNDtHEM73m2(f4rF1ArGFU={cA_U7h#jI1DN)AiBi;pl9oH#}MC z;$$BVf{j;H@{E@+d;h8=E=#e`Wxsj!nhvOq&7IxN^|k%Y4Uk*lRYIMU=A>g~lc~=* ziPudX2Y_^vCqV(E+dDBaG=U%&>w}LYsC4J&{|E|9;-7$^(mDC$1eFq~Zc!Gr$nDAE zmG4@~hGg;0b|>*RZ5axjZA})%!v{pfkdceX{zw}BKW_L>#0_OKepaeY*kHz6Z3jci zszydvZlSCZizmn2@)RX#>`D;caZA(k0r82V`AaGSR&aRy7YKj27>^HdlQ}==PiP#8 zXw$Dw^VqV4D;9#wtLe6B`tpm=78(jZEBJY-i5|=9(e;zHmFZe1$D(MN!tz5Gio$y) z%VVeEZa-HctC2T|+HjRTkF@ZuK0pGr4o8EV4}kHp>=&!f;U&fgDxjz>yumK9m3||a z6^HFefl@;*D&ao>V(<2tBKsJ6pwL%*4v}8SUKt7zjOa&-??ozyx~fGXSWtXQB4!Ds zT$YZrBHc^5R@A20=gL0F9P2=vaAYcU?47vMW<`VTUyS>;jRzXKZ~JTjLF;4L-_|Y< zKuf$pAv|%5Gk^htjp))M>SwcU;d_qONg#tKyflcCXNO5#UV$iUOiwJT7h%U*2qHBs zRk;fVsP-!+QBBq8@GY8ADu)bb^%R`wbZ?3|@|zhRZ*q@v8^ zi{^<9O=nXYD=4E$0Uq97fz^e2i>;@!v2P$GA&kLhuAz|%fnDo)-3Wyn8iM=a_{QWY zE+y8&DoGKgLdlRQbZ3_bTj`pKOnG`jNmAK}TVpVjm8vh6p5g+!{uz-vqNR&vwQ$Bz zk1nnOY8b(qZPR`0WbO6hqI)r3oS-JRk%Q(o(feMO!_3O zSJzA7NMm74yUw*#m%wLP+>>1DzQs4cJ{X~^G)I(h)QAq)PDz4`_fy^4M+ z>D6#=j6G4`$H!Sl9094%wLeoYXgfPoq5+tPzaI~;^rt2;Dvm4gvy`EOu=qAHH@NZP zLp!XB!vtCH{@L=09@@X(;_wNqxT0_r4Dcjl7g?^DH10i1(dW1h3VSroMgIVMhpZ;H z9!RgKB-wI^3(pjujY_`yS?ju*J9+RyU6C^BOkJ5h003`xiKm^5+q1#dq5dOl3Fo{r z0OjRI$d998Qd(xOf6!32(4fxF$U-dr;_@I&*eH2EapQE zN(;~TuDEye!O5$j?tOlZNJ>~(CY;j zqV8w1MV>`&P-ArBWGK#@hZGHaGEQk|dP<4J*y#*CAe% z5a9~m9|oi06_d@j;OZD~iMB4eX@G0%U<|il04_ONv?$wqK} znF6TTaIDrB33%2XQ0R|6%dm|U9q)T*%Bqt&{Nu*AM>w|qeCyjIokixJ3ZNPQSEqwB z3?fWvY={K{fYSR60FoLf5}l;mBGoA|fli^#VfRw8=jacUy}q{o-R9B8)^4CdFP+k% zzY{2>;mxZb5=?~VD%6Z}X+tD%R+X_6ES=w}_eet zAZxM!rG{ohvsGGEgg8NY@|h}iYFG4imh@o+Y97m;Ph5hb)L^Xznb@lmt&ptbUW;5u zC{|{?kzj37GJ2I(n#r{ejZ^8A*C^#=>a-ETy(?LTCEUm=ZFZFx2{O|;z1wWk5}GiZ z^)td@CkuxYLU!HAKqNkHWqgiZ<${YyOOq!>#v?PK$K6XArRK2mwN7#J6F~APButoi zsjIl8^-7hJrnyzgjM*MczcHz{(vNSd#IK zWdIT5?kF4#nsLD8Z3L$BDRj>cqMKN{;5i@s2m+nqAD&K%AA}<;pW}Eh4+E=26UpzA zkCj>5HB{V)3NyabzI=->bSL5;gnaz^*S}77E6T#sE6zdTs%MynS2q&9Q|%qAW}|=g zp(Sbl(TfUv`k7mj=ER7AdK5Kwcobw7#j1C_;RZN;yZjdv3-X5$@T>{kY>9q@v&dwT z_@})6f^U-OpfG0?R8nYrftV7`_oPL99r^VyMJs{AMLEU_AumJBxYaI|EJ4-t`>RK?A%$msd~;OO<`cQCF496RzJ0DIAIVF?d724Duqw#Lj}@W&(F7u4GJz0 zy9y#;aCUxy|GyDAKzb|eUa8t?BNz*I#a!D?O-(6%jGXmvX^ed6Gklgch1e-W!;Wsf z6xHt4)*I7wrqEDH)HEtcJW@^f24=f4%wiCdjR2fm8|0!fjGKg|>2Dol{lzCg(mt z#RJd2)7$M&0X;7@#JYcr!G{J~WQ#?KpbV)nzqYDnoF}u@NR#!JR z_czz~kN#uxPvTQ~v9-RxM^LdpBj@UBy`tEa5t_$74bsoa&ZlOB+|U=aOGIT%6~u77 z68=?Ek5xWszwLi$ztOXaUEKU^URa~CggQ_J@x8neC9BRCte+gQG+ZZjxgv=dZZuNK zlqC(N*@%&weB4uk?k-!EiYn)QVnmfHYtlGcQ|=(qD!yDep{hL1tSYBfRh24^Q*Ug3 z^ZHvNSK*c&YK3)@dhC=#h&mXbrIJW{167!y0{;x_Fz^2yJ znMj>SnN8Zd?ZXTHy!qVlsE|&hZBaA=qE{nhHsm$gyZO+aWQdg%?}U9i?(?>y8ZLL@ zEMW7mH#84xXd9#GwDpT82%&xF(`8e!Zc3r7HUE|ngxh|ATBviuB_~%dnj=wOK#=MX zZZ*_3sl+G?&HbNhQ{`5TFA*`(*cDoaAV+=_a5mSjiY=IEdqB90xC>(w_$$R@gVuxS zF*S;m9UKiO!8=qjaAPoQ~wR~-Tq?v@$zH!*#abC^Nw8GMs%sLaHji-MkV;)vuCCeb&3xE8V9wL2VQtw|(^zCq$$q9Pz&o533chz(xH zs3UD#NidU7umYU4JwILA867I>)yj#+U}0Jr0ZM0LVIX%b>KrO-uMLNhm7NeoT_Lo^ zM#YHqnYFdltMuYqUxA+PNRgHLJ?R{iAEJM3hQWp;Awk%%>*Vxav%qSqgjO*`btk1b zGi$`K0-^=2=wvI-Feg!M%^1zX*~RdTglx}ZpSOj0{G-SGm6erB7J^)k-=O9I$ z;#;A*rhS_TP8RA5>0cNKy41PNP$<+x`$43Ntr_`^ja|kL$nWv$>ej0*CaMWz6>2O+ z5ps9AFv0M=)g)+&E5To*H3$$D1{k800=#+DpTMVU?fMqst;WZ&!kTKZ3pK*AP@^A7 zk)o@Ymia{Yf-aaE%RFBzK$jTdVr8FdNQt3P^<|Q6fWZnZJ#Ai;O5vxiuDZ6>3wFs?@RW-Rp1eX{MoH?6UUtpYA;mu}PCht=XaNYIJRLRO%x9*`@ zF&}DWda=+dOdnJbBrEXYHfS}sOKDN*b=ZuDNL&TVv4i_UGD;Pf7EH7gCN3{uUxm}F zl-pA$so*dc@3@hv(3llc@+Fe=MbZ;Z*5*vTV6t88lPF9uL$U6Uk;9IcZf>x3#7J^s zKk~)awd~zx64d54tAxQ@{rk}}qDO>$7eo=_vT-~>Q}mxf6pE@468_`J zHL_^&@GeaB4B+r($rg_8MMxh1HfZ~%y*h^;W$ErnD?rBzV0^5r-twi`Rh6 zIOzyqmuO5}E& z!_`MNd&+D)7uA-Z%lnUP{QRui@ONh)*(eT$!j%tNs$gTszBF{ugjrkyP;CpB6*M%% z34`!TZv4X=S)2J((#&m`%Tbrx(#0y>?T&uF9^p@|_PBq)9w!X!tQMEw;_c5jwD`DnCPG&SAiSCzfSHqil^ zm7gEIiks?W!R`nM_e|u46XLBJK-x*wIWw_MT8}9nUCqyS8pNMN%V?ma5?T~m2~USF z2FH*lG2dL^c8)_%ipZfU4ej*1IQs(gka_GXt+cQ4dFetsgBHjYG#N0R;HDO5X#wdo zyXH&Gnql2$OD(tICmgbX`YxIAL)&g%rt>eBKBfU0M<3cfqIOtG6!PFF(4*h(^@OjC zeCUDT2gl&l8S0(%v;mN|mlr0bjgI6_daIH8QMpxNBc>rfDWFucNiV5E!v;gb{&sjYI9r#;Ltb z%r!dwazn@}EZtln{!qk=SS(?#wFi?o7M0hDiC~$r7;&sTD!>fJc3}f}sN_1`+k3IH z|6-5Z>O6FZ?Fd}xE|D0$<#J0kj)ee}L(z9JNALqVz{L%YBk}g*_D*KL8;@ao64 z9*)}_=10IZ8I)DhQ)g_BH`(zbi2TKD>-Dr(11!$k3vbq2_gv4s${A{A~8$lG<=?KhG4F6gH+){OU-@6d15#2#HyZvvM37TQ|407_*z!%ilfRWM^PIb zU|8pxhWNFS@vsG-Jablh9OU|13+Lu;2B&hkOQ1a0F2$Tv2ppBDK zMQN3Qj?m|tg<95hF~b_oS|=75H-lROxH)mOwbA;z^~=YUg(gePhy*PL4N) z^<=5uUZ0JaoE=MyIXi#3{HxDf@6HF1om}pEu2}Q0sROqK&`I->*Bc>$JbHieA%lz| zNgnGQMo#=mi@Lr)DO*U9{*!UZ1-V%*DA*`a&haK6U%+#Oz1O)evkhQ3r=>?Lzd;n;*0yA4>hs{?IK7SR zQLAVb(-iF;iR?xdvK9#DcwhtkLg=x$?-3Kru$iPr8f5IT7I}$4+eKT*wvxA{%B>!i zW}X5nVP^AXNpaxXO(8fCr8-u7Bf?=O=<9mqvbkG#$vUdXKm|fUi4DzS&3P7%5m_2( zEvk0=8cFouA+=<9*Eljrfyuap!1EsP(;GGoShgQ@4|5uBgNoet<^;H;@f_;VRRCSt zoZsyiI2^)_PTsCv_4Yn`B%DB-hv_87xsydel}S-hq45fpm_A2b9Yj1p4f2z1oJdjS zw1}g-ZAJV!5{?jr^`FFC&UxC6x%m)%`3JqnWK~oEJ2lb$VTc10Lxdx-GHnFEMj87b z2(LsGFkp4nb}Tyg!*OCB%p-Fmt;o2F<}5d&0d-FT3$t!kXP9mp6v`z%axJ&bgd~p# zH^;XKMY!1(@JppOd|n#zJLDP8O_%`(bq6~s=eNi8RrOnAJy;u!_3&*{z5U_a{uPPo zB;6iGf2?=Ik0qRpX9f#LLXff+zbqZ|Q&9(ND7q8^8WdzX4>!E*B3ha!{orHL2plEu z3CpY}mB1ZX1-1ml0WP^7ElGH9^)+Iv!NCyvCbS#DNzq=wBEu)qbBX5HoBjD8v>9qDug^k(D`NQCvbs6lp-c- zwKb2DQf;|CZ?@g+Sd)rS9o{Q-_`FrI6Nsw`JGSIznJVv8hWw@N;%0M!-u49(pRhN} zV(H{8L!2Y3&B+xhy6E)Y#2AFln?Q)pM4bZX%6Q1tRWPmn!DWAVd(%Ne@tehBQ9=#k zkNPvY}$OINH!6Z?^R}y5SxQ|S{+hkr0&pJ@VM1xyh z-F&&Z`|akd^*>=Wp)qm{HWPRzB_;k!*4c%J6&dA1J>hsVw7UVZ_F{8)e+B@`{&5;Z z%&boU!usyk{uZ(kOoyTbLd@Dv0Kw+&?)L8V?yIw42je~g2!B}HeYN%K+o?d5D8Ox2 zgA+JQKAI3-ZDSHPr}kgPfyOC*bpH%%oZ5Ri`d^F}3%~BIJ5rxS&0j z)}6Ez#jXgq8dz1;R%#hTd}#|czKF{vz7s;uZo*6SNcS;8z8S)5?y%AzF+KK39wg_& zM41*~MZ1mt8P$sZtU@yD&z8s2yvOj?&0vD%@!{8Bkm7YiDmMNWK-VeRe$;h!gN&v9L^oPX1Nn6lE z>HgM@0StHnnu~&JO@s;S$Q?wTHgZeio(a7&2cv0^c=kFO6n;n0kb5~iXf_BWo`R>D zx1+9x)4HJu0Gz`i5cqmlSF89#%L<)FB87SCm;q+ntR>rqajjSsztJgf%iu*I*ppd$ zs?Evfs7?ga_A^0rf`_pXZ4qJKSUshj?zJL6i z#m@5Q&lWrU`T62c$X^-sKgb$66eStC84(DA0XT!}qnqv-T(I_)S*`&x`6j+1=bc0k zs@v6Hc+oF4xw>hJZ9gAm&zk-#ZFhP8@M<2MIpCf>xt^)$qb`0&ujRBwE6X*|`m}Yc zvzuqOyK7}_W9C{_RsnM%I%EG62de1Y6z+4P*+`f@0I&Shv_W~fL;nH1>z^{Dz<&Zq zQlBi@QQ~Zsb&smaE1$LW-MO$n<#|a_T&a2UQZ0|XjpW1T2s!-Nfx7FF00AeVX#+_O zdl^_BG7ht}{*@~=UGg~zAgX~3?2)C82=f#*45mL}p%HE~SBdehL&y*UL8^WrXfS*#wN_jtc464EO znxUvy7tLTwJymUECXRB3b>n=4iZssAK%WiNlFU_jN8E=8HTgkkg-?Gl7HTj-#%7#7 z=@iS%Q}$%Vnp?x&PcEV=Z7Goyf~>iz??upO`ByT!6Tzc}pbbDIK7BHYXlyzs_V?k_ z_ZbMGxnr5onr#`!7ew&v!xS27>Ps~KV1x0lY3M0(zF(7j;KdJ#dX*{rRFIS=dc!}! zqYw?*q#cOgF0QCZ+kwclt7*r4FGDucmhkF65S7gYfWStb?*^q|;@Gg9 za1s}}#Uhqu;f+tj{BY4JmI5Ndv=3s1H?|j%)`8bIWvC@#)27i?si?#WH$CGvjYWzD zsN4BzcQwmo`}O|L>wQb1BfU@`VgQ+*3RUm|HxVmKmDtyGtYOr4;Us7kw>{t$*5gX+ zi^pGnW&e=*L#*u~9*L^_<=_P2Pk5oHM_Nmcg3t#lnCTlfIpxCTFjaw$7v)YkKJu1K z0SHqRdW-05`R+{n;NkKYr!T)*dAR)L>G%!qsitMoP+z`?|B#gPsHF-se3K5I5)=_e zQqxT(r4-pxN~acHYz$B@2{av~n8d_@P1ZlugyBd_^4@~RbqrXOnFe4+Sh>al{QJ#7 zfdM>gQCH>N(m+Ms5K|H?$n|5uxfd+;48^z8YB>}7F-9nv#_h zu~InTV53~30ZS7#9i`)ypc6+-yK~`34J-X8Baxfzr8Mkzm)rHjC`XaB67a!pr!|Cy zDQG}(_Y)T*=%qRGfCX`q-~pv)12N+D%dWx|Quq+o-t(jCL=@mI4;G52;# z5tSw0o@S%jIc!fm33iXj^G@5VOK6`zumyOV+)SfsK5i_UrG2(C-Gl{@T^} z9Z!}7@{0yUs`>k8<3I_3hkJe87O{RY*g+1^>znv%b@hDE>-Deny&pfW*9jof5n~^r zz+|4B53vnLS!f#GvHAUs}1uz_@7&kcXWioB~ckxC%) zv8vKKo1W`E>foBx-3++2C_miMb?OILLck{mh`&L&9Vj^yhjE=?o8qQrE~(ZRzqJ?c za%z3)Z-Pgu^;PmJ*#4l4-}rlT1hu~S71aa&mHfEkY-uW{RwqoY=9W+-IyIV$nG0f4 zf#BEiLRG*URb-66)Hd-7w0*j|8ul-Blnt%UpeQ$L@)6()s)Hi_Np$d8I+|f1Giv-v zZA141%VK({toKu`Tj{ALVzy~H-ZQWi>s?f32dW(W)c8_z`liFH1PgJT%=I#{aixv2#O zV`j+p;zJG%|9-||+6hMvOhfZc^-S_Ni3fGQw;+ks{N#4z(b2Y3~{0ch1J1ZAb z1J0|)(w(}jdFxoR)@r{NmzX zwL_(_zdappMa`Wm3_)z!4Ar1x7iXmr!!Sd^Tfcg65_b1wI;Xd$GRSelg$i2oB3cMJ;16QssFn&9@Hm+U!U2F$3 zDtxU#sfiS$o)U*bddo;garK9D@SY1L3soBvqCwUI{TKu~?f{BF6%CG?MNLr>iNA3E zW_Z=T@b%n_{6a~eHT17Ei?g7QZj=i*bR4!VZV*s z&^+nN1^LqXp?xqc@JnEc_NhPvixjUy^A)bh!j+eyxUxu=ab{^<)?kOFYF>x73ZBX0 z#j8-Xf-x4ZymXrhgjZ4>n1jTlaR-|JJ3GPEHb@k9f-7!5oFJbq9aDH|iC5H3r5p4% zK;uokhr!+`1{nP(^uv1x5!aL#_cab{KiNJ}_L!88Oi@{aHIlJ3V^;R5j#K0%Qbv=1 zsqoVLbIqJdvcD}|0N4WK25+Xn0(l14C6*G($==+KU(b9sn*#E@o%f>PVMM z(M||p_bUJ<`4o76mvf#Zqp)QNwsNVYk60^KXHuIY-_fUa`35zK98jPlIwGgCbY-tT zpAm$XT3|(rP$5U;LAv4b3x>vwu;)o}VUR2Pv8{hESDXruVF_NKrXfHLHA@&w3Ov$vS?+LTvNJ=`SJF2KMd9z{FI)!DX4l&M(_^kJahN&D!8Ey-l;W3U-);2njs7s05bWa&xiVw-N zTUAoDCv2g@?Glt2D5+dtddicjr=CTL^Jc)<5L=#cM1{KkjoSc0RTtSzZQ(;4vM^_`HQIF%yoB+HE@PX?X6nAUKUEDc^ zoE|Fiz!;G5L$fzY4iDy1V-_G#QF`B@^(0VXC3q<#I<&>>MB1-$a7HfuomQN81jw#D zZe!yWBkfm1r1T5Tw%+07e?TS!D<6a;A`iYTtQ*GuWDQu8sZ=_>i!`JGQFz*|9b}x1dAhl~}Zd3|1En z*plOi4cz(NnUm8r(9BLx6CNZ~wBEfMUNPUAO&Hn+^MQEwXCga!#FGz*qe&iZe0XE5 z3%_*Xrnq^Dt069LFZW;`4ld+~Ms2b1{rLsYihO8IsxVYg{|RgU!Nf3QaaM%h_~B$5p?? z)ZWcbA^=`natDd~M8FxR@Dy>7^&?8!bfcl#QeY5XBv@>sj)6j64tt2G%lJ9hh`y+= zwT>Xpap`gKHfr55(^_1EXT}@Iy)@Hm?gj^m*H5(VN#mfs;8m37u*yIks(a#hM#kZ} zMlj=i@QK~(!ay^v=hM7qW^r~2lNLz;rHO?l&AAZyB1u)Wl)Z*m=vI>d&4+8YH#WaN zdbz!^`NXTv16kCdgK{5vP?LGFy|+KnzTo$t!@x+yLaiEfAJK1^ubClSqlu(Tqy9I8 ztL_M<8~D+fSCHh{oaeCXoLoDp=+D@Ze)r2KIR@N(j7wjVdyOpIeXjMpo8j%rdHO{Z zLrxGYKEQ4(Gl+HC-+lM}OHc2q31VTg>ls2d96Y??u_{Gq0e{gW7EaoHoK{QcwK+f% z#}_C_J#f)|qV6_*iz@t|49L)LlmWZ>WARlTTD*fxP(lc4rHm8h*rMl zFj-4%oLdN`D06t?#4^YFrFGq5p|AG{&zuuJfQ zaiEvsh4^-8nVMCdn-}2Hg)x_ss7Ui{ctMp5Z~g9g*ZTu1I1CIwNlPyf5J^nh6&sLV zYC(-k5&t#V>Ux)WZ`3`q!E0*-x~VAPKar+O0-cCJ&T>j~{% z4u9zPUemUNdbnd|8~PFbH%AuWRN*^=i8lw^O{O{I@?SeS>0e7PfvZb9v`8aAf+QkJY8cYm3?6kJj_R(9W4e{wHNOfvcVR$+(>F11b zvE8`bv;(d){<7&15{8AZlZUCpCz%9Xv;@H+)>U|ypx4frxqLH7bV!L5!;~YxI@7uA zMC!J}XnsBaR`qjm`5|L=PmZ<(>=Y|Q5SNWBRynASC?zbo^cFyH8Sb-C5I$#EFv2-}7wJ7KF2rmRw zvbu|Q%sVCfj5aI68pEZ9l&bjOOcuBC|4+j)TcZ+XJk?BcTkrMfC zQ00{J8vVbF9G1IKuy|6=6i7*ZN46YH3#=8+k`01Q*}6^|o~_zJTgn|FM&lB{{=466 zBdr8Bg5v6E;ts7wBn31AT+ zt_EmkS!e(z0|X~B^L*>lpzjapDt$Y3a( z6$gzws>T#7G_sRF;{I&z;BIX0Y`)q+V%n|Ey`#PD7vF1MQf=ALmcni0DB-o~tjau{goCQIHt}S-FEI%oTuH#Y(gh8VXjm0 z-s7T^tveV<=9sNzB|F-Zdgb4EfB1ZaJdF6@vU5Dgyp&Q$%4AhB3 zZEG!C0I_e7LdM6NLJLwyqw!7YGPV?ZM4}6Xl*bOY1AeH21`snUOyQ9A92LW)3fZoo zW)TAnJ?3&Ab~W0^Qf%QmsFXE`#8JrdXWgRCi*6 zA`0|m6L-12ioC+1-zc+oh8a8Te`j7AbA|&_G$y zR}!hF$~}}*w+-dES%??vgbK3Ge?L6l|8Px(CeB9)G;wxLy_x59#Jg@$Sul{skSv$J z2#R+7b?~OihHj{;lFeQ=|8IPhu9 z35WM=)o(I{+6?@iwe|m4`*!oFy$mNKHvX?6!V^uq;IO)`Jpe%aO1Hy{z!1fHqXQxZ zrz5VVF4cS~4;f-MPEVst@I1{+5Y7HDdvue#NwZKg8u6lZ`$)ic&2yX7S&f>H(I0R-O92Sl*0O~B?+yB6P)d#chw z7%pw1K;*bML9e+>#{~8kb5RhzgK+6dXe_5Q_e=yy2RxfdwF^zKO+D6qhP;VgdG7)U z!a|D4=+x+-DNMON#>T(hzs@2i!T3|f-3da>;Sj#)udeRCezAG<`|WQ$oM^Q{69YSG zwRShx_O@TG;<91!3H5k^H2DIzBi!moaO|>yCjBZW*|SXsaS=>l2l!-r040pRmUlPd zkKWzfn+ecQ1Bz}&+>QItFXJHp|A+mEvem?UVi0O$gt2@efs1`MsWp;$@B*6~&sgs3 z(FM;z6i(;Vk&!x}NsQr}CkGY?axWv(pMYCiA7d@SkT3nK_!d&t_83xwY% zj-WJqX~~06AEv0KFBh%$O9u>wdc$3=&eW|7w*0Gs*frKZs{q~IN5}hQp{ib z9Z`8boGSGKHoP22|ET7XaU_E-n1TRG;Scb>b}JKmO5{&@r@C$JO-ISCY*Hr*{5fzy z?@vtNyS7*AxoFntA_uA&gN3DFmP}rI8Xn6wxUe0Yme*!Az3~zkZVgUC>WxnO_0`)e z9CmBnzy=n@VbrWwj}M4L;QH+u7eWMCjQBt#3f0n6{tN%8bcJJs5+u#4N}#t_ z*ll9hN3Z@4TfoY&Fry$9KC5$AA8JAnDZTC0mDl$tf zoYya+pQ58irPg)AFHOrql$y-cC)+xWa;wpJaDvsuE_d0z6vY}-KBGCoN!9&DqkP>j zywj#7IN81TDUAy6kBH{|uRCG_IKwz(jU-|oSP=o6FK-T*u~)-uu3b@6hS#}t69cOq zWrZyAq+xeh=gi;Om&cgF5)ompKtyWXA&Cdx=P!EN9=8_>Vro{=qDDzRgJ$O=LeMrKGA37&dX=8(Q#=hdlEsLZL)KRW5DqFYOZH5-~n zd=Vi+NIsRLf|w#7juixhgWh`yL(G=i)QvX7MuPAJYuUzF+SKcs9kFUl#moRZ9;KGy zRw&nM3u&t#U-^QE>n~kj+@1}t+T!7yS~wsNC*j5Vp-f~*uCKi#TwYGoCz<;w#6T|* z%b_OO7#2`pv5eP&qv3ZguuI+RfdG8X8wTZazfO!la&ra|HZRuppKtHJ3}*1|=C_-F z1e;|i?8USe2jxKF&>Pml>~tgEHpsk?YM}uUprS~+)?OqawAg#^?V-ig-Z`>3=d>vi z0lvuxbScMeB>2EV!d`qm&8rajWCS0&-FC($vL*B~D?)0~5n8FoMMe6k`ncsGt~6&m zA8XT+QJ5m{v(rnG^MKgRX3JEzOgn`?YR4A=RO4M+C#k+)*R&tLtSK)CsfS^u>DeEP zaGRUK#z<7>2pqzc(o@2Se&dOsq$tpd;cB$7kg$CYZUZWr0WWKv|GLlP)*{$@WHgGq^1?-x)@Ias@C%K43rlS0AQ7wEEH z4@Uyxy2flYNJ-4TXd`9?XdJ5Qa9x21rw=x0ARAz~fp-mMSXW0$ICKsHDBOd`J% zM>k1fN9FW%SGU*?AZ4~^+{IhRJf#eziT5Pk$pQ$W37qG2aX@7No|CgNWljB>)i=nJ zGcRSO`oMk~VeexOC336O!C!D(wfSrkCrfECr-fMJdD+Pfg1*vs1=LIoX5#>*G<0qY z9&xImB^c8^gb-uUCAJW<&To)1DM!xwP>+Jr&p%N7xC}^_X`-bq>LsA*q|RY&`(ZZ6 zL9Ogsl#&5YwPh;2eCes(xQ+vKfyIYou(E#WHF=*KiuKef!qRwy=s#F|anY+spu)Iq zJC05iot#g79%SJL>GW%SSzP#;(uiVe!$PLx0Er5Mahv<}k!41Mkwm0P1SrywLs>2e zTN|C-^7uotFYyvy7kYtWRcM4-O5H*trwL`+y`5j-!b~bA9!3a4t$02dyQ4bE$DrVU z!b7g;FDg3_g2_4W#{G)8+{{9c?uI0w4ultU7OaohOEPfvpqjl+)N|~!g%=7^LQ}E= z$_+0h7y0Wgf;j_~<3u@@9oqLzy2%lYHC-4rGqA1NHV5FN=G9i^4t7*RGL^BIQ}*g= zOMVvCxQjSIQiS4UWp>J$SdFJG9j(c$5b`G@j70^D$^}em(Wy?~1G=oGYD8el1fzVk z$(426up&am)Lt(;qrj)4FWaP%@K9wP*?gfW-eA6qA;mA;EXyhd8B^8?A1YPA*qKbO z+D-T%TXIPNRKWzC2l>oY;H?BB*3DD35M!z0;OfFCmHx}279oXoos~r@T@?jP)`az` zK0310*SUu^tW9VOan?jZ0BcGP1BaERvIqC?brImC!MY6eK-|W87do4%3>h9=u-(BN z%~THWD)*GiTh5_z!5H=>Uu4mwN&pQXkM)pak30dDx(*4mu6L+i^7AmoZMPrp+Wg6~Ga}4bmW*UyT8^a_ImS`MN z7bUZU)m3*cX2wctTbxg%yn|%8*dN*#TrsCpe(;zcU6Yf=+EM{4z{NI+ibxO{cZr2; zzGmPqMV*+alRLQwZ!|CFX=vA ztXZqfYrOH4LS|4T;<-8n!kH2^GwypfvS9P7Uz>)fDok)qraDK}EtqV=MvY76)cFzF zr`9tXBr1icWp8usr1j!cs*M%Px}nRmRHehBz`fLv3^=S^QR5R$Br}L3*HIZ=yH&{GuUtn@0u87l6nh*3 z0nT#8tTJ3Fni;??apgE(5q^X!RwAq&u!n>bQJT7c$cd$V_8CsmbQX!CUCdIQ@_2f>F>Nl`{7@-aAXmi%713Bp0#DL~`fun_pU?`dB7hod5%h^Qbiu0R z(ZwA3LTbpT$b;2eiw=#2(ysx6YGDd=nG;u{9ck&lCyxRq*9aDN#e|Z=tOFil7{ zN~lf4q8Hc(ji6xWdQC@{Usj;#N}K0sUJjU92P$$}5lb-rR)zEr|*PFi?KXRW8xBpY31WkBO!Z|VWL7e~P#NmERj>u>fp6^}|9pOPb3J~xy0X%Fwu=9I z_wL={^5V1O!Oih4A~tRgmxrUXzjY8a$Jb|roAcY_LuAakTwM8Sz?=z=y~^-N6U0L( zfq|vVZ%d=D0+7-0s?L%Qpt89T_7b>iru&^|?>;31=^Kh+13gxUF^Mck&T3sXd=QOs zVlY)M(#5A#5OBrVDdX8CEfDPu<-}h7w6h;zG$x?zu8|g`^O5?XJMAAk&%S=LvUt#4 zI(@wK?{9wm>hVvVXHQoCwy>zA6~+jHXy}M+itau9Rpv)@PY><1Ng3C~dZO{{NLt9^ z3>7fyY^kzrz=-N<8t|B7j-~yr?D5(|)O1t)i0+sIkcEV2i^AH$jK_`)6bP&z7?!151>f#^2MiPlH zLC;9}Z?W{rtki-av>?@AV2#qfd;=#j60*Wh`e8We8CA&3-TqnsJr5=1W(8H<{h@yB zEL7V9F3BX5HI9XoG*~Po)129{nZ2AkTT}K%&Y_4l(6G#|mByxmhSjuy>jS&Eoux7{@vqL}sd-T^6N1j!hA7J#zK;z=rIygfU!iSR z3Q5>pc)Nocz4-vw9x%;IqN%`S{-AVF0Yf_Lb^KN4g4d$G7N2Z(w}75-*&flcLnl#) zc2FH%H!8B1Nl@twOF5O3X_HtnSx`^<4aCpKv?GuXB&k5-yFH}irg%!RNECLNS*t2j zQP+&dvfGzz`UJfTya3l2+I_lAt4o+8HbSlZ#(glq)Qe=sRx|4CFh@E8Y$+~~W*(Vvl#)4~yzQR#|MRwg%NuPd(+>Bp zFXWj}BIA65&Pjd}&aw>b3O`=z*4%DR2cUcH!TDcmXFDS znOJ@T2f&ZWOwgPYGJSguA8UA$@>j0u3@BSU1%d>JGL+_#0*Ok0nGaf6p&gA$<_vc! zK50ExU4BB7)b$%?M)@#~y@9x{&L~0XCFQ@DYPsS#oj|O_SLPi0`dxQ~@S3xyMn$dp z^m;vxmpAFnw}>EU1VF!MCbA^h!D+nQ+G1W?w3X(k#*{%>jkA`FUHD0S79ef1g}iy$ z5MtkMAp?_A(jEv2ccdX)I4?gKZ^}hECMkj@SQP7lmf0*|qku9O4%d6`zTsB7=B!RP z9F;TFlnfJ}8C`#H|x#+$TZ&` z*RvqqJ{SdPbHXL_5DhDqaa%H!IJsp*Nk`T=`%7?M%Mb%!w>r|H;Y(&V!$Q(pt#D{+ z#3re6Jp|N$uqBZXE&)mx2(N;?7$dK;=GL*&^glgn9pB#Ysz_#GhKh@zr@;;M>@dsy zUgO%yhFXwuSRFZJvf`-Z1oNU!lc~RnIm*OvY5M3=wLhh=CYgOH_<-u8PJWZC<$jZ_ z_-kz`UGC$94iq$Sj>Ta0QjF)@+g$jO)T|XX0&$2^q-N4I$-?oKdKAT6Gnx<(#4bZ; z6$oTRQhfWAtioG^)`|WIU!#GEBGT-e!ua^!uazm@tC||V+6*p?fK@m-ORP>lycWR3 zW~%T?(`N0?a~$rt8Jx7jU1u8f-$|0W;dCi>1#DQx=I8}$zjK$|+ zH@mla9g%$lqzx9PK1ScyRFOrf>~dl`kYy6aU(*m6 zw#}k&z=T9?_`{Gt=m)q8}&DtDi|_{4K!DW+4{EN z;i$PRwPvhM<$Soc-*v<`p4t*R<~ZV-xsb&%X|y$D#0y)~>C7)}tr=dWfR8Kxa3{J_ zZ$`&%T}G$HGS_XWgk$xS(&umODm80^!X8i#tq ze44z7RTFDg$RvL7aoC&2e#$Wn#@!Z*ceZyZ#6k ze^mXTn%qIkhi-z+gLrr62I;#h^#&WJDCQW`aikyb1epq1o`UD)awelMZtlC5RCvvLpE@lz0Q_gQ-}IAr8s6Qp$IUYJFj60vuc{E# z6>&%^-b|cZ-1fyw&arY4`FbydJtWC zgVQqbgDxBot~Lg!lE31}yU^*Aw}WB!h~#f!B-UF=8AnHu(_rkkCpT5Q+&Tuc2L#&P zqgF&u3kSZv15CD`tu+Exph*M{e*Bd25M#DoJLRlzt;CLOf;Me~puXhOkrw zMaM{EP6i?^*+gWxVogDmx^<8EtTzqITLsM*?CL7x6gusb^UGn6`VUJ#YCZn-uYcY8 zMeBcVFL5MD&YjwwA_eKld@7xkUSQ70r<(?j7}tQ>aaX-76xZ(weZc7{)1-&cvzZVV zrosml`ufCv(R}yzW;jpqS;V>D>gr(p2PB3YzJt(8$EIY;TbwXOLh)fD1I|cQcrY@; z%)}^s!;rSJ_u-OAZfb}Xt!gv$r+`GD4`B;c-qmo0Zzj?d4N4){=_XtD|K7rP-+v)Gp_TVfWjC6yqs6lEksbO@9rpXkt#;t!N z0`*}>w_dLg1x)+XD+#nPb01>6(d!&W8?Q5;QzrxjF$t=kdV0oj>+ffR!n(UN$7ihU8<;o$`V zgPD^=xc=({#X2g>EaU@eaGU-3!S6-UnAjkR)GbM)|>mdYI+y_3aI{*c)uC zfON@B!b&{rUonG7-(;0wQ?7t5F=<^*AcJYr$QG?TvQuU<>LZS9@I&94C?#TseHDE( z&Asfo)+`I7+Zoyb%(G%Jw$_ZldhC_*3!T$)69G4G@d1n2db*k9BP4%7RYhs5&+~oI9*+x z!04kDJpa4R_3yS@+s~gLjt;Npm*?hTvtMwFyS1^kxAx78&5g|$Yk$Ij|G2aEYHw@% zm6zMw+;4re_3CJUZTH*F{U;Cqr+58$Wqw(BD(IaT7`s8by4oL~ba7vxQ$O-gw&xGq zZCs4&&HZTL|I|91e;C_+csSpB_?Z+ESuz2+HI(wHP%DZLXaw`pG&Bg}p!Abf%5fP? z%yTFUsFoQ8@}1@dM)Mw#n?08J`s=TEcDMidr#Z;ARH%*5=jPD(lZ8(2$>G9}j~^ba z9OCcIXFvV6b%JY-4;Om#@{7nNjLFh7bRgU_~fl9=!kkc`EYhk!w+zsjk=fA22O^Tmk{5rr5{>t``m6lUBRx!isY*L z^64+W_${wnx|;i!fAx0R$e6q5qZ_pZR#3G!0xMc!LLUydcY@h6o3x*=T_!XnugK^U^t+;36aLc#)L%$l5Zfab;9s_SjQLY1lkI zeRKbU`UeNCg^rFHDusl?MPz)|dh^DS_b)ohJ_SpcGPQ%zJeKyqoDRS+oeugZ=ff7J z9k}>gYYa5C$1AOs!`8}KdvOl*@FTwbv_f(q@y#PN`giklb3$Bk;YZq2b@8KoV5WxD zxVhB*Gk}mBozkE7jVPTv5?_DP53UgKX89`(7v;`U2`ZCUYxbmwIQ^)hd~5Z~C}qAa8s!ni^8q>kahF%XKP>?{A)WMa{rEv_Eyj8bVXAJC=l>VsP&u zBC8-M!l8ul`J@XjUi$?MZg4sfSAR`xTaBLp(qEI>s$Mg>zHd^Ns}{jKPN$=IOH81& z;O*>>sPyQjAJRgd4^DYqkYpKj)Q)(YWO$&Kqu(n-Zh}Q!A9nx;Ji8+srgF}+!#Frc z?a{d39i2e$3IGL#KfD3F;;R+qT8B1fG$fRtnvtRLN3Aa=4$B8t_3D(nGQeeY?3SI0 zgLfbigaKCCejM(DI3Trx^xnoD*&Pa~W9iIyXPLapo{+N3Z#QrvuMiq6=+G_@!}GC# zUVZ@Cal_ZXVYv`a3t~^AXl|`C+ z!vJDk6|U(KwdVAleyv!TpLoHTbSuMfl$L95;8)H$UVCBXC8t1=H8_&vI};rU#JRgm zQ^H8)T8axcncseQ?QyVBN}c|zfa2f_k|om^WH^D zkzcxO8&oV#RD_Y|4rusRR4bnZK~ms76*TFld8bBq;V~M{W{`;q%?(}ukkx+Kzd0XD zmgVqX<}lo!7r6a`7S*dIKq#U7q^UIIOoGu!B!HT>OnzjP#R&~v3W&CBWFsg}!d>P) zoeGDvN~cCgyvp(UaP&%~Ip_s3w_#yULV-r6Ou|5+jV0rhDY9LFDgw&9wwx;D8tyYV zw7^y6W+IZIIA^$6S>7I#1st05BHDOXLPUbs)pATWswMfHQ#?DWTa^hcKxH~DhCQDZ zD@c(qBV}oAuy%Svp>mfgX9{We+)gNI!M?}N^6JTG!Shsxg=G>!5S}3%uUyHo#|djW zsJj2?l@g)#S#b%I2*+}W;Ud&6*ViYlhzVvwLfNu9W?>JeV38-*i$xCHtRszazQ-;EZe+Z@soMzMiBL}pw@tB?>%)w0HF(or<+LwXwq94PBn}UH)$4zy`n|lJU16+Vnou z&De-^dtc3!0R#ekv=kE%#6}8z6TT&!q9^`waz=F+pq=ys>VPjaTHbSQcp1kK!I}y$ zB|2^7`)~p(6it)grSmUyk(5V&ke)ZOvo`biup>2-J9BMSg{1HH4rrl`?0e^eV0owB zfws6Cu5OlYAO}}l-D{elg7lU5OUMPZ1ngZd0Y5sa*z!UvwwEQ%u-l{K+rdTe2)wx$15^~5W zF;GyoTuwF^%crFrYEU`l{ACQ9FA0S&18eXhvrtMLzxoFfM_{0Wo(2L5g=&e!gbkh6 zmR%#b=E4?VoesF8)^B0}8MC0!NR4Zm_f)$F>&fDqsuHC*X`NUGQWvQ_Q^urxP4b4= zYf?Y|_fgJr#%u9J7Aja$+uhy;Jp%NvlLy=KH}_97lAN^9%Q z+eK042`OH=mLyK2;v2Wh1YcQy!7?6eae+quQ)*Ia(C-5F>aHZmXWcp6N3A5L8U`Z0 zLpCmGLOCtLZzM1H0f|r~J&3oNVLrO$WN?pKHvgLV0|N_*YvPcCB)~l_aL8jD_3CDI zwSFMrEp8EF*Hjh;szcuRA~8NPC?kz+WGoye&&ul1_{AqMe2}CsGTLB>W|PNC*^5xj z=8*-$lv=ad$fF^Tsa5l%oKg|PIzHJqDBPl9=#90vwrlEOz)1C&M~<>M-6{|6 zDh*_r*KxA3GB4!?P_nR@zO%b|w6VFf`D$bH)%w=v-qGIni|@ByeG4*dUn9X$FUbS4 z#0^VCB_ISZ{#i57d7ImT0@u{gN6I=A<~4iVne>Q^J{%dT^S`S+Y1#`2qaK8fb0?)y zkf8y?<(iDU6(;M&85Y{CZX~6?a_^@OXm{-zDgq?Ib59PaeIQWe?`9 zyC3|)w^tonaH2JgEV`ub*R{=2GwQ&%tmP>_5-#DpF5rxI8`Qa3m7PiBFZ3rv^R&e@ z!?`FAi4LTp+~~#Nc!cO?2WiwAFdefTl5K^Tfq)-Ph)anPXNe?FUP6uO4M!W*b5i9i zw*oEeOmnTw1c^XmG$L*Qe!c!tFNzY)&A1;Wl%|K* zre$hym?K_?8Q(Yc_35No0^bOVTfN_&ah5pS!Ps#hO@K}jQp&A?ZQA$> zxu4(#$Tlk`%5ZS!d*@8DP3a4VLWGn(8C)_A*<;3qu*n$HM*LE!CIrdGhW1iWuwuZU$XQ+1dLRA;&wykF_L&7T_e{O+Ov=^DJ+n^R)r+XSybtHg-jX)5k<@Wsp zMOJX{1TdC)X3~oQ06CE!Ur5)k9#kc5L&Vc87@E#*Y!r?B#=b*)(ri2tOi>Ez&J20> z{Xo={AvF$ISsGiaV3f-uLFB|qAgRZ)q*HSt!Dn+8H&%aK`K-O1ZMCj&qj2SO{8vg^ zruQ&gi;EzaXA3{E6#m~`l>}4ON%vGG;}WDr+be9oSP|hslaZ9J<5{#ITOnS}Y&T>? zF@6xjurLCD7l%*-Y9ijMkOp~I{iJxK2hv66M#7SshZ5zOPvT6~tUKj&bv5OhLu)*R zgaqO@()fAPnqoLJio-TH+<*?$l>i2Y{3Hf*rS0>!bZ^&aDXEt=Thpz#Tk~m7WPq~m zRG?W=Dhm2BB{suMWL83m$^7gG%- zCWEY!+qDJmlHPg=Tp6KRwZNa8l}B88)v#`~(>t#kLtAdt5VoqARW1+NQg@&;h^h!; zc!i3e+HTwyG?qNeDIUXdji-!q0|5-o7}y2(f>Z3LMr;KcQ|~+a zf7~>x#mY1Sxr_2{`cYe!-9II7a_;nMIK2K@*%D7&HLbb-ZOo_19pR)UYRoJQs$td1 zr9b!NktU`QiBYjSZLx!UHVwcPEe_yQ`>JXeqj#uEZk_+nfS!obgo`y`G{p4#8o0NM zw+VIW+W0Kx$--!Gr*v$xE0`X2$qC+Cz_lCAIU1$LrhDycRF#C zv?lf}gk{__z76rb*!!_#^~LULO?Mv?5Vso@R~>v7RmGhG{;48r3t!^bLNuGxy#~3N z=80?&%<2$hjJzRj4*xCnw{){|;s1^Bu^EoRO^zvpN#`4h_?gGSg}65hd2<(vniVK+ z!9n;H<9uMrxYZP1(x?j$E=Q~Q$EprgTN8?uwQ%=ECevOeq9l+gh7s+ot^dc`x0^>> zulDxWUc3PO5<%pw!4&-@cqj=Ss;kSWp{bP-zT|aZ?`*8?Zv-(-*E}%q{{HO?$iCoZlLyak$anc!rK6di_Le4pidT>Zzb^+twc;H0E zd$6JqUTZ|dVCL{H*#F+c6s}YWp|G4hSb?D_{DwD~jI@h@)91Cp6HuTqh`=_3b5s*1 z)N%}A4QLpSkGzK@gdsXT!eF`d!$WX!1z`C(JP2o~aU_8+2wu69YjYeVQ*iH#=MwNq z`bc_6*>vVmvx{|X)GQL*MBGnkOPW8BsK!H}Gqhj_GE{@3zb#Ej8@iJ1op549t{04W z-+~P7BK4OfrwUabwUT_yUSxIkKQ{k#w6=HjeCx&LtF@P#$iGutZ2k4_?&hm~FS_;W z`SzkDU?7Oxcak$eKXfl{<&dhXb@FK2L>Bz-_>vd5TW7 zZ!C>1>EA+{s~>U5g$T{F-06&=C(s%E_b!{3m@W}?r=LCD*=xxpcz%uLs%fq;te84* za#Kuji^6vUL2?aN!1#kEnA*7qAl9G(17)|C{3qo412CT?xf@AjLS9a>P(uED8-WI|9?<70c@buIZ%U$1&&wH(9 ze?-bONmb|1IMO3WD-}l!@ zVTbdgGM!dV$YEn^SMw%jU%p3#{PymVd?eA}2WWC$lA`;Vj842U6xn;CB+ggQw|84B z8?A>Azi(~5YIPpQ7SYgWi>-}qq#I$nM;2zD)lgwaD?I$Zz8uSvmZiT3{O^O`K79B; zr#o3|i{UB$!ZKyr;MNi|w4Yqu_8?dw;h$(DMT$qY3@ejeBToKKQ(Z~PsHrmdcGqQ^ z{tNjQ|4U!lggyB$V+XB;$Mz*o0@65IG33kC%$Vcz{>2400YwS%NhSl4l$?U@!+HEq z9-+y!lrEXd@o*uwyz<#^TfL!{;#`4S9uVcIX=)tOXIe@+4RX@J*ukkGitU_E^G=~f94X`cz z4z*Qf`uQvzYV~#JA#DS-KRTpdZ#wekd*rrcHn_rjlek;l0;qEs7PYL=nT#vXre8Es zF}%zZ6U{1zXA}&oGR__g(*7oSZz^_@BQEoHc&Pa`aJJ;}=3+-gMh@m)y^BLFgv9*Q zLc~2Z-!zsQsod@vEFnmD5G#`X2g879V`Yb`8{aBBRMH2{g2jvQ^( z`)+^!Jxa3@g5M(KtBDTz+%gOW5?_0rCT@o8^e`YtVp<^yUOkDatVt*oTF*9*)Q0+~s- z;;BYPkP1h9GRB1;kYfY|Jr}`HGaMu%2jrGwSAzOr#@xPVvIzsp5)vo~e2vw0gm3!Z zFiU8x3It-^Cm@aYBUC=Q#m^plNXgJ#h+kQ1_N*L4wxePV2_2D_csk1DZWrn-gR9eF z@Jz@y#0KubkyH2Z+pO(>M|1z^o3%Z05umRHq9$;A^hAzDW>gA-(5eAdsf(;_Dm3;1 zf!o^I=`bG_;tX|W#XzFF0ox&nv>VHHje>1)sPLU2P>aY(G{lqNbx_ zpl`h(x_c~A=*xiPbuhjl!U>}|1cMmYN;ZLDCGNQ!Zlo<5i>zDX!(IyC5S~B;(5unE zyuLx29VvLw7M%+jlqdv}9o^SguqNepw(sdY+T=J)%cflIFxTKyzX1zX)X1TMASZCNgf z;xUFttpX*8y}uMTYuZXI3YM#u8e+|PkyQd8G@rvm>OVODqyMFf7nGF(X7cVHZsx40 zGWd{Npe{@xt;+#T6KQ|%I5s(o;_#NhI^T9GsB=P0&j>*eU4nF?u`)?D+jmJ7IZsu0 zy-|8sT3Wu4nT>}X=i$mHne&vF28)NiP_d#0dO?T?=4D>PVOK)=!LPE<%eQE#ATjP7 zCA`DNUeH$kagwz8raAig1xliPG$Rt}$#5t`a-{criYe-kAb~?QJp!vzJkkbmku8?!5fB0N( z(}AXSxP5zrz2EQ*{w#m03pQTftPa4qOhqt2>dfQx4fCkb!@X~NHnci)LItyVZu%0tL-KudtJHlFY!mL+XeJEi?Ek{pHaru@f$4 z#eAS-!KzJ)xCZ(n6NR_$MrJUb*}&_z1v$*6anC% z;m}jk_8Rzh-~{~!3#9_2j2Iz$NhVffuDQiF2CKNbIYkoOhp=@c&+W~rkgUjFt7|vWH6mtR%sGO`kZC=NF_s9htA%@c4pBz;vcTgNO@Mf zV7no{z~S*@dsjEGQP$Xq;HVz^)Gsd z{QLq$AU}4PCfl~p@~`{?WCT}0;R_V&XSaWeo9>^7n1G_pL{aSa2gKw)FR0_~FiB@7 zH&j#-WcGq7)-co;Ykxf2TmNqJMO4-MUY@`fB#Pi6xn*U&z8UZP4+pq@cPx(`t}CnSHJw_ zuPAffdZT+gG0sM($om*RdZJMoTq#>pF%5pgTkK2x!d5utjsuxu<|zh8p!g0PSl1{U z!CPqzFa~xi=YrW1?>Wpa1ZK|a@w3&Hm7fNrLpD(Q&l*)&$x7Wqp6GG75U^Vvfp9L| zvSR)&W;*xlVvSqq5INFdD#pB16Lb?Ooe2-H#&kNgR#gd+tJO1g28IdkY2O+U1Oz@* zZ2a)unSnp;bq?|_9VP+!NBC$k07Iy#NS29;3TXrT5}dAcE#>31kz>V zV_oRW@L%F)(=KEwoCY|~y~;&6#iU;;C=ha~O1qmsrGr-HuP2rpwl81GPas@&aV z1#}8z{a;;$cXHe(pXmrLvdM_z01LNVw&D)$h`4ScNN|j)!$|;ol(|yB`sz2L62Yv+ z6aK4b*dvf=n-j|%QDXYyKeFQZQf9+wly_B@OR>?FjRWV5EroX(9$zH&$K|7DiFc!= zzT8#~MY{i;S>C9pcTt6xm|SsG|Ik3Dk?F#4=eF~tDn%iRnL@j-G8|fo$a0X6 zLY+xDAujd;0~95ut=|RiPNE!_`%$ldBDYakb%0xl2q_Fk)23W$%VmDpjFP@MSOq6Q z`{C-Q`yP>{TA*eD*+8Cy#{h&Q#(fR!NShD*F|l;+i6nU&<3Be>Iet(O9U{%X~~2~pSxf&tO0d`{i0lM zkz3oQRth(ly&yMGrMe7W8xlJmdva_Q7n|tNKj6v}#f2(;%B&j~;Dgp4hH-T{8Jwb6R|r1i_kUx87OR!HW)GV9v{R3a6+ zj7S$9j&fVPIR5;QW>l+v@P_jyn7}IFzrbQy{zsPoNy`7obV*(w*sxiaH@eQO4gM3Z z<>0J-D*WT#XkL`FLrWa6c8-yAe{mM^1OOIpuhjTOBrm!5)cO^ZC2eF6#Bna7cQO#M z5wo1oh^j5wSTM~GDgb6LtwT<274KmFw-?07?}7Q?G+kW<|G~lydRc)1Os2o_)&ZQi zu5HS^a_wUEnxR%M-^${FAnVDMs^fP9xm{9U0P+)@IS{^aw7I*>F!Rlw{qI)i7B0tU z0KWaQ`+jhFd&z6gPu^n3{~AVEA5jJfWA8zZnNCqQf>W6Ln@2CV_Fk^-uP3d&zUtxL z?+fgTgYT1bT>`9e>vAv_X4c%``aW*DTK{fsH$oP*H$*Ln^8H0K#LL(*W`$pR>OPhF#V8sU%QT4jn{ zBkWe?Es%$h6$*P)(s{N0YE$$oVKy172BFm}SlWX`rs_DI*4chGa8f(`GjMM8NR6;_ zqPQ{<-rqB|Eon7D0XzWo5Ah8G?tYK4?Z^w!IKq0hQcF(}?_ui{kmZBd!d&=#pNk5` z1u0BrMS>u{6#UmBC?SwY+fWL!upa6IoY`e)0%ipX(K2+$jYk+#w=uE2Vr zGPowW2x*7m1@<4dPyh24RLllVSw6l!J=M$bqz<&Zldhyx*Y8wVAx?2qHz0s#to5~c zQ~?H`KL4CVIES!0us*_ddkoNmbQ$Pn;-(})nbg%Ug|T)QuB@d|fpJkg)8=HE|BHo3`0!+PJT?ifF{p7Xtb)?wEb@ z@KDqv{>MrvcVXk98P__130HjmxN!UOm|i0&bx=2{gkpuLDF$4JE>p$qh=&sy9oA{D zbPnp^!}F8Y!52&a{^oCw zJI@{*_7+=m-`DnPwIcBw&H z6l)y(h9z6`X!XzYAb0_YTKN|W^#8}+yLQKQBWI)E^($I3 z=XBE+DcLhg)-bj-G$q-}=)xn)PBNs;mm*tK$Gi=jl&!J--_P^Z1t?%+Z#E@6Z&udf zB-ZY|fkL5BRVWk+1&{==)+#uGr0S6pM0S48co5#Q{uCtacY(pL1qQ^NU>tXJMu!XQ zcQ$YIwqK5&d=gsN2ltR+GzH6L=%K_X?*EJ1UeE-v4COi&%UGmh&T2#Pid2WN5$4g! zsb;|JI5`@5#zN*OFDQi6>pKmWR{wxw-S7+oMs%GV5Y0M(&E za_?@#65=`IWjM_A39Mqqk?tZmq5zYSRvMtk%IEWbLWkdL@=mo49&h9Smp{J0JYKy4#k={rds6R>SH-5%+~Ao2?m{HO%oA=9msuAJ zlFR8mS7q`F$NcYs`+uGP1R-gQh;|4AQ4$m{#PFHUMN*7(IV@I^cTj4**!Ec3r$1L( znqddHGYskORUHr*07mQ=MpY0h+pc1{QqkY%Q=o^};+P~v}YR)8f?(UFmdS^ z+r<)625yS=BIidA9>)nJ^pX2@h7*2;(Yie1TpW&DP)eOwvX$B$I%`>O0yy3t&%IT> zFB~fqNLL1hUs$a`z%U@twm(oWCIdfvu^Njg+wmwTsOUD64ftexb8N<99!eR>$=HbiZf% zC>g2ju~vZJcxPt`K4;?!*$MTH_Rz<+?sQf8`{AiWm6Ua-K`3vkQkaPxSuu{pf{i-T zELqu0(p%fAeLovr)~0x9n-K!HVRQ8;LRQ#41ywPiRVj%={6+5x6K9p+>P%lNi-AE* zvuux{pG0BQP8qRXv-Ob{i$ym?U61}B7iB6V)!=d?Ef+(tXVlcGcF}RtkyIA$7KA2U zBYJIZ^U3;4BmsGvGTgm*_|?Pzfbaz3L=9PrkK%(UvG1`l0wu<$H*ek^+ar!7*%OW% z+SAShK7fV2?%kE8yR7-C{rj2y`$zltPx>#JKHyXagpU7d1v)Q$2Ae@oU;aRXCLIPZ zZ=e-78DAwWC&VPpY^b2=P9Sy1XQ(aTCQExf9_v3|$$Uv=)wKqpGV(fQvc%X4)PzjG zp2c`SV>XoDx_tEvllcU3zObTZP|?_Br8QSpZ8oNneRX@ZFiLYk%MG^F+nWtReGA(^ zG_4#fnGd@S#r|lYLr=8MKt~!)b(^UAQnu3Di%r|b?RJ22;tn+>IpTZ%S#P2k%egun zZOpvU7&s|}ewI-FnH&?kT#g6`l?*f+=uoycH?_}YA4r+^!@Z(lANoqm5^b$oVBSk| zXE$iX*$G)Z^K{}Kw`8Ho@ow>svy<~@qP77a_PyYhgyC)_&B$vDhB6% zDR~&3TE28_>F%RvP&TOJVnzpLjIrn7A0a3yv5sK?7U7=!kq9r3)tYR4rWEP1*d0MN z5;=gWM{Hg;a?oIAvqWsK>1X_7&%@{tt>rW}x9>0i>dQEzI$=E(M@K*cW8%HWqnTRL zne-m`3mTo(QUNB|0uII2Jf@9J$kB=@D`AE`#8|1)O~O{l?EdBXw|n&S7c@8cVQ`Ag zR)$fU1Z#ckj>FP#qEnW%z_z#k9mh)0gDouo@`V;EBD+Swob|p zLoRM6EZ1jFb|O-#lsr;!Nr|M>jhaVWS88;f;w60}u$2$zSSn7r7+oIGeZ>`MY6Fi# zG_-YN3E`F4Nn>erjynz_w|0z1is6_^1Zw#c%8ClGQ&OnZ0GQ-R217*G=bd82o#?$P zSQ>!Fbn>AOkvp*n{RqZu?&Inh)PO|{`{1`pI5mez_eWYcoV~0l{^b;+-EJCO52VxGyZwNPSPVxt`KDIQ81!= zWU|{u0K%kYa}R*UEUT`1HM(GFH6~kghA+LE&M%(Q?y~6?Q-mD_mG-Ce?YrN}8BqD@ z1`%v}iEK!vpOn^iSp2~)QwbYl3; z?JmPU8DFGYX=s&p5#uFb%h511KDQSzM{S}`U-UN(x^h;lhWhG>m8=O~HEUhLVx+C* zVw%;Wak4ez!tut8n+ytWE;ih^w0%pAIS-p~H*Lc9D!|7y+5VR&qF>ykr?v83&?z9H zHXB@O^?XhSmt}%$G2xF|5dltI)m6RuK>8ClsjqI%ylC?73QM_?Ncy#~q1*IwWs{e* z*PvX{x^ib+>}ZQPG=`yssb>UJwugRe8E2{^7Uqw73~DyhiWkO2VIsX**gBj? zz0md;EsVdzWTGy=T1C4&8cECT;cU+TTiVa}?QC}17F|}irDvQAg9$C`s;;Wv{vLbp z-*MwpB0o5nmkP=|u8xHATP5zc{uDZ4j}d$B@4fu&={U}}VV5)UH@mJFWgT2DF-v^8 z&}@*V>T#o2jgDBQr11waPBQgdAY6-oJ9_&Ggx$hli*P|R3Q>M{nkDa|_Cr}D8chmFP{sg?GhctD~lh+1v zPy~k&mQX^1i{Kd}I5}{#&9~yLRN;2CPzF>`tUf_>bL=GIbyt~xPM;DeP#OVSL~E~xJ*z_5tIo5U zi*#1{vMTu&wZE8_3N49-1v`ldDZT)U1+W#rF7#}qa98~afu#sy8|)BH@@aK~>}>zB zBbno$zn^T6b}qjAWck*w$6Y>0U&4Y=J7YQ-s|BBmfJWtJPG$V)h4|?jm=;O`t${wb z5lUgk0C>h+>Y{UHh3NB#6k#NV*YZl7g=9Lm;@GJZX9!u`58H{X*XN<+%$#iUJbSMD zI%~sc#2!g1TBaJo#VPxjxGPhvwzW|K1XL}41Har762Uxt@ofFcZ_r)|%3Cep7Hv1T zvOcKGO^A+3^J9U~i-)+DO-?U|y|oWQs0q;#YJMyb`f~ljE{}-)2BTAgaumw9?Y5sk zdbIV>HX1*O7^$%cv4ctc=m`Dunw@s2&S&2n{flIxfMn(4YmLK{!-Dc}*haIpI8(LZb0IH3GB59LlD&YK5B~iGY0ZZi5+u7+QXH-9GYI;9aOAR7?0}=3RYwdQJ zJO`Rmpy^=L_#saFq2SVp($>}%r|qVOJA(?v;gYTCF%Er6_DhY`4v$qrFoUr3EwZhF z{QfV`Q!)uz9;Wr9+1}hrr?L4G=dS%-$fmr3i~>gpTDMP+5#DnC8atfj2wJiMCgbwi zn54rwd_$)Aaxi^c{60TD_1JSk4MS)C`v4B>)>qh$y?Do-g!f>#z4`Ok^#OM6Yus0TQN0Ghw=S}2%@&cJYPyY<3}G;rQBBz_(~6UQ zlQXTRoNRptH<=KS^LLV+&ct2aP||Q1OQk5TAWm5CKPvSt9dk z)oZkC?$gS>pU03}dQdO-U(e1NEhUA2m1Eb6ro72m8|*D)p|Rel8_W>_Ing#Si~>hR zo~%*x1)5z~Zf)p`L~<(qW?gH#QP;*^CyZv@JV4|wTaDFYGEjdr3UdR4ilf?nU>>!% zjmj9}fu?=*8x@^T25N7(Xg8`SUK*&sX*!@$-MMd|_6t1+Q6#}Lsyn|9)CRQ%Iv)hX zVF>4R*bb}Y7nO#En9AM09bRatVh`KVG3`K~%{#!mELMXJ2!o3C`t1sF6D46MB_MPZ z@RvM?gU*N#AfnWoe<{VSK_VUXf~BlEax9{iHE@_fk?1^BpS$kmE0)4~@$V z4X;Q#rEi7Inq{b}44O}fY)N_bCF(hvEbL3(>{wwZkq!?8;rbUL|A_A7JCcS2^FD+T znA-}FZkhj^v+eca0VS8~1ND$sa$Nz& z$}~Yhe>~Q7_;Lk}@uE(4kU8VDgw#;+CVWhb4fw;|ZdK?5t_UwMBC+5wFu6>vqdXG{ z&?AK<<*o$PnjV_4P;eO7iz!lfz`=JF?wt0Ccx(A4p!Uw*YJ9VncwgW~+3Q-xXl4Vkv_&z)&m^7-W#ytFlep zz!YV%MOmr0RZi5lmZ$5*JnZt*5y;Q!JyX+PHtv=g&LEF0kfKeZ}* z!qi@o?ld>h0J_~R-q#QI*o9y*IcwHvf;aj*_+gYI*j_Sh$&HX#F?1@`ht-qW{uFU6 zS1>~=)dyz%^5XR9)bdi7>!_%*bFn@^Fz$1qVd;t``@lWcOUpNr-TEq0l|}miV(F63 z_+3G?vS>d*q#Kz}A0YScmGDBzK5$?1x?rxphgmEa>jU)H>AN3K&kwEwl#BHNdVow> z=QF+Ta2TngWFNS%PmggXoq4n7y9ui%oj<#Mvp`2i7ing@60#628AHuf}PFRUm;C6Ziv@>_C0!| zDAoi7?FwOIY6YG-H{aqFD7Gpzf$#2KoNh^`p9nE~BJ)>d5}t^*8=bz+S>dDpOTH?c zN|deK#WonMM>|GVdadaVN7fOzogd@>^KBcONU#N6f}D(&T}kWkf=?zXnQNWo-Y!8v zqCE8ylerfw{Fe7^Z=d&=3RxuEVRj8-L>`}C(azC>udSncequA|5il?YojEK^E$opBjV06mB&kGu1**+KVfh79L ztM!b6EZNzx+{Yk3(^DjmW1uAZ3Di<55QhZc>8(4wPlb+#R1+RwHYmxs6EaIj4ofEZ zMR%Hd7=gPHr`XNd>0A$KY%-m{!!4}GC98BV6MIAt#rcl@quVIGg|^O)9)UQIajP~) z#{V>QmjI}bh2X7m_l-=1mJ|VPQJzMOtNca=F?IiRQVk4QR^b52EKV_F) zBC$mJOOEo6%wvIsJaM@Dor3~A;jcSPqKMOY-y;9e$s>)N$Uy=h!}mlu_Y;AmeZRD zrx%YXJ`S_*@BDn%R!$JWMJ$;h}Q|d!(BAiit%8^QKoT zW|w-dSi-4PnK%Jlg}`+|!qDN|4vNA*uB~nT_3`HJgXb@|$RcUr9O}wF>?O=#*wCo> zplRM^JY6UZ73~*UweJ@8X~OXugLx33ki!;I0&=mW^ z_cfNP+f>OwY13nA?;J+}=%&Z$sJbMT@fXUR;=A00BYcR0Y6nQEffRog?GToD`r`=~ zuZATT7RGJW7qVgXN=!zgRW%69!>zT6KP2K}C)v6e+VWRw`=k=yK&w~qpFA&Ufa_+8 z-vNC2fjueTWFD5j4>b!JG|nR7X%Jm;6D5maV;r{v`Ct}Gd;HP%_j@boSPcE6O48CovqT(i0AK(oXq8Xh_yCaodM75|kiFn_Uf zFW+IFy4__HXI`>M2Z5lH{EjLa21!#w>@=ZqS&x_miqxr9CVn^Q_}M8~M|Hkl3&99l zLz)+aD%O;3r?oJFjF+15q*2GF=NM(&d~n4?v+mnqXZvDP)lSlFt^mk&zP;=3ys`sU z##Am+tXA&9zj}E-1$QCE>FN2qhjKp}qHCP#0M~w?2RN#G#06*k&in0CPDr-6imXPs zUqE;WmD%`kV^~t3pq>L2F(YOCXnZ*pjF?XeFq5&Vs0w$R)EdIlT>^4?=&i2UX@du( zWmX8`)!a1mJ$0CwngWF(0!2A=P%a6?GWJYvt%EV2uI)np6=9w6D())(yINffkR`N$ zrYUu{{4WiH_C4uhK+V6V50>Adt`cKXbi*+cx9n^)KP4vk#H z!gThauslD)psm~kMaArt0XF^QCAY^;sqc`eLRXfaZY8M$m=)_v&xf{})|(30R1W|$ z0{Z2vH2m42q%IBBuTjiACVn#BF%Zgh%pXC$~U-l7De}k~?Cnc2Fv<%nEa?ZWu2J-N`LJL~lx@d4PCeO$22V?Qkh4 z);FKT!0`&QxJrg)(SX8NtX%(kwK(LB5wB1+NR(scBaDxEDHA2+l%g4P^>DxAZvH0n#=`ReHO)!N#{=?i9TFk;^x zeTH`B+$qxOf3ft-9l6EAhF^7QCXps?OM1;Gvk!143j6DPMcmCc`K~m!8jF;Rx*f3N&?A!U=W6b@cE6)c?YLiS73FXFcKOeG+Ay4Vl`C=@P(eCUskdZ7h=`k{^uQ&(giMcIh1`bn1F<<%A`reBLZcYzd$-a$H?UD`(qwGD6zW!?W z@mJ5Dzj(MSDe*CNeJWR-;eMR_>8mHtzr>f%)S?d7m22UrILvP!e`EPh%q%uL+u`gg z_jzah1a+IX2PEyyp^v0YvLC1mGPI`Xe0|u}1XnP2tde<}vu-ewv2i<>YeA7=L$0AY z#0>h29GySI)gW~Ik;g`Hj3q6ah80h;edG`#W}3&$AW!*xY#(=gSj_1@9Ge4U& z!Ws0hIQBS0CtG0p-_)l1;^w(6$UVtHiV==-Z3sDJJXahiv)MUhI&(b|4-s#VQb5m7 zaD8_VF!d!#dn~W9b+0awNuOHK!!srs5L&SjE#OxHd4yAx6m#l&)TZ)>Cl`YEEmgVffjsQ@YY7UE_)xsh-5k@_M`D1=Ht<8jJQ0l zfe5#_M36S|U?Ywvy|Ljb#zCXTDZ)dfUJ4mCWD{I3djM~!OIYWkS{n_!lJgRwVyK2F zeZT^r2KC=&XEtk8(LS9J9x7E`?B+V>zvWEx>Hh`Y-4ojB{2dmdD2u*CJUpu z5VuDK$L}lmPS5Bx(9D@1z8`NT$4 ziNg3{cqFD3`my;e!Mm2E0)qv&Z%N(gEwk6Me4&Y{vN%sl_CWroF>TpXCxe-K+69 zVezlA&1TzmQPJ6})Dk*tgQ7Dx3g?iLN@ zt=?l-BPC)OgarSFX62#aMNs(zVZk70mYzXSRz?!aL`dm90ojUa5RjGW07~x_kfg6! zL57YifN7c=IOR{zM<_i#Iz8jTp5?m7A=d}5)r3UxHhZ{ukT~qi$4?%={7k57Y~cMrb>Dya@Z~zbeA=k8{$%rO`PQiR;NiEZ^%-h)xX1DU;ana{D2v=+ zl4^*dkU||l88gKi#DB5T{QhDC-(F2G_L^UbKfWW7=uNwT^a3S(B{xOoNbq0z2F5Pp6`x^+Q@#fm=$T0-sR|IV0e&*4C(;fMC)#iz<*!za#fAeXtPtRu zTL&D&&b(~C=)zQpQ>s65F(Q>Q!Kr?n@gnCHcnI6Z9dtZ<70-VK$OE-D6Ga+zGr|K9 z*sh+o6O;r07zPP9uI*`N=*VUJKzoQ{n<;0H>{fD_Dkcd@fyEqui!tz0Z0N}{6N~8X zAE?rR_abHSc@KCtORM7_$z`#VBY5=Bia1erFaZ~aA(vqj#_Sz8)M15W6i6TgS-D*U zp-Mrm2BftNM2W4*tIUL1y0YliBiZxbj=lKG+FQP7i1M*p9=2I?U3O@%qTZ zj<9;dZSGtU^J4b{+#1d`9}Z2zJK>lVH@xT96omY%ug_qO7YR~>S_?tw9<%RIy%u-J zC#lP1g0%7i2&W)uU2_ZsCaA%((^jg#h1uL~hDL`gg2a$4uaybb-v+>ZgJ{IdX^-x} zcm1-8$YxI$jF;bPqC&UNDu*X>z`-@zAJ;6Dp+YAeDEqb&;bZK{ddPMtY9Q7TJs>5X zzg9ei&z&P@%zL9jo*gh=2Cds;7g!RU?jCbspk$*R@HGb)N8en zO8BtkL%iuzIXIZ48qGX{(@dN4(o4{zC-8!3>6QY5I%D=Uxk`9_m;%) zYjCz7OBla7q&b9*VC=1O0816uhp+@|XLJz$fd!((gH){*Ck7Z9@~3zlFVns3DAQYy z@jv9Wb`Pg$T%ew=KMv5wXwQ=g0AmY)BHd~M#pL2yQEX1JM#G$v(O9xaS4=fDGInn!ma1+Omo$?w zX-A%|@hjjH$B248{vN=rfK70vn6;oZarE>rMlWNGfL~@6n32LY16Vn@<^Wy{j7xDL>t z0iUdF-1{?tjYI1qV_^E~7RgPrgicmtZ>x04*8?OB(OR0G1O?oF>=w%&V zmqOq~Xpe!ED!5YwmIKmrzzr@_d}?8Nbiaf6XwBxrYwm8iBVd+}6!cAM^+XtFJDEO* zG`Ba?M~9H|$%{6?CB-&SCM%nPLWQ&ql*=;Ug_<2mD#c4Ra8C@}SEgXpzCt}*LpHlk zQ09Zn<1@(sQh0~W!r2Ka*(;Z@m#}{m3-lE0W2K(rt@CU~2sO>t&(HVXDeg3}s)gGD zI(5ae4bx3Fec<4;Twdbj<1gl}xPbGS|Kjq1^F2PuV(f@1B~DM&Cf4%t{mNWc5G;;a zL^8GU8U8=;hW@1cD18(kEec?#IVO`*!13kE<@u2!AxTQOxrA+5f|_aOwH0pNpacv# zy-EoBM^rACm3p_(n2sM@X%4-A7EXNY!a|o~sVGjjGl_p+XMI*>zfy7RY!bCq3ip@!(!xOA4au#S>0=Va-vFl~%*Fi*y~Ly%Z45hPdQ zNc7Z^JYbC~=bp8+uVPrOnH%9)^N-eprMRW^hRt4CRc*_DkU%hnIwa!Z_6~s#@N(Qs zo?IpM)GIVe!}29zy!ZfFSg9b2v9MRg5qhN7?2?o9%H*5F^kn%KHNBnd(9*&IEYu&) zPJR!yJ1OdP^Fd0l-}2 zo+~~r5$8HEB@`r%2EA2WEi!JSxaFSrIE=;;_fC7 z5RI_=;?c$*|M#&FO`uoq;{#x3ZsrV)iPRn4CfjOR?%#JUQ zlVhw9L2&ZZ>O1FZ=}28M@T7fOaZegZ1Gbjy)kI!;OqqLfOGfivX&$Vmxqq7POT6S+Z)>q8J&yiJ}<8C9TA9lji7$)XUT5 zf(!0X-6RfbiP^0ee5;2yZDjN^jKHVq7D)MGLs`@HTBbhmD{TCsd!5o?ppyKl)%^sM z2uP2=;GI;nNDGG7YPNM=OVEH^Jkj|SbW-Qr8M=P)F3%oXKyjx&+@7gUugL*{-ox3%LOig0ypRnoN7m%CN@=&@M zU<|t!`_I#p(eZ2!9AD24FVC?Pjr6_vbbyd#=3y+PVRVT6Z6~W_O!AfGr*dtftX9|Q zquSu+pAHq0@1{-Ez-;lg@-VaXrjnW5o{@K$P`=eguRbmz~X~Uayobh2?t9S z$Gd@c6XJWM7Uno-#)KxjLM=aJTHRcM5g@9&s}Fz#k{ur6Y`h0&+qbee}V)BbOxG0F6j|mOyOyB=t_PNpQVn~Zr za2qW0xvGwnf?gt4_iJ3aCoUHRe%aDXJ%lN zTuJmbvk8FG&Xb{O(_!|I3q%!Gow zxKa&?3z(ab3j&z3{6A7i2wEXx_2S&5uWl$Y8Yv0c3wlOI7g4Da= zP;@DazHE=}RZ$pSOKAc^6$u8rW6eRaNf{*D7E<5$Cx^{(!CDbb3S zYTM9rfB-?;jlnHx(3+qfjtbF6vrYTLFO`eam;ffPdre}`i&pWrs z3Ol#fMysoofh zvS3KBkjRQDEi%rQIL)|snh?lUl*r8*dE>NEFZC7U{jV)mk()@HpItqqsQJ!mqnTWn zT-R1gy2&cQTN9b@!J6*8AwkaWhoJ3yJz3LBQEu&P6 zu^s^HlC7L5Mz@MZSiWE>FWzJ*md@-{O;uhqvJ_*|Tv{66Ej|sLS8zI;DYor&o4V#ceTk~=hzojYy9$?BUm*GR z{%hRb1>>XIwvSZ(+4j4BO^M#bZ07oBj#o*~V4Bp}AE>$bQoX0&`WYE~)y9I)cl=#e zjf(9JOIg>J_qp%L%N0d=Tg(SCe`lu?VG}>4?=m~9-6In;j_AWoW+jez>ewY)qW`5H;H=@2`Os;eUM|& zx^S&hAM7H=tO2{Y(lZgEs+EA>oYJSAk2>%~ zqII)+YRn}e21K|jtk%xSE8&t_D!j1=uJkQa^7kJ7pa(<{sQ{aA3Fq0lqYL|;DXf`x zSOTI_w-U5%ElR;xzX0OFQx*2JF^%NPNG6~j1GAqSyD&8QgPRl7qJ9Rmf?rjrGJdZG9 zSVnhF3I#e%{?)JUO-+W7jNo$E}J@DOShi=?Tp12)pP@90WG0Enx{(T4q7$at5T%4Q6ZR6XN)o;ONcl^%!7vWul&4_cZ{Yy^3yq3a3USJ zioHfV1kCXQhXru@U5s4(3QjY6ETQCYj}DRWu5_F^QgSYM!t=&&_Yh$qd5vWxt$<|^ zWwGV#nr>L)9KHk;_Lz*o!A5$S1+en-O2J$XLA1pjwL9{@CMkEKpQ2}@8S3J}xboi)l$hj8n1&*yZFDk86Y-9M&#DwQfR>l&C=%>Tc zTJ*MKMLe^ZOjh@DWxHJHrL}@g6F*~_LdL2v?$Zv}<%o!IB z2QNi>yd9q^Do#yDV6@!CiCQ+rs}azey5Jg!HQVgNkm@8dsng6b;|wpu8~8Yqtkr3P zbjHRK`1S0kVbOeciL~;weVt*Xyw|o-SiS)R%X9g?FT_Yg@FO9j-!p94m5?EB+Cgnd zyMGtniF$Nh?3(1}`160)IaBvUHej*f#Gmde+UPf zb*!}rgdY?wXh1B$*4836w~Q7k=gth*n#12ei(LsiKq7=HH>^5 zBB6Evp(Gywl}6O3Vg{jZRC#!Q4r91c$w|E18(4^vxu21F^O_MvJwRCI7_Yck#Pr`7=n>{5v{BE?XGMqo3^P9XeX zfXTNKtrqkIBPCQf-dEbJ+*2YbhQgiiuRXAln_>i?$f%@Y!Z6!ka}Z4E%>YKvuMyrr ziFz-6SFoxnyXXw>iILF>L-+HlA{%0~R4*NJYq3=o#ld>ZFc#8WKEh>ixE-`A>QK$< z_lTXZa9Rr0cwuwXgHgQQDym@}Qk2W9vG~LIEVYKXjI)tIu$`a`~{yGmu z>pSHRKX!v_8%}WU$3UgG%AyKCy)=6vb-N?uQFY|5S$}wCgft}wEnsiD8}@b?+v5>D zkYJ4$GOAm%DHOwhYcVR7)|{fI_Bn*j=OM#_nkDt!#8i9DFW!IqQ1+Y!sy8?aMti?R z)fcnsJFdn~Zjx@qtmver<2oe1is7L226mt)VE;!tFi2C^z&9(miEdy=HR_Q|s5su; zJx2`O?ykhOGl$BlbyplCmg|R#vj%IlGGB}u+t9(&led%c#`C9}&lwQ@;`#HJyWc*1 zvGw@*GrOnA>Y?o`O2fv06ZLrJM|7a2l8eKDaLVVn6%;oXLi9c**?N;lO(=$uMeCp) z4!xqCjFUeSa)<_sxRgZ{#j)nF5<2;JnOQRA@u#bISMTy(wwlN(5u`C_XqTNc5V~qG zl7v|+2O*%a%mNe3WWZGs(nsGIbDKB)$UC>PYEt~=d}nH2H$FcUoH*RDmfkxuP0P-l zSriBMd(2jhv!^pY#)PCSDocDzPhz}wBXWL2<{Hnmr%(&}3nfE++9VR2oEWG02(IxD z%;$}pwS9!)=fb5soDXnxXFihAHor(~H=%K+Q$@6B4nUvQ2S%-ZoqE>R($X^>Idxvg z;cl%P500AstRBRWGnZnJ&X+zq+B?kIGWaQRP4POd*p=@*6Eppkmbh8`49l`$r;ChH z3#7POV$n4yPBwlvA2~}u#!3N9bJZz$J&G=Mfoa}1$5@4Za|alXyrVSVw!0LWZ{?nW zo#d_v<4E-@kHT*xSJz~@aj1yqR~Qe|X5#zU*XU%PAD5;eAn;3IlSmG|cgCx%nixWc zGean=0c62)pjwCJL`fDbP`32KlU)Qnb8+yG=z=ilMmL@Ad^*yR4}MTvIj)}{!qISY zk=D%l2puQGw6NU4y%jrS=5)nQDfZbU)it)yB>N^jrFfFNJs9h&_kzQBx%u_x?!)I# zMyof*53uX@?(}lBx4)0<-Y1N-d#BrWczZtLJek_sd(DHxh&|+rUUu+Iv)z|T*<5cv z^^yRTp`;3B3q({Chc*mpU8Wwxa_m$xOQ>-yiZN&i9Wt47#ZKCG(K3JbNjI|X3MUYm(%I$}_{)m6-< z^rWAjZ}C!1v@2|8H*Efly9jrF+4<5>0TC1LM_U=?9o^djV_IC6$SO<0O<|ihZ&zN$NE& zA+_)>e!^~M{y?&TNNLX|e<`X2Y~lY=9fdCVk_)3E3k}s>70AGUc{p^s$iIoljS+@6 zvo7EZr(;gxAxCWCn@ar)6emY_^%GH_H%xYd(~ zTrP?L8r9&)>SQY=UJfX0nrj&WRJ$$@WZF}cW0ctokHY=HQO5QoZXvC8SiuSJ#R`;2 zjJ_-I63an79t^BL9Ukni_nDHzz+97;G7}E+(*2Jn+j}do@2>oLXewv?hTUWG-@sj& zW~K?yb(L34@}@z3v~pSf@S8cWD^KpGdMgH8+Iln+CkA~+3XeU&Oeo#otX4-=Hkpg> zdM{v7=kT|%Jg~!sO~Uo;3?4olD!Oa5@%6)vzwWL-c>*&3(eLTem)|`5^7%K<9_(&B zf424V#rorCFSk%A-SMo2@a6Hw%c=}-dv+T(Z5^c-g)PUUsMpx`1k!LWhf2^Z;+mt(>FdF zpR`5x*hf((-MW9&<4!AO>#5#5tOsr3RN7u&`P<&gKd86zIpcOT!$zw1w8T;g5` zP$v10&F;5Wwj>s6;>~dASz2ua4_VbJ+JoSsl?9JoLTgatiR)s?(g%W=`8}S=$zh@7 zMi8aD*p|LbLICLOL}C~apoJWRW$K+W^ati2j-jsB7j{DH;1&YJ=M7*+I}3R(bgGjgNLGWXcjwLO=V($kp@>zPlHD=wrSax`nE^& zB?ulkd3ucNG@-p+FOSbpvD8ryyh7zJo%htlOh0VfZ4fM=OG<2CCi!ZWS&%HGPi|Yt zR4C!wqvJ~)Ddo9z3$b00E+xjwM5-1$bqM@C)GaEh9ZgRvyT~OetkVw>FxI(01h=Spua`RIh1Io>dRLad10K^#E_LO?4 z2BiDO!;iBc=(+O4_IOf);bC^Z&o2k3`%>i`L|6R)9abrZl9~w#qbAq7QAG@w4gwW* zr~yslRI^c#l`E7(jb4~kn&w0DU^iQm3A67ZNrRt`Cr^?iAHWV>J+haJ7jVOq62o9i*(lE(yEne3fbG7R;bR zO~v~aYA#}ey>xl<>hv;hD`2@yufRrfEKSB=K-IbV-?zq;b0EiiIQ)99hnUqtoWb_a z)?YceDIONu8r3;4xu!QmWiM>3V~gnR!u zTI2Dk9TOXCCjFPplk(@#re- zax)*b%N@_g$d62?m3y+a#VC`GiV;vzGB)94!4z?vz0YfFn@`qXK6?J*=`NB9|8@N< z+;{QttB3yqMdZ>V=WA@t3Vof>>XO+|X|UT|lz1LmkT|YGc`|-_Kp9Dl209Lruh*MD@(4Z$BcAx(4Q<4f9p2x${k8j=;ltW4f>$xyy za{=X~sgz|eMcu1r`<@|wbAY(r!b)ap84KR7rtusn;urHm@WgT4fTh4}=p#uxWf!n` zJ9Wn26}B0#U_X5gk9CS}z$&i@Z|Lm`14o*J&}dVv;z6c{e?jkX8z6it<0L{9YmR~$ zpUux#?sApNhd=Q_c3zJ6&iKspPO^5L{v_ADt-L-zJzhyAfMGJ)jrKl9xmhuQ=#AHy zCOkmnU>viIocNhsmZB9&6r-UN2h;(##}PFb87ga{XDqx4dIgD#CVe2M%1_m@N-uUI zkN}0XbJA%sv7jNe=eRO1Ux1ZA(e@aRWjA{V@u6}m)^5;Fg>cID)2ryQDhM^HlHdqw z(l4|AaO89Jj~Vmnnp=T&(KJ_}vC1E^f@N=zsF(Wux^uTp$9mc?)+C$l-`sVvR*fabY3 z!pzJd@nk5KGF0ra^q?c+6%UN7ZV89s+0pBrvA5n829DCP=pQHN*s`p!$X&`lBc+9| z_5(gN6@@MoH-vl3(SsiS)6?ngqFpMbVzUC>{U4YY;J=BgvZW!bFh-o=#M$vHNdD36 zhw13E)w{n7u_=3=QD74>KmU935&ZsR26atUjU~NoOklW}szDWlq#hHEZaM>cqN#Nk z7L(TUK<5%_iF~BPPIPRlha|;1_ySI=BrlgM3cOIG2LcI|?@Q0@E6|u5KZzaHEvp{w z&E>dEd`~wR&P+nUJ*Ldo8PY->KZI%S-xoXc-w8cJ)rX5@8L(ie6!|a1h7EGv;K$uv zr?|HH0^TJA2Ods;!oOIkVFw-On$edk-M;qJLD*^{!bo++<}ngjP_o&>(J_DsR_J7D zcy+w=>m>|V<6tvJ6Ku04;P|48gd^mb7k)BXXLbFxsc={gi2s7IE7H71805Z_y$qFL zyWq5zwC{d`T*z5UqqK9eCjV!2I1_ZJWG_6HI0dwI#&Bo=>cMN-8Uc^NeX3Bdhc3ln zw9-L^R8Ub|;N&v~{0?5P!43aVvPHD88tr8vrW)7t0DU;<0s#}gMmb!`501Ssp&L)| z3D0rNODyb``n#eh3lG2fy&Z=k(Oq!##WU|pvquD#bWtcA0U<&d{8n&BAL0Ld2LGnr zlG%sFZ!+$3!5)Z5Z~SQQV#MT|7x2kX;aZf7ZGLyg$cvQ3YOddHej4JHnrO~&grL61s2xWaea_5Q*WF{8Hob-VTfB5_QAH(&Fq-iHKUgZOg4ApNP^ts3?Sy4bhwzNLW4 zj+J`W8`e*K{SuF@=_%>5S~q>RhQtp!!q;KFp$V?ddom3e1Bc+HA%VBjedKNWWqROc zI_m`@3&kyENq9eNAaqj(JDuK~@4tb*2_8eDL*Jl8Qehhx;n%{GQY}n<^$dgyN#yvQ ztkQOo^}*#%ttRsXu@I6*NTZB{z+_u|d2|eXa@$c3)AO5ULJbu3O_<2eyr60GZM+ZX2 zC(y4uo5*1Sr(If{f#~|D2L_&;?*A}72TP{&8%KEa2yX0f5aT?hVMP-N|`589a*X3JX?}cK#UYzNUQX|k}%&8RK5KD%GZtQ{cQ=ibYda)M|iv&`QmS(57 zQ~EI;8_{0h4yV$sJ6xlA8Y+8rd(_}UfW*GOU+9xcVzH|XLZWUc0W|%rsxxnyt1Ev_ zZp}{1EY*3LiZ>8og~sbtg(HMA#|Qt!J@^Kusbm;DRM3rWtcR^j9Mh{H=s=?~V_`%- zlunL(K#7FJ;jr&NqBtjuPacx!?YRu6+Qym1DGh&!TbS+`IZcx1-|kBf9?<8|ZxO?uN^<^z}2uZe}jHr!g!j;owvL*%n zUFa1=EKT=u)FgJV*wJ%x!C>GaYnXvzRC#%8MNJ*F<;C>{4I9ppwtzCqZwn!2hq7y6 z!3{YDg_7CfHjBn~ngzt5`eA;09(raFgOxy85f<=U{i;rJ&ysr?{HxW`Z2p8A7n!Qe z3~;;OMStbq^e5bQfHaoP`BqYMvLC8Ez`TSO^Rm{Rm0#e zO;$^)EA+({|bYyYu1_!6nDjWLxN!xJWz}K?R=rwQ&0t_+db1Vlq`sVlyT$%|HWh)Jq zuuMhSTxI(%_V~D)YuuewVns#KcI=y|JI6^<&)8VIr$=W)cXLY>6;S7IuPlfSlKnrK z(a2}-RT+REp>_8mu*i9h^DD%5^x54{KU=x`=ao!sxqLb z8+g##jX_r2o}B)Oh{Qz)cmdoHBgqiQns|nrf4vGf6Z+@aeLaUc{=s7l`(EO9R}f>P z5W5&Qid;Ry>l#L~C;|Hh)}v8svZeP|WAem|7D0tmE(c#ixsnPC%llvmw?3zNd)Sl? z6X=uE!`XhYT9EgXye>A&YaZNK9er~maS@pEX!OcwOprb!tA*h@q=i(L8A9gLKcq*u z$uj&?^E^VW5{*QCrU-4MiKEgqTOucobFRUZz#{(hf!eRhJ86T25~jwIUP?uh?hs8y zSV1$SpDK*0lDC(nAZZX#LT5ZosH7zf^<&qoQtH%c)d65<3v4Z#{eIQS;iHs-k{fn4EI5r~I6@%Gm z$L=%3BpkiWT4!q=T@Goim2bni0}Q1SJFx^sAiR^AHN1D}%CuuU-ML5_>x}IUnV#Z| zA?2!}rb{8JWhqmPekx(gF_bWdxIt4a8^?yWSjE;|spH{ol|7LYbRgyfYZPm}w@p5^>lO5eM zP$rvu7x0r5mTKNov13r~=Eeg4%?Fr9eMSHZLHM1; zqpnC}Wgpc^(9Ro9oty4_^5l2Ex(|t(pJ5R%PUNIu(&QRSeXD0Ov%5y;Iu$KMLUG0A;J;^l<_hK6`|Yz0WXTB7 z5-*W}q6<`;_#o=1=tkTjSus>3U|3{qdY}Pjgr#-WMXaNAJMSeRg9V1{l{8F)V()y0 ztpX5uesR9f5rUgeeMl_k&m0S4e^+HWS0o&F2IaScL0WeO$Ub2)zb$~LpMd`>+i*~d zwt^8Gv?ItB!Ht2_K4Tiwb@R$_gyGi;H(( z`~g<$TY$lc{7k%Lhejm_qQRm;Nu!na*8H62!1!JIhaJjGpG)!#@o{FktB(q;M8c;u20T zu42UM$F)A&VM6?(H~iZ9%?gpzpxX(-;yt}qmvuBxDE z0>yD=7IRvNZYn=x{ZSN8>&xOTc7$fHv0Ub+j!*))OF!I%skl)?=66` z+!pWsZae6`-_Z%IjObS{iUbtw0C3-DKi$?tL%?6RbErKf?2H>{yWTd=_utIka_jBr zvhJ%2SI3VUBVojtg<3a`p@Nsm!T5ypI*b===$5xXN;Qs$8D>_Uv!FeuMMVLR1&S9L zL}C$?vDQ_M*p+}=?&(JKo0{Zk-(3z%0P&Vw53Wi3z4Oq-Qyo8~j{1$$9}Fhz36O z!JwFXQ4y4RpY=@R22R^e%% ziQ=(}X&*ep&f&F3*uGu?Lu9NApprh45har}@wdS*-lefE-kQ|0QoqchMYBsAgicnk z#(UOQBkFEKg4(E@jBJM5 zrB$bX6R%BMsXAJ|at&DdD**=yNS_*yc&0;uuOA~2zZ6JL@oSu_>D1)(7OmWU^H>u(p+hYYVIEh*!e4>=FbCo zC5T<5>e@|=+`-@^bC*QG$X^j7e;&XnLF{5w*KT42N+%Gu#sYc$RlqB}wQ=-91K36A zT?7M2htb6G@rig-#P8yii@unEWqeIE<8N=H!EZ$iT|`?@nwUN-U1*b~%BL1Et-cW1 z>eT?gbvs>@dtf!OUN4#V2Wl19Kr3SFA;dimE;5saN%<9oH?c_7Jd!JxH z6jOFU+Kd4ro9qHxB@|Uypm_1$=ncaxVD5C^vg6h1Vr+3?ir$%tho|%DYnn^4G~q{@ zOYAN;&Th&IH~Dq4vly2jqiA+pN;!)iG0@!J$}vCoQg;3Incrn|&f3&7vTCK_lVSLc ztToDw1^5hgTMHk3O}Q->rgKkJJwn`nO+Rd$iq68nnDdc(zZC~tpUJj1>7sABJt*6P zivA#C+{(KWw@bBXN$%_7!ad3bY4r8sburPA&s(Lbzn~v*wiJY9EV8(MMOY8`2!X5! zXZQ{=1!GbEyV!kyGkbl(i%IOw*(FX6!L`fkvbJOxV`_nfkpCqQDjU?7*SIeZl2)AA zg>|AQ;@*X$!fBryrR7h~yS-*!LfflKB_Iv*S1S+5l!ux07TA=PL0JridqBYz;po&Q zU~@k{z;VZe-V#6uC3-)bKfDh$8CSSqDp*bF{A^cBC61qE5ZOI_z4vPH$IH zd`moRSN2GYQADl=hEfCLliEzH&;>v~)s^;O$p=`tUfB;8x~dmeX1EV5v{(;VGP)nG z?8G9S^@(_BG*qNICEHT<2m!Zcg(bwgonT**J~TUw+$+7sT6usu!?ul=YKgcnXfml< zrNWYP2v}YP4wcF)v!PP3tQW&AFIf^e60cTP5C*sBLtlGJ7G`|{+W=YG)n2kLD9#C2 z_>-*rlRA%Q>xcL(SIvE>8$(C)4on|LDD=(A?C+P;avEsB>R94^Aa{2!PA!AWP#2W* z7bw^ii7Hr=QD-P0=UU4Kx8OXk5sP)6?PNJ3*wwB0qy()k!o$sBQn9mH-vwlG7bBEcLoRK)4Rn{PgQ_< zdM4i1^+@myZ&I!~(deZioYVYmC}UA85lsPOf3-NmUIY00V-LEU?v_p-=OJX18DYp8 zpT-n7{%Ub#y@qhJ`j~r=P0fRuwcr)VrVJ<)88ZeD(H3YMhYsNTDhvosvT%$hdlv82 z(?}|w9I-=vfDoh#1|Op1uX>t@l_)u>_&_fzb78pXGJ&9#)Az}Gd?5fU!19Mo%zUlNdut9?eo#K7`3lq507D1N= zQr&_>>n@gndm0Qix2_Vx4&^J+^R{Ys2If^uUA#-!4ylQCVNuc9o%khCI=%)Q6vZ5R z1J43>7t09Mp)_&itPEq$D5~5TuuJSIo58a~ht)bs;pdTep+}MkEuufL9hnT{TUI_VFEB7aaq`-n^GW%pTh2iVmtC zC(7rNt1)QDwC;z9Ub!e6SoU^h-V1k$+7PtC!f_P6U}LaLp%^IWyxLbJt`BU~eLVpM z+$Pgi%LE>B?yX6LnA|Syo>OS##ZaweqZ*EwSJXrvXepVBsb9%p?8R=-{_Cyf)|HF& zp4)!|*hLG?5G?Jq-qtF`ZIw%HJw(f7LwQXz!K_ZrZ5Zu_9YxW-#zu@E83I9y4H;$6 zD$8jagm*jN?$39=K74?j$DM;l$c6e0F5Sn^tJ+)BV-)rV^>6`q8}+@drS)}H^XmMo zQUbilb9WEr)E!SZ6+Mz7TW^t+98JDG;ZNrRb95OBU*V=~9G_BM zQQ@Sw=;a(oYP>6Leu>^MCAMG3aeGvCL?VvZ&g~Yhl5)KFg5?m#?54@{!v^Qq3sn;&!HIrnP(U;_-wj@#ChlZ+rKhRP}~&EGlM? zVk_9Y^@jXNXW4{qbMttqp5(PY2q>H}ZGMR7bg^;kmSU(XogYB!>%;$vSUozO&u2&f z*D!NBs_#sg1j2U}I&!Yk<;gi^B(=lfo0{z7GH&rh$jj5Z5QXDooW}Lj$3C9<1?<)w zD4Jh$Z}x&oj+r*WR$6vZ*Ii9}sa-VLst*6!D zcguPyy;b(EWi-er4ix?uFI;1;VH~CgKx?@Qd^}n;6t_S74o-%eXPm3>sXJPl$O9h& zLs@bU&+X~;(Oye0uiQJ~sp6yAKa2+uV?%m@K(G}$*wiS0N|d{JDH|h?0X%K#ke~X4 zSLpQPK#c#af0DvQ+1^Ma{F=#BBBARE4JJ}dKSS2CpP9?qq|rJ+YKrj7sAq1hu1y+! zy!}0nrQMOi`jx_?MH9m`x%WQV_d2X+P^WXd*T`tBD8f@ONLGYow~#mkhgL6-sAg>Z zM0c6o_G&*PU+T^jGrr^wfRb1_!e;*H54bGyV8+wse^_1pgLe84?7vK{7}hWXib9yz z8mwV>N^lUg-mX<2$6-i$Rw!zLZDj!Kej0#BPzKPyFPUP|aT983`I=dNJ5-gS5^piR zDrEN1^DlVrgfoDC)$=MM4GUMbRV|!qYYv%Ct2} zX+dHdE)2Iq(yUhcjF+lk(&%`$uRKpdJi(8h8MAjZXxvJ2=iyEZ;p~>?DW)vB9D7A1 z7{4wsy6uU}vM&W@)al4xsHGaT*rXsE*EXK1f{(iri+) z?h2D*A(|p1h6-+*cBXW|wnbk`*&Zh6mtq)9Tq&YzrKAA-Ds6u~b)Tmg?V7M7g^HQ7 z3Uh^`xzOMSLE!N8NP%&b@;~(MfviZ;Kc+?Y1*)z`CK7H7+#kn!sDWaYIkBUYU7 zmQlS8FI^e`i!zI(0n=r7FEr5tc}e2!$igETGNgqL-cov}mZcG>yBHDkyV2{_(Rf)C zASFm*5nARV##YFr*V7Pl%2kHrF)pv0p8vvl#2I}o4YII`-_x~I5MvI0vhKh^YBFTB(S#C*YAXv1l#UuQ zyo}w{S!X@EWJsW9*N`dDB`n|xw##eA&ynp=*icwFab#8OeI~0sWT>q(`?u;yqXwc( zgjE-pFhuWz)+5mZPzv`MWer24L{aCT9&K27V0x5cQgSBm=(8FnWjpEhV&>axY1*&$ z<_dzs+a30-i+)3!qkjyQccHpIcnGgW2H2~(S-X!0A8fOBMyjIaVy5UYBL<_0v%`BY z3}l)Pj<_qh(){J6D{V2_&*5?Bof@z_emXwtVL#EYn=s12-|gvGN~};yGzq)t>Aznh z6e6Xe^vNg|1&Ka_Nrb~K@)mPqvf>7t^QmgF7OT>^QUAbpxmp@*lLZ!QFOm#;8 z$(1pXsZP!bS))N;+#!SKO_N^(D_j;k#Ey4z!COvwlh<>e&;)qnV&7;gN< zrtUZ(GJeY7c=sQ4~$nQ{;$)%VHiZuihv(8ZGF4T)$V~uCK&RW1N|0`URBUq>M zl0QDdw!+V_B+(dYQKmQhezN`j7i%MIJ#6zIYByOpjF&zgEq#VrO6P6?y}dL8$yNMi z<40B}7(4Nkvj~(hbfl!O)dbF7Z}iCslE)A2igUo0GJt@jXc^&EXl`Uh73@Xs_7~+ z_af6*po11*uqhU%gVNZn@FDyL!3rq=|J{q}{PYOD-q}1o#rA#?0I;dM(K{ipX-^3& znZlBKis_}91NX*yi+W+^z)iHKxUzoO$&b*z`lqLHOtiL;ZDt?-pP-eN&ZN&tOJR^T z4QxkFW9$J51%AMPL*xK|Vk`|J*Ujm#migDSlY=keirVmviDZva5rEY+`=F7+um^1Q z>G{_;XPqyQSVZLExN-`sK`jr16jO@QI(DFTqf4g@z3EsC+iYlfRhIQ>?4Gsm8*PSvHroLn!A)!)#8GoMU zcxTFtcQr*C-|}t!*HXbRa0~J=?jJnh)v0m-%`q~~n;A!@ekkri!39A*CS^TlF=`f#2+ciy!t5rZnNw4X!%xLc_^b zPC>I%Wb`JESX^gcktaE7!oK>|jHvvjY?et#H1=}j#`F3f55N^&E!Bln9Z<)t6rZsF zXL+)-Y5^0=jF@l}bM-FOx4^hPTKN>uVzqiR`h&)+H-m{Kd5gym5-`RA5^B2w>>Uwn z(wnANm608VZ8k7g!i;CwVdK&KG)D{$d>m}_6DudZFOkegEwVJ99^+Q-*}icNCTUqr zxw(qVy->}#lwDNaW~P6uG6Q)=f1=GTgCp|@cUA80Jh(5RBCSgF18`tXb2f|IwdkX} zF!g^{wp52x9hI3|$9CktrWff16lDsd=1gvjQ?av`tUME5|_ zP#{J!+g0Q`Z<31;+!2un&8i#d@%Q2Yr1(kZI}Hwd{hAlrVsbOL2{kP3l;+(507@wMM`}T^Y|aP>aGnf5QxU<6AFbb3 zQ<3C*@4H(rb;rTUk4dt|`F#U9Q1iKrX`|`_t#0HYYyc?wdOdaG<7^kH2-gb_&4YZw zf<^@2<7bawf=FW=@w}iT-T(CK#`C97*PlIL0Yj%-X7k0v-A9j~JluWo{NJBFd5%C^ zl*U}Q!aFio4satUtsB(4j^+X;g^Yq!aBsEFc07LGk)*8NHl^#SfcIWIxB29ouO2_! zUEh4X`|ZOQTaTYVBj%s3;!f|}%bHpC_AkWVm^enll!DvsQNRW4cq;S5`|Bldp>p@g zK-TJN>+;on|9tjpdY(F4Rl_lufL`@+(|l3(Z_iho{mLUVRoQSRCqMS#@FvpHu{Wqm4OKOGRtoJkjgb}pKQZyH`(96vqCKun9H ze?HH^VAEZPqrLfj1J@&?23otDNH9Fb1ZJeH+?GR_6!9of-u2N^NeZ-M4!d@5Wi6=& z+3XaB(LkCnobY_@&*@{QnX2Jbu%JKDflI7o9okgX!Evd4BW7e{MyB4hBz9>iWTFU$ zYYB;VtD3_C6F#jo>AsS}dn-%oES<)l^h9DRd~%8li}OcWrz%zWl0;Kr6{WIgv*6k5 zHI?omlMWL3V2G0%0WayBXjmznQ5>ie3Xwho)4DN!V_G)R@iFT%IcWG*L4yX6l6V=G z&c5}WbaW=_F(7=3o~W~VYChWlGzK(pb3$P}+Vxuwi8y2br4aa{>Q zw8SUNtG5s_GCEfJ<4SVFOfMyeL+oP2&BMKX(d%ou+q|MEJM6d0uig?a&)!$j-RoyF zQ#U~*v8hmf6!CT6rhm%>F~yZ%l)n5z$;J5i@E67T(Puz5z7VvpPky>#SeK+%w+uek9+4Q0DkY~8(i!t{($Zf;jq;B)<{{` z$7hb++k&TM1Ozv1kibPS`uN`;KY8*oV%Ko%BqE4p)Lvim+?zx=<3z{&&FSUQ!RQry zQL~?LE^mEneRDK%2KXMpWbAQhfw@?gS3kew*oxCjJSA<84BHR=W2uthlir8YRUINrROv%9g;&g;y*)+PN%QK!<8sT!)H!_5`~*=(1a-!N@MBtD9y^}Muac89TjvzQ(0P9rso`P(LFh3a;&V11iDZiZ5di3OIgsh6x<$*ZEDfgLD7A40xzfBsrl{R(PdD0W09`Wk8iM` z)0?)BpoR}MARsG1m2OjmaFbRN<@yYKrGoNi?=55p8IUkZ;czB)ZZTdeS%{I)PT@!c zhY&J#DOT;O`iGaKlFV$g`L5cg>P61I<3-hFhhbi{i()u{j;|z=1TQah~};iiCfHea!N$a_qgi2?80qmj!amY2J_FAd$<+<;c9k+XA zev~~QeZ+PmP!idE;@f1gfqhJ#7gPPjGs0(8@s)e7>^LxC3|!nXDJggooQh38c+Lj> zgm8hqqxn>)L-~$wYW$S^0^U0~djv$`@MVN=L>Lz&BY@-)6diWvsh9*q1S+8%{aL%v z1lV!KS{Fff)cH8L1xWKLT$^OP%~Ns__Qy0Dt7=ewQXW#Ps-BDb!b`EFA160pMPWv3 z0Re2x>?@M8$%K^?uj}D9(*#H5(WSkko%tur_jzu9j>uU7g*TJ&s#LJZ<<#mj^z5(4 zGmi1nr}7qIXtLAH75eVQ@!5FQgY&}jAL2+fZVOJ1QL$OLtFFRbKz_S-j)7xVV5Or4 zq8N-o_X@y+$1irP`UkV~6I$Oo9*Y9nc(T5=RfBRKLSXV#17!|)+k$tdB=4Q?LCw#1 z?oSX#vV8xJ&{ri8E<&PMJ^gFUU|~#00V_GfYdQk)rZYoG}r-D9^|an5KwS;F{XBjA&nJlhT9TC zX8V0$$k7f^#ymPb{o(S=RjYr;07&l&IA$}{px{CA*v3SKZLjD6gR0aB6U=~tU=deK3HA=FSmi*$-5uR>LG5?s$v1JCS=@#RB}RSbgeBp9BKC79x~ zgHh_jJGa!ha+t%`KZMCPUX9C=RJQIeS2(V+T%7A&8=#{Mx{*jbS%hN~f9FXJ3(NH) z$i}%k^Ugb|mi6#L0%SH+9C3w1@e-*Zx7-w>HaJC^lwx6^y4xiidndGpX9zc8XeHgR znIz~WeDAU@>U2g@xT$G~&6dXS-pan?=}*|Q&RZo$F$DOFO28F!)b{esc`J>`^H=|c zFS<@jFQs3QO9R5`T>~*0;O}vItf^fR+cWn_B%!}x_#*8Hsmn!4hlDrAQWR&fkmoI< zpfBO|9vxpIlqcnMAH6z-W5bp6jxt>cQV5T-cQNilC05)gSsl~5fjacrB)1L%*raU| zaJ2wHjDot$tab&b=Pm;PhD*I%z$+WlI>{xG^Ib(9ajHl%a)!gh6=ry)I(YAM~2`z!CT=vngh!OKxhX4;p*b55b$-@rTSW%8pyG4E7OO zj8wr$DM_rnWMWExLB=o(nm6=K*H7j+>h@xKIQ@y?OnNK;P9Qjgi%&A_{7+5afg-ij7{r66t{7KCvCB2VL zwDuc~aG9P--+?WAH>8D(ID4iTRpdeUq{mgME){%=OITq8L^#UN8yYS){sIrr8Msd~ zu!1>usd})`4I)(sCnQ@mdNW+X+be4;u1uU3qIXzZe9?NIL>Ry^GH{_;lB8KJBafbR z>&a)SXcL&Nj^ve$<-kyevByXnfCh1OZsyvdL$z#H_UW6+c=Kqw2c@ORkKQp~8+_xHiK~K7y-2;3?=U1&f)7Y1>?* zkZ?2ns6Dgkm%h{AWP)L4^EKZB z7raV`tNDrwT2@P;e6c`CP!iPz%Ob`KaJd$-an;~n(Q_FGD!QUh>-=~w4zyrSWNIa? zZoN#FvXRD3W$h&+?u=-`b0~P@j5TIyBaGplUw9l@tZcoKlay+Xr}`nlseoJPKZB)` zo0lZXW7C+?!*F8d(HT#o&C_1w&yjq_o5sL84T{Bwj5e!lQvXbHZsKF5m^%0WbHNZ7 z2n8jyGf<4@@$=6??nq-(Yzfv(l1hm9Ts)QY@&$K5Hdgw=w|fR!}S-)oB!yWCr@5J z{EwFsu402*a(R0iWBEpdiGm|gF#B1x`_i|+PT&1_dVT<9h1~p1MMFnOqDUsSU=JeNK7Exp{W(J;0>{d%+>`acn?$-OLJ3=*IHXEl6_kjw6pc%19jh%ES8k=$10uzr zza~m$Kgw!C*leI#7=71bDr-qU#dONnT(amjxJ%kM@(vzU5XewA&c zfO6{gktS@nN#CS0=>SRbx@fJ{s!K=L8a;>S?Ctc*-u%WQ!kE`D?iF(IzM`V)fb$We z!72s>HPDjAY8lx{i9XAie$q4{VW3LxT3AYafGcGB$3Rmn1lG_ORej-1N_~Sdqo4xu zs%BE@#@vfIWV7I4HZM*P4xY07xB2PG4*x@kYqjJu+U{s@ zX2>59BA3(hY`t2xrL6*bGXBTxOq1SZ-@;C3zh%thBgp(A z(y_|*6zDU$-_=__zt)ABCNvl`FB8UjD+*dZRl<}6 z*yD9ZZ9{W4%wW)x!0m1cYf4f{sPvxEtD8KM+Ky} z!!Y7AoIq7SB>TD9)?{_{$;;=@U;cG_<8MDafA-=(H@GSz`@S-QckKV7FpopS?|egG zU}1L<8+xC}q+R)W83o#VpqYwe1m`M+= z4U%NvVTOGEP;LC+&A=yIF0ncyc92idI%j8@#3=QVfe}~26y_D4;o%L9=k>P!K03RP zeJi-2vk1a6SabtNHbMceUtjKT93smaF`ge-;RJX!L)l8_SKs$&;R}o}WHmK{D2YVnN-)}cyC0XsUSS%fyz7jc+5EcQP$~&)$b%^b_ zd!GdFIhvhi>LTOd#HQ>31=uEv7rMeo#)Yit?5lM`q%VZ&S0;mh)ije(# zAbatS3f-Na@nVR;ip-!BdST>AsE}6-8I3v(bM^+d@dS30i;}mk0Dz1+SCy&><0-R+ zJlJJkxI$r0>e9uN&12qE{b*P_HMkh;1LV0k#T;k-y9BZZ}W@f zyY=CKCFl>KI>lkKV0SB2)TY{O{k!}(ixwbjjFPjr)pSPEF7Zl?%BGgR?h_o*$4N&H zA_?`TyrH#lQh=Z@cjeH4PWlIARt%7t3^7S(B;yRxboIwVZo1;NRGgB?%eHk{dsl-|0TS+6(*J6#WY1UTp)Q^q zKFR|>pmll@fq$eC4#!k>!sU1Xs}yD^Sx@!7FUdr0ml$ENezROs9SZEHMaJ{^g65n> z|G1H>b}Vu`djbf22q6((_jP*en|7N)YHkKo(eT|A3bIjB&r2PR2t@gaj*GnN!UB)M z4{>)b%!@f-kc4bW?lC~jccoeOl>egTeXPl|lUL&**CRZUm5^o>&$t3k)==POV)vx; zWo&Gn>kaVg*t6t6?`|$U)c2bm8?Z%+xX^EC{_`(_7vCwNOyumT{>}HCPBmX$Y$ziI zZ-wYO2?;51AuJ_Tk9*1Y55p^-gfZW7{somjWBfGLvR9dcSE)1(i=7!l(sSI|@tlpDPWt|JwtO|jbh za0_CtSpk-W=9D+{5Nbe+ACAzE5wsO{vcNQj4YueBL3O1+w|J{Z~(3uK%#TcK_jj-v9pb_WI-RAOB?d5d4S5bMatu zI9cn21H5(GL%L2f37{~<6=5P8SZlu=kl8AfRNH&^1}$hXK!}-%5Kp?g*Xiz@Gf6)k zKxS0z$&vVvk}Z;+jnhqgUGSxRi3%pMIuHE;ZS=!+d=9x2EI}D5uvmo^Ttam1Y?E9@ z8(JxZDc%4iW|B-7vQnfAu#o#iA_q6>OJzXOZ;^h(;K>p?4{{!ir;-Hn#k(XEUyCv` zon_<;Pg{{8L&^5UVjw7Csv5e`Q+cT&Ba0B314NnhATKj5tMZ9W@Qssu#E`v^?^=*V zUPqxQ?L`_lOfg_QeJ>MkCAC7o)wNA#X>-PeBEk3)cD~ZRKwTFm{*900w{>l^;UXs_ zj|B~La6>5XV{CyKzjCp}@J%BW{3}8JjDr*22sNc?;mW<{S@DaeS&EM$py`gHNgn<( zks<`v7rD<}8TLrqUd{IEIRg>nIRZRusxiVYreTwmMoAl*n7(OE6Z8$xZtI;j+~GGx zupYvqFQZ!E0=OFru%2KWhg`wpX=~)H;T%I2db49L`i~rAK!Rayi<%P;Ma$Z=6^}xR z=%Tfsoxrlb;;jR$X;EavKQu4R*41yFxwF-PpAQm^fvXAlC9ys5k1S~ND2*W{kF}B( zmLPcs$4>T}4q-VnlC&vGpvr-c%^*U* zZ%5$IN%`f`;J5eT)AFT%^&GA(xOD1!gfXKQDg+EZm`bHBY>s@y2k&3wcw&7cbxdX7 zQ4$O9^CVHy-{1&0{%lN+NR$U-$QpE6o#!yGd7kw!k)7KQBbL<5`=;wId2S~x_J%Uf z-Vd5G7Y4J}1oY8hI^Y=gNb-&lUZq=3a;Ib%dkI%!`sRUTQ$Z$|UYqo^h9H*OpfOU+ z`m!WlG1>9&O|2FYc^a;@94Ek=?)Ykt?$}%AC3s9U1qf>_wpe1SqZrTT*1P35UvAyI z2M?`e21bA_?rXMDG821&@w$i8DSf*z8Y|yXLsg&RBY03p$Gz$+^_>E(1XFsR+=BxZ z`+F}pR2f4Ykl(m56%fQ`u(7ZewF+_Ls4uffW@Bz8BDPfxXh<=B5~%#Ys9WaF4(H128w2S*jB`q z%#7}oH8~8xWBknT|KlSQlK(Pi6U;K=9@D*)ZY}Yd@8%^nTcun1+MR(y*SdmPu@~|; zOT}2ZOptEYLmDlyZKb;4?MEF1iJy-^>LT*mLf9V+VIkiHOCy3MJop1n%Oqv5=>lz~ zQ^vpmy2`r*X86B9gk*5uX% zTL#-5>oZ9wNcx00%)XiDuvJQQL1{zhL6X z7f1jx(>R>n;&VJL$0kgRho_S%g&UY3NSO?EKY`&;Fz)Hv_UrZM+fQF^yuyYB=LYZ? z7Kx?CSa)Jh0gn$Y<*FCD#E8<5`|x4+PR@^H>(Z7egKr2~;d0zW6hXeGAql}TxU zl`JBhY*T8X+$f5a$el;c$0HMpXD+Fb`wbZkd;WYpG?@{co4%E>=RwDYP(RItNIoXAWZMLX z%M>8HJxAgP^h5@jT~aOaDDbXr{WR{YX`!JrnC%sSY+2*Z!|iw^=HiSx8SRPx!D#vByIEbSnrcqOhaW*sDC zY^&h3i?VnW54Dmo_ZCvWR5&+pq7Fqtn!*;(GQb8*U5}RjL*l$<5LG>wR`?DW^&K(v zO4^H#=kw;cnt+hV$_qvDRo=oV>TYjylZpz~mhnTi#^u;?u|qsYDw%uIsRA&D^U~uwI-lyyYEn+Uzsk;L$kT-otI@d*Ohkbpo#$l88eWR+S!lA})L#SPnFu zL?Nd?gRbaUAW9l%5c%^kI`LtW+23t;Q3!6@hgB|28U4@*WS&T7t6^t8>R}DYVKOOJoQKGC?#G)7a78!Pz9z+Yk%R2Ud%{cd#4e8lP7*BTjnsen#pk4 zF{j$aB6OA;9*mK&@w23G%Qw?>?hTxLImdO0AQz>ouW!la;D+K-?v)cYcJw(xtl#y( z0L@AvEza|Be2UWl;;9!@$U#knCUXr3wy|hk_%fyK=E1t=6hSzLCzVYix9UAe04jz$ z7|NJJkQ^q|0cOxt6{~WB8lSeM#i;_CPYxS#7n}Rjvd+#T;nNx$rFpYQSvx zXkHA54@^$Fyig{{tZ}iptU&t6FCn_WyE{Ca@@P1>YazH$&^T;>AF(08aVGE#>`*%% z1ktxMIvP!JGB|u-e{kAlM(Pc}!vHvRVHBsQv8Xj=_|Ok5FL$wR**m$iJt?Io;WZN! zO%*!S$zqLK317$!XLvx^q0e;V1=6)9jbmMe?5wX#GK4`09}_TI5LP{RB=fo?ql7Up zGavyyhvk7CRfxhdg9I8*mM9aIp#_Vlr;G6dc6o2cORX;Tct$*wmW}YF*@v0*&25*0Y27TM zu>*rZuhugq%nyxsimwcOd_c7Q-q6bWmKN?*H21PBJ9rb5C_RPIg(5yKctCK@W_2}v zjH#lZv(uc>Xm{}m4=ZcG64;Z=oSfBFv>BWYX>pE1hAfN3`_oTg(I;quwoaI?fKB3pLit#!pi#guJi=!pYaB(f})Psx@&ID=X4orU&7`mol zN=)H`yz(5bG*3)tBwkw_Q2jTNt4S=ZT3ki$XJx^96q0}?Az>~XB^FbG)YO|`akbhq z=!jJ%IA=Tuao}(otc#9Z5P)>J0D_XOL~P(ip~cQU9+t!dJ!#8SgmKgCUvNV)J%n(f zgqHo09QAkqbX`-BmmL8Y>1RzM-vc|qv0GaXU-q6U?<+>F6L%qD?HBhl&6I3ZZ4`{1 ze+DGT)Iu#`bb#%Lq{;Soc#t`_GD8Awga={B(~}V{L>>J$1X@rjp{@^~l30&kxHxI} zX~T$gH${_AMPomC?=iv{HX99-2wx{ue|$!(R3~|?iLcgultjf_*+*(yu6c?BeY)8_ z7)|?F`2x^1yH?tC-=q;SejpOP(hC=QJlr3S$2`%5eYa!9TgJDrZaBL-pbiga!gP2r z10?U`Y9L}(aTTT{*Em5>EDqq3w4_u=M$#)P1Vq%#LY!Artfjo5zx#C5RDmwtH#0e^ zw!@$%=ymYTHM&wiiR+LWmjn*yW^Cwk0Tig#reBq;w4A&Syx@AZO#KwO7NjSD$^7Y( z+RM~rP=seG!)Ocbqvngrobz|!QNRFXF-u2Lbv*)lA;d1Q7Rt2k%g|0A#5h7RO@NM0 zc8|_+@-G(+wablhD)Azuk_*O}N3M$q7Fs?XAFLqTm3gKK=4BsmRH5q;b1OHy5O2Ff zCFX#u94O!dm{Y}H4#yLe+GkwWSuN*hk@}ui&Q?P7#&m9gB1}P-TSEfT-|nk%88ln~ z?2AJfGad{yv)D+oWK|)2Os+J$D;f@qZ)CC1g>* z`EqIbhNdbn-j*u6_&lR0fyK>8yljH4&a!8Go%4yTSZBtI7YR!+Ick? zVbe8}2Nvf-T#{otUTn*463|75(Yz>3)Jjlyv(dIwl}AAnQCPgfyn>?|=P?xehBkKL zWPKxGyb53tf5kTpuqDof{KI}JYW%m;7OY)ELeex%d<`FP2WCIiKp9R4f?1H31z8O; zmj*OxNko~bx9B0Jcx)ZS5F{Sj2-FxJI|Ha`4;Gg2ei(Fx+M(2o-*O=h7z@WW#Pq5# z$nsiGvNy%>G_@}iD@l9r}1Bc}x z!=`yW#$c2cqAi-lkuj7(;fgJ{p2cKFCNwb|-{8S!j3;Rb$5%zx!XXngFc1y70lLv8 zg*OUJl7Z!Kjm_iW<&+deouLr9IjE)ZbNtMorop5tPxz>HfSdV{LD`DD&2D@x!`jcv zu(hOrn|F1K&0(=@$FT6;ZQ!MbRA*7W_ZWi9affTKoPVlB@85F0DzxXa(GduqsM7@L zAiCjcB8VC`$o)hjQ&i|;Fg;ESt7mpp0j#4zWm6sEwN|Q{AWv}Hq7 zf!u<+LFoe&7!ht6o@gcr-|@x=y0KCR6-&EqW_PC#(drG`w3K@TS=Uf-qC!|T&XRCk zkAPZe|AH`7^CiD=`aVm;dNq=K=q!6roQRrJ!y7~B#M9l<{GjZXy zkhAV)5FRsf!i{p8v6$J>uy{`JN4m-in5ROJFBn&WSumoc`zD^Ajh@Yb)2H~qWd zFt{^$wF&U~1k>`*S!ExIa^4^4te*j8TUR) ztY#wm&uE?r9QG$2;@Dsx_B`JW$R*1P`B(@>QCK-apP(1Eni6d|myq~7!iF{9fd{$f zW^r!Lws&CP03m@C<^;eoqYEflG4kbn&L>C1;aTtYE&fMz7oqnkE7uXU(SnK_EOm(Z zeUPg?DU|NXUOb%wIp*U+`0bs_VHV-x4i9lpMh7Rze;N;$JFgI%=u8ep>Z1NE^(1Pp zca8@q*oX9|I9Yo<+Cv@2&R{x)J%$f(z1aBt z`_ps&jho-K<`a5`_8I(w;+l#O--!MPXy6IhqOOLiPX%8Pf9^j}!T)YBCM;%B8eI*42=O75gmuT<(6m;-4zORR9DtI4RTHRWf>}J4 z8(I(&=cqytTe`C|?Cir9dM^>g;U2E(vK=0&-Ghnm;NK9#dQX`YA_%_`1H)n?4OoQC z@!&l>J4F#X2s@DAuw+w%F`YapPd)doBFCj@(Emn`NbXpNJFvI39E>lDYp2A8j8>BU z5BWoAw#{-Z6M0?W0SKo8{F!nTZAH4$+!Vs({-1r+c&;$wvwFDD-$6cI#y(oKLrz!# zbBNK8D{(>pEfP0<@@&8F^0~i(-Sbe&l~k6Oduf-MK~z7iJMZXoFzXAn5|3VqBvQ!> z)2rL1IEm^Y$%76|14N;j>br#3c?Jbgm(oXc0UJ#}rvFPpft}7=E+>GEE2zLpKsS{q% zzY)@;?P=Dxf0lZZ6Kkm!%HYG(#1S7l-=tW_A-qGU6(n#BZq+p4z;W8u2I?Kxa8Z_a zxc8&xd(VVCLx**Q99=QoZ9KKf#geqJbpXVImd#U%4H~8r8vKD%QJQwDbYcRjMWVl& z(xPi>)rP!8h)MY`tquu!8aD)?JmdouVvLVr-tj-@K$v>w0f0Zu`Yf{})7p3DOHaQN zi&5?W^l<~bCBw-w@sotIkXgCzO^~%EZZ1yi*vy=LQ6);! zZKPOX^Rk(!V=nB$ZAvH`Rq zfhp4g&Z;OWM(l!zPeO?Gq4GN=hB`P5h8!0T#hj?H=5t^ITyOGFIX+pVlUw3H`x zY>}&spbg#!RhbSzc_EwEIA-e5<4x$fbrIWHG$X1@yIg}U=FwmwL%fQglHxd5{@;a7 z2ay{ioGl;Ya@gS<_q~=f#`Gy10*KOTseuav0g)HVr%FNbrmEt`!}H&Z`{?}m%Q_D|kVaN}d7gJ1~#6&p?-O1Ph2kPC_k))4&)-erlJ)C~F$3oN9h#j#0V zFze-jOxhmYWLp!C0ES6up5Hlf*ZA||)~N0HP3oNG5dFKuUl9|5c#P(S4B~-JH7CmyOb0j2|`LnZ~+k?x6)jKoQo1);g1j1)Z>wy)-V-yxEU~TEX79 z!b!}^i}}WW>B${rp?bxS$aLNtw+uDx)64zJ>M@=-sIR)uZcWA#Ge|6?_FOVLqyS6E zril_e44oW;f=g#0BE^vgf)QXsUWmPrhJm)~k0r?#r6lI@kp1cITIErILk+9YX-a>A zObh#2T{&Wl3APhEY;jL`HpPc>ZgCb&fxx0X^rZhaBT*Bx2A`mXgQ(2Z-r<12DB)D5 zt%P=|t8LCEekgA*D_gxB|1(sogn2jID}>1`VUylw7y5{l9CQ}s$Ynv)9bxF4;-yg} z*1k+G3=h3rEr<{=K}WR>QNiZbg5j8Yi?dM4wlj#6Fnr85y4<>C8qw%_eyykSV+-Nc z5$G3T=|rlyveKzo*AYu$1_ha>IuF4q=z8y{6cniz8&Gk2q3qeJPCKTu)4zg?jpc}9 z`YA52!%aY=-5x62c^;a6pmhV>Yb;=*H-VyPNWo;rYBeb~m0*c}P9FoFTyWPSyVoMp zV~$D`?J9r_4T*TC49oQ}kyI<-DXbd(-}9-&`-zfeWK;Hv*G-&WhsHaZBF_S~k58Z_ zg%w;KcJ_wY_>Y+5Sa)gZ^7XBgtrOR{)?xb1*2#4_#j)*~bJj-lIo%>^)>@a4fElMf zG|J8%w%$cUyY9-YRr8zr)jZ#yPxYEKBh?>zf3h4h76XqQnx}B9-8wvV1_V;mva6j zdospO%hxn}0kfI&E_%0cWyHc9vMmmg-Dr>F2e=|Y$98e%TS?ELkDsRZeMBDJYB?=E zIUDZcq8m9b{a={)e~~REPu31Y<3}vg}QCu0E4s4H~9xkhPrNc##4GmW_}vu6@g+#VKOa~1W^|8pdjR4u;iVfVAO$w4aXiS)W^lbg{ z!&fiY|F*sHc{o~mO z@)@As=38FfN@|GropWR^x$BkrZzM0iY`vA7^Xewol1sU}1=MH8QEX@}y}KM)rZ{84 z=rqqe!B&y8^KOE8R)pR^?(lAQr+!^nKTsSosf2EiAe&_8Ze(h64M%1?1JGFM5qGko zi=4mUouYm*o(Wqa@i1qIOgd*@N!RHcRLJh`4d3>Uk+v}$<18rXbrGpiXUL9X!iReN z;UB>5C>#t_QcF&Fij4Tt6gL7PKzU0~EoQt+Pp9v{ z`{F6xRKvY}ikpRnnoHdu9u7`UhDYm?yF~0r zW-^L}JbZfp#f!(!w_j|Kxa^Sm8OB^@g^-isbY)jR?cZL$1=@qez*qO5Ki__M|JCE~ zU#>rUywURU-gIvT)q1AO*s(Je`QVh<|Dhl2<8OYa?#A}B7f)W!)1P&QO34yDluX+c zI=iHIUf8Ug2pMO%{`mRh`x|r5500H70=}^izbJU#IT;YZPNZ;Vap8B3u1dFuWu`xr z>;6QXLdx)#2`7EI`jbH_8j!b%V!fa%sTg{TSg(T-^S&r(q(?Ph>;g1@4c3rjAaXAC zNCg1Da7N)ilpRv;1(3w8ZzDkkd0$%hRFBPA@`Zmc+czn~Nh_tr1^NmW+uGy%>m8$B zq`PzQHffcM-C}rta(vB!S3N?y02#!YEiEf6YS$K!Or;fm*L5Br{}v^ zK*Cj`ZPNL#FDC!BL}4p95FHW>I>1D9UUY}*f>f43H(n+bpCMbB#-3lJsE4n@PgooS zuweM@qJmt?|4LId>6G~8D>kZ`Y*WVn4eS8jA5Y} zkQt7D7@Sf57H;=+ksF0?T{ubvGdjfXXEMEgOUJ-UQl14+_(nmMGaaCRZGOA4`R%;9 zla;o|6flB1?-si2k2hZ3Uj9lFn6qwSi|e~PE$>jhf@ygIy@31WPeaeW3#l#H^&bJTTpNV0<<{AC^klmLjAZlBm&vOh=LykP z83{II_sw-I$P~>$t+-){E1E(t1AuycoE*U9JNnx(8oD50;W~Nr z7gr4d=QxbwlU)&d z#g3*1B;5$EeNmk0qmZpi*#!ShqnWLcAl|AWl}7>uHf}iN{4MQ<&$lYYW?!hoq-O=& zvP!!V-}Mbn(BNpai>tnCtQ+3IgJa&~>7(IfcRZ3yh_Pf*B*_@qw}6pb8tj3HT~0+F z6y+II7oocuXv2OPV0d0afk(-Z_+|V3$XDy;C}~_@ATJ@2RvMYv*9RxfjBzan3?v0A z8hcHxW-5Xc%bh%*mzWCgQyYuphU}ch4Z>)MMw%d(_wT+1QCOpJcR)ru+Kg@|LzD5g ze`Q_M)l0!`OJGD(_S`}MLDcj3K)qyvoXO|2BpV8k%h#L&UmX6*%M=her@)hh)9}O` zPI5`iweLE(BZh4>P8h!<@yY-F0i!tcnfSag-Oq1#!Ff~ks{Jvzvd?&XVgpiPR%a%b?C*Pv>|vCkl-f(jDf z_D^&v1R8@|VI@W;+_!KcV=Rk=w8`q-QdWBuqhbJ5FCb6A33s9Xbcx?a^x}z$ieWlM(M-o`AwcCa|D;@f303E`beoB!w~!tyT-4i zOOd9!+qF}n2CCT~u@{Q{_zPlFze;+R7IC1^BpHrPlApqD@UrRiwdiuH`F@TGyuupPd`T2@%uh1QOM)Y3q@>2E`UqB1LPMHAHaOz`nE#b1 zPeX%{6Z$;}C485ViNEGj`rl!x!v}mDeh8XthQ&&++h8@wik+)tr34yZzm!cg=2i1~ zHu@DH+-M>)y83#7dHt^huW8dJ3X%AhN+s3j8rRvb`7U`1vUxf+90h8#_v zvbH7l+C2Rk8yC>tz9oajPNIyK#dIrMhb%t6Q-190Z^oygQicySr?7WUs!JS4Z~h zTfRc@Q5#=#N@`gqO|92D7=-Su5$?sY8l8NU{g z-D%9YM`uVJ!?2U~A+zG=fNLKf*Xe}c`!_@&K99y@1-VL8tL5yN%Dtv;9H}oh)m;mGC z-3Vbr)KkP$ZAi7i3FFpN1UhlI)8ScnRS=aZLd-#4=mI1fiZ3vCD+6df{Bls+Lj~QY zOAXxtqVO1oTQ|)Uv-Qu1k25)e#^!mg445H&XtmSfi@NE|=Rad|N?a7OgGZLYCdBr*&9g`wk$p*uS)R2St2&j$4Srn zAi=yFudXd@*hJd8`57Eig`BNOYRFmZ1ymITT-4;*Q$t};!s*Lqk*MMc`8YQH@5iU- zXD%agIwsMFY^tw?HxfZ7=npcV0b`GL0eZxL7utN8#v`)bzck-)}W7$R^O1br- zYo3U#^kC~WGdi6h2B@N z;%mbdvP zQ6S+i3w=3V;MUzKlG1i?af{znudWO~x#r9TKS222Zj7jX72pJtO>#!3f-1mdC0l#q zip)*mxX&aK2rdrJRy*ELVw8WuuAT7Mug_Y~*J&V56olpNlyWj;??kz}341~+-<53z zWzD+*Az4_*AtO%qBk~uE^(Y$5 z>?F~B=)Bu#EyOP)!SefxP$iYm61%Z8K3(Loa(S2b=yvPXkPWA8A}$?dH|&T^ z$N_rFZ4!5UC+urn^N*&xiv1`%u{v`_78yE(6;txf`kgs9>&K%L-DvCn{CM!I{?t7= z@*#LlratnmxNQJ)H$9HY;WUs-qirc2GC{60x7SdW08lNT0xobnmyKr*S=NW=h_t7q zB$942)!kz2t8F&BgkJa6#R~^qU&{E_T5VInvkB3rvy(lb=O1Wo!R}qjNl3(&i8Y7M z#|GU<&9H>{0&u|P1%PAgIf46jHgGbw1DBcLwo**?J4LpCtei_pz-a`$2sCI@SG88w zC#n>q2~jEbHym}6)te)htX|N-Cg70$dM&oqF(I9iN23MdWT+v}%XKTtlBRwkpHW>%+?D)@Oq0P zrtL8msy*Ml{bpe~+>7E5?||&!#)&`aPr#wfrJA;1^rEA-VI|1uR;y8KO{QnMvq zf3s1Gr?H54zC;ql9d z<&u~km-I|pOnyS;I~S+v!|CXuiNU}g0(R`{5-7GQpM`!BaM>>T8JhSU_}>2(3I!g^ z2*QN}VfB3^W=~C+Bw39XI3C4>+?qbJi8cmnHj4GF>8Og?#@?Q5mT^rdVF??*UH4?( zgt=|K(>1w*pplJK@+&+BJEJ4qpOr8b^oAW?U~6Nok|Z*=3cU!Yq+cf-5*i8R#&K=F z`KU^@X-};*kv6PZh`jHXo;Dn;noa`z{QjaV0xV`nQCh1xZ48g0GV+p2G$Zu+k3s!^ zT2-d?aK25%`_@AZm}_cFQKiW^cZ+wA9u22M`(3nZ=uxRCo4(xhhpRWwr0mOhtN2~? z>!Ux%)oH~mLsj(Z0mPWlkd4_;_aSS8kENzu_VCGI7vVi{Nc>MD-)nHX#W{yO)cbjz z){E7i@{JCU5hgr20w0D~Qi&k^GV227pwL5j4wqtm)v34O?$fcH>BOAc7+Wj`st50B zW^t*Tonk4We8Mt=sagB{CX(KdQ9FTkdbGkLhS=vu$t$D7e#9C3)zwtU*tY0eZa#*4 z;(w=c8UghJP+}ui9%CpkOo02jDz8OAZ~-3he!%Zgy$Yz^#&0d`uSDV_=x)tIs`d>W zeyC=om$D)XP`>27mDmOmwxrC~>l4(nlB2Lca^v{eGx2*}Kje?zI|m~yUkAGkt)j3rtjI4gVqPPz3v6B^dPpz+>Cg6drBRmt(~i5Nv+Uj4rXJ4bF@o&_Cl|narf;&*u~<8x0fe z2?dm#AxR?62{j5Lf~cQgwnCP{R$fZ?j1>)mlHYJlyIg#(y&9QVV@!|kLY_Oc>1U$d zmj*0{mUc+R>{W_t#RR|hBgARX%~c&6zz(-fmWQfqZhx#RW6ZM;use z6Hc+h-72eWv!MZD_mfs`#pb0snQB~J{qgbogO?kR0XC#xk{08o;tcg1q@7X)j0Q-J zSQJta@f`C3K*&u&c8aMg44$aBl+zbghoOPHknNLHyD@!7bTFfcSjS2ex+6k-R!Z*- z_j(9aAz(W>;-%WiV6hIM!T6WtOW>scYtdHMKNuXp<5c^Hh8bpf_Sc2i>Mlq~47qt|sN{b6kXPI`U^PP(=te8)!1}R-VT?)L8XxpD+pWss{MgYF zA_=tSW9h_XFtHdiX)TcAF~r7T630s;sW+3;yG1jM&J8$kU=D6U7x%#uURnU5x@t@f z6eQM2-ccW7paFwS+^W2@g=JpOJPW(fwzOb^3c$b5N#daC5^w)n3l|lTxkRW>gm)fg zj*8xr=_6%dE59GN{*i73)$v6_dh@VR{4n+?BWIinYHe(o45;N{cT=8nWurPBwPm^c zh(l@Kj5%oHV}&5}qx~N7u5snkHg1c6CXfjiI;*$MhJizoBC(rxu+k)^`gv%VTqaGf z3;9(@lJjJ2jd5^>MOF-uY3ZJ59ey-6ce%veA^*Fku|z~JNQxnW$7oTKRKzl>L)C=y zjU&`H5fiKK%|9JUyTY{8oaHX%XM{Ww^AyF3kIEb~bsv-jWud7Nkpx!VKu`% zO$Pd1K90S?`-ld~ScyqMz_k6Rda8fg8}6JRJVlZ9qsB`Bh_Gh2@S5_?mi-$l?H8_G zZ}y{Z_;UAm+4krhg;lrK zJUo1R{1HvCs2?Fymin7Nue|wkX%Vq)`e40!rU@if{oJYPf8qTp8jMGXa$)4KV zYh;vs7Yb_D^huJAnkY7qYDltU%~K@IIt>jsu|o)jU{1&*6!Iom_*(>x)%UC|Mg~s) zy|#kAGX#BC&;>Q+VCO4C9}Ma-6NeSyrcjIk^%*yMvU`j8P*XfiR&=ueBD>p8BXLw~ zb-H>Qf-v!qJ!!D?44eP={`del&YVnZJj2*=3Ppz-P>5t+9!BvxI#~Ai;v}vNCzv#Z zz>)P*i45h-j3rT4*XT}>zKE%z#JS-KSS$E&b#?9e>+hev*uKB^Y#WY{jb|@kKr*8b z;u`Jike@kyvH>tC)P!vah2!s^jclz7$GtlQe(r7l{QrO1hj3cH zvG^w*JX^et|4jfT(mr7=#ezFzQC0OGWU8a5M+|~#R<~dfw)IN{7(w$W5y>f{`ac0x zW=)p|pL0Q|vN@?oj~~4LUd(5_5OLiCnacAS1m*qA1?0^R4q+YQ_8}-)sI-I5X31W z&c`%h4Z|M9DDun_PIx&47a8495Fx>&2}_SqC?Rlr%Rt7vo$ep9X24&AW` z;&}%1p>3ixFJ{k^z05gNp}=Qr7}W};HWMFv)p_f^Uwe=JU=0Zjj#qQ>;`peY$-#+e z(V$U(%JmiLhi+DIor%iPi66b98uJ6cT_j+f>XoyZ}tCu8J0|MbI||o%?(&Il1m(R`#sGEcDZAD8Zy02BnwP430kx2o6YOGb1W;K~I|YJGYlQH#&E| z`7`BtQP=vv-5K8@e5$i7iRm_N|7d)-Gi!Wr$Dtf=gK zz4~+v)L^)=6Inc3 zCr}1OihQy=vgHmgwczQbOGphH%hys0amN6T;F^m`jHeeM!rqdfpvCm)w4#x73Z-;xg@4sOU)ynCb~!7rbB)PqX`ub}GESIpVyA!qY^a3ubY6tJ~aJ2SgOP-Y@h z$h&otvO9%jg}uh}St7!bR@5*D*z$d|3c^Gp4(dHEC0cbvvav@WFk794BAFx_djI3l zgs7yXXePAdAgY*fpJhecW|b4-i`*PKTe1|tSm?z8;tS5V=EB-sq;>prc50a?OkM5m-(x@+rGIcS}DBp;K?K4`>0JlD>6i%mTzSF}{R)+%_ zn+3VBo2pTPR(JueRFM`969OfgIm-ZA->xo*?n~abgHW@{5$XF=17IQjKRXoAdVu4h zFm4gEDiY&ooOzh%v_f&~H`!YQT!@TXR=5FA<*omj?#DioHOzu=R#Ee0e0sd07vnv9 znb-i;OQeBd?8Iyk{J?JP^j(tB?=CG2%uREyEYmMF!P8tTRM~tat83){Z~)rW=U*vu~cn$ z^sh-PBmKula%5Kb=fQN1qN1$^j#!wmbavo(>N_y$^(oM*DDvHT87g-p@-tu%?^3VqR$8 zjY}SIn+p=(=&3M@vNIJ*v6Dp9hkUhhP>p&?JAWs6ZBR}zqM5XnU?$v-!(YS3!9_kw zeZq7@@`Y+LvDgxGv+1;1uX|m_rc#2myxu|4i1VX8v8-T2$~8;4AU3plk1P*3K5>c@ zDTOyXrzc16VP#D)E^N@G)jJqya?Dp%gD9w2jg$@Nhj2YeuFiFA)@A<7-Rr1mB&OfC z-wh+Z!c9^$2Z*m__#C+rGyK56T7@qd0-Iggvt8hP^M!#tK1WLC;ovPzSPrI$9qySf zH5VdqhR`WA0vBC;w#iXrKfaCgXW|nA0+>P^?ohqwm#B6@?qcACFW@SaF8{pn?vUFD zKORRM!Vk{T&GRjQEW`72M?61VN$W~cBpV0WbJ-3k?3cVB>O|-{?nAXn#Fcj?TPC?T zHF%ywA&4gaSl0krC zDNrTAwQh7ph>K}&(t#dEWwgQ0(Gcb_EIrV}#^d|z51(#7dHwu3LzkF0g00!B7x#Za zMp5TBfXWT_V8NkmyFu8qf8)^ZM}|!l{wpIXSzLpw4}=N`at+6vUm~1P`IK}(L)XVH z2WC(}Jiis1Gwro|S;qtH}~fzVQSt3g(;ocIi8!;(frK%ouG!}Vj< zS$Oc?Z2{+U!WZB6@6H&EpW8Oed&oto#!BL5sh6?emk#;b{Mq%7|dK!mfq|tfuOmS@RX!gF=33? zizXmgg}yAr5K4k1K+!rS8tRNw1%SbN7=2(|JLLdKIw+;uffa`I#b|uPfy=r*r|(Wu zt2)}=->$qHj89;8-&K1Nt|IRJ$?@&jhVX?<*gyJVI6XYwLwlNLNzR@=$C);E2eUbm z%(9n;uXk`P4I1`rjBJ0<(;5jzu07vNu(Trb=d)a%2%RdaJ01XjkJ1!p3#Ud z>ga~3Rgyq&5MrE}&O?vr37Pt8f@1YgU6}B*kmupKx?G|o>MA-%CrTqrBB#=~uk0F-AKlPBY#M6<^bkk2L))Sb?rj!#dg z&ya%xU(l%NXF52@+ag>01jFYK89@~cg0eI+3kFP}@sD0h(H7orlf2{*wJ#1&kB7A{ z-VJwPe@Nza-qr5W>B;aZ!c<9zq^x8#s3pk^R?K2Cl6P-+C$FI5@Q~jWX>jGi!6aq+ zPd`hI8af4V!c&)dYx#71u!7T4JjiMP-eVDCcr`z++*^{vQzj@8Q-ko)1d?Fp%9@f> zHFuZEBNYV{U*CzZ;cmiFAwyh%hu{ENE}BU~_?og-77o|^1o^!XYC5mLyP@mpuT`dt z>VP+#z4S?H{k;(mRN>|y?IeH@pO8YsuSlIY7yLHOI-ElQVIERUO}r!8d%lE;TC&me z_X4Z*B7!a#UKzneL6{Il*!-J7e}ss;00c=pIX#Cb7igSddv-G2?=5JtBG3U27A|MF zU%9h|Dwq7^$hS2gMKMq4-fp98pBHKFeiB|)iouS+~DMY0WXqgZ%A~H5)y-wYW zn&WOEnOKl20OA!W6G98+)K=&F;DKp*|0wAfghl-8xan1p0uilx43?olM^~`Hl!x*X zjc^!+lK`p4e3|BMoh`GjBb<5n-Z&Y;&g6JOQ39ghv?3d$RfW4qZp!b;csG&Wu6G0G zMGS{uLHNIy3-N+G{?o=M821L#?=n3yHzWX#h`>Lx6n;}*%8j+)1-UBQ%R(9peYSqq zj#cYCAZ~tZLgBB6o?`e6nK8~l5y1Eca9LuVPRRsra>b_$b3}QvCMp5+QKS3_62f@M zx@eMNf=BJrgx5Rlh7e|p((7i2@tqK-^Eqi|M%+@{K=ptL=rsl}l})k8go zAEM0c!4MW%wyd9y*@k{Eh0%J;bp^zBJetymRU`B}l#1}_2zZ${eWkYUu1!Reia{E!)^I?fM#Kuo~?$Z~ircYTIgVjuX zdRsNwRb_zLdhi^cdw#RN0F<)7!5f&X@YbTDk$xBv-A02f7k0H1tn{dK2e@mQINvC# zcEzMXTFbwU&MrYxzUn0=yV6~PKtR-nHN2ZZKb{{8I#KjY=21^5W@CM-wXEI9IMid@ zz^M~m*P6yvq8IoRDAjqAmz?x7JQv$ceguNKxzMz$#7(YHlsJ4W)4qM{7A|#7H=24O z*0O}5?Qa}B$BHeBF@Z|;NGMu>!3x8b=``KGy*3F^-I&q}d4bOWwr*D}cfz0RlmRzb z_^S4qxMhsdKmb1|P>)Nr@z76%X{kbap!1b|9n34e1dz+=)LhtJv2>1BNA{X+Gos*t@CuuqmWdA6a4q8o?^2;zlAu9u8PbMPJ2G zAceG$dbGMfd2<-MY9x8Lbyf3X3?L&L(OG&c0iycq{@l)($Px|;OcV_kKsCO;=xjaK*p$)IT&lK}V7EPCKB zYZni4a6nfBE2J@K{RsynN|z&eX{$XhZfXG#B8a}e^_40ut%d&(X+#JzqTW&O>sz;L z-hfCD5kWpzue{3v8aNi_AHWSs7vwJNxX$1JXK@g1*5KtW*~`n$&tPL%84{JQOZ!GV zTRM@jrIf>1g-F=VI@KpCm{zFTxYIcrhz}Y%w(Ump5PjW$7M+O(A}QzayOPhc}W}Dz7E&=?@SJ55##% z-FT@$O@jmB7(Mq@>d)Shr0#;xu&cdzh`l!EU3U&yg4Js(=O6!y06fyy>*CpT-Du8n z@wQC#ByDP(`-vQ(XbE!1B^4t#ixNkDo3v^PT+`c17_&|guH5|vMXVl;@@RQ*jHL!8 z0WaevMG%-=5fQPvI+{qn1dlqn93+D#mTLk5#xjY9Hq_q3qsfr2ePtk@Ky0SUxFPmR zaV7_Ib4L5H`7?@Jq0rSEscof)-*o^&#;=y`XbX0a#<~$pt%GBqp>f~KkG+DBG#qT) z!_pLqxRior`zR@ik^$%AAr?$3p&=Cn&I*_b6-Xf)L`Wfsv`y{6l7@y;YTk$S9U5YUDb4$v}#UD`sSrY>p7;%7WPJd_g^6)>5=2B>s0h(*W< z4sU1Yy=X0Dz0k<(ARA?jv^F&{omiX-zy^mS7%s1I5#9TT!pc5RtZXpRsTV&*M9o9{btjF`04(Fw4a( zd3ioPJ4Y!#EML(1s=l1fap;@$O%wWUr0==~0At3M-XJWVGSI~trF{iF=;ceROz_2| z!#GoCE!PjLtGIS|zym-5uW+u+XcQ(uKa5h03QGMuN}fP&C2o!5+b7}^u(B$orqKaz zdl>HN63vzarCk{o9c;FY{*3r}E{oz~A4gC2Zgzx&)K-LV8+63Y3JhD24eFHAz!amA zl_p?BzJ*9x?p)tG*+N~4WoPxqnX5=>m0s;oQ+{y)G>tnolYLndaC~({4pHT`W4t6C z)@+=MY<%dYND)Ddr2iB!R6Qj<#w8O2GE6|R&W5U>zT20~oROl~&m zwXfDvgkp&iUubL7vdj&^M=W)zE*;oXX5hFBt{U}lw$>r}*rD&R(?99l>3r;<63Y?) zzKy>J?|$iQbw5ZFUT5+4?^}q}V6rMAnr7|OgW0)-y`lyN6$|q7#qg8x+P%za4UEEVUA3G)ciiMs)HL2ZV-sjkb;IlW}tkLW#Oj@-tfB&WJ+F| z(sd32uTwyY7S#Xy4e>h>ClqDXxs~B2Ov$7+x&1H6CNjhov4!lerZ1SExD2mux)0SI zJ4*DU7h!>`ko2GI^p_5>EHo(b4}Z=G3uGtlB!9p7KrIn$Pw=0{572R;SAI-R$i+3N zIUuL@Y0@MbO3A}qOl6S+x&R*i!=eVDYLI?zR({yr+FE%dvSj6+Tq+^8Xq0Hl22uJd z>`_$XEnZ4a@oVWhmhntKuZu_k2eBXQ1Ysm*0g1U|0?W+-2?+`q6`^P&Zz*|r%&8k{zx%sF6o2GAR)O)?JQL4D7yF*!`0| zqb2wtoF5|7YD1AXhD{OpjkwGt3UQ)gMp1>O5VY`SCQe+kwfbqwtwDolBG(CI4VzB> z0`rP?DZ*{m2-H@D%0N{eRX&}5|Kzm4i>sDdak2DPIVk|EuB)Qp4c*rGbbOQQx1@D% zAZ{fFGY0XlWDgTPMgCa)LHdI;CGFZL%;U?Y$<~d|mp$IlBho`Z!U%ebxeZUrfo8kG zhfZ>Fl;_Tj9K*rZs(NLi#6Ght8mopi`j_%UCn>|hyZXgswLN}!HLm9{9*G^No8GCz zJ0z#mIcr{I{H4+%(z~kVe-3~20*&>~(dj{_e;fR&5&rMK_yV4MIdHd@yLMtAG(qG- zjW0Txt?L^zWL7cMgxVuPRRd?*l3FVIOy&##jtiwQ#=22}h~Z z7{aTOYN@AM#1clW$|T<8wojAh!`HWdUxN2yf3iaGRw$@gec|V=4_m)4skuyFcAdcD z2b(6O?z|CyY%IZ0E2#KIi+FkvlwpI8aq%ls(|)THMZL4;P z%b|SOrdrz7L*@$k@N?&e)V^4G`uP5%PNE}3^>7Jub;9daZgiIJb#91K!9Tv1xS$Wm zmT?K8Et^g^UpYsbuiNc(-B=sL*3E>CH>XDRzhwb)1(dzwHeXfGXK^H@*nl@3JO*1G zUT*w>?cGQG11y{(f-_vY_m__=D|oQAtp6UK@6ak;b$5Qxv?f+Bvw zUKt6)Vs7yE{*^Uf?8TOinXMMnIz`Ahbaq zHp5+k^D6v0?nB9(Emu|6NNo7RX;VfAvVq-6^z?kEaO>d7Wj#A`%(UXFQ7>8cScNo} z)Kc9O#!nuWHfQ?E0svXBIAQRtT2QGrpET)eG+DEIwk)Qm_(&az+%hUo^xce1)J%LZ z7Ki`0a2z_4aGaGQqA2+blCH7;Us}c8r&gedj%j>j^@$*BdAYlZOWmTXWgm8Vf_Ms0 zRgPal$R?(oB^HI(U&q(c-gVetd z`%-bLfA7TOdu&P}5RwW*QFYdvoIY7E@{6QFNpThfQ^;TETdtV5@4o7=M(NC3P$5G` z9Xo$i5G${Ik_da7TX@wR3(OUM$^DIaKt6pnMa6y&gUy0NzLn|P;1PK-^)H~0L4gQt=v1%H6M8I5jleLvSfrppd<0hSvf4uO2`1s$tc#&h$qF(7 zyj^+wRTi?#XRV*N5J7$F*k$lv(-X%{x(ec%)0Z3BHAztJQL5;DLlqB;DEIv$Utwi48@ z3Yk_!S?!}t;RhR2q;c&e{Ik|cSfc>r{Bsc4p$rol`MHV~xm^}a=&bx3?M+8ODpgIq z)qf-n>rIQ<`Mk-L=N2O~dKGvUk-YPSM+Mf0aP&!`I$R#_WiN`CTzI=VJ#p(^g-ka$ z>_m=^Nu?v|g?sRveb-%vD)Jj(qQD0&1V=8GW4_akv0@}+GQ_DB1@P%?;sQ@eQl_no zgW_R`&8~oeG!&HKkYuSHRMh+C>n>Yelk(HdZ3ZOze0y)W%j4!r2eD`IJ+&oSC5O$S zudyz;L`E_)_K|H(%;0#3WYJ{!i1~N!*2WUgJd95)e0}R{JqVQ^l$X^`&IYRqQjkQy zA2qKhtw%WgiCBWwfJt+L_GQSup*JOSUtn^s8kZ32m$yN{{HPTLa&0C~ZE_jXgk*GZ zL8b1I$vyKFKY)Pr%70nnMgL+-8h-F(n3Yv&K>jOWK_XZ&|VoiG$kv zWHlZPP|Q?v*FA;MbJ%@#q=4q(K&$0s0?b!nIDLWSw>8W}^166$0B>(fL5cijA90pK z=Jnbo8Y7on?bt!nio1(C!G%BioGTAih+@Wwu80G)LIdVodw@H$e&L~o0801Zuuo!{ z@Kmdjio$!<#FF$%a?ziHC1wKwAit4U-)!e|R7=zialwhWUJWnzS& zuXynWdrxxsH@kX8ZbpBgqd-;}D_)!eec*x$KTC)r3-J8u=ib%07-he!em+#vOfjIC%9JDP&L!wLiBYs z7U7PFOzxE0;G)W_#z1ZwHO$DG4Q|J|a%M#{he$v=KR8~c?77rbf+8x5^MC+_F0Z&Q z6Nn27pQoGQfqQS~$u`rqC=;0}W+(4PU=^sc(#YI}CEY+4mk6mYFFaWbY_ z&RVcW_kt1b*;SJg)^00eHM3|^(vOi=eX$}Tw8hCTj`eDchG#OPEu=4A3{jgWdm$Du z)f(>Gp>S5DJ=F(#_(PiL_iRy*dA%D;zx$DO1bd6tE8KfO^`%Boe?y>yFrm#h9h965e~#q6bd_QZrDW~iT(Yaz0zI+{LdIG z+j9-;2;o+4mp3;0SK6UsrJ2N%Qd&pvomGOW0G9v8vP>pZlOjwY0^X=9w{c6oC}DD> zw~gMiJVbQe;7H7L0E2$e>3vJr!reT>_1hJBxMUSNOi;DZwzJt+tE!ZYa^|d5CLy3^ zY*Y}CDySBqV2p^;N`SfQDrK;CfY|3F?3aYsOtVMJzTh#*Gf`$CV-UPFzWL$jpZe?G zVSjx{g$)0|YUjqSMbYG%Ib|Iv73BcH3k?apMwa*2p5b&V z5QAHP4sjv}2wKLK^w@d`Wn!-!iIA=bDQf1*4yF_GtRub7tYOwxzO05%kS;ws;r(=W zP$&SKCJARsfl+HyWTirw$*rOH6cvx5j;+30J%Gf}pmW6Xv*S+eVkX17T``+8QsR|$ z4YU-6KcEN{>3UX0KN6!M?h;B#O4qr+Ih3%g5Jm5cJ@}E1jOc6L`2M1*BQ*FDrK zT(T+Xcz<57)aq(i>#}fpSGJhw3fc}9$KU$L{k_i9)zKUi6G0)ynBFlkFuy}v_cMS+k4LKCDm(;^sJKcOT^I=o z9rBJoZG!^BvrJ{sd8;Oo2ooSIV1|o?DbHNt77t!fl>yu8oEI$Sy$Fm+GktjszTCj&U>6KJ4KWb!Q{ceU*P=QI8Nevl`4KY(8slZvE2vnX+ zrI-vX<}r&I1zH20VyYTx2(Y2(Zld5?OS8HaQ$qAPO`4$LFzu+aL^;*0M2SJzTcVA0 z9WpGS%mb9Sa&Net4edYIspK$t&4J{anZQ1YzD1If>%&BvFr_t?z>t)w=Qud@)OWsr zo~cgBpYB$51$XOa=ifBzwab#TtD2rCm%4#^f>OK*JyU-AZ?rAqCPBZUGnao+pm4lw zWAJT{X^eOO+_^Cx9({M6^}nx6>N&rF){TdgY_n$hg}hu*GsN|OFLu@a+_hCNoH0vX z)O4%pMbdaB^A;PWY=w)|UC+*aKG=as(Eq}@Ydloi#)`o|wm0eIN3V6~j6MJ1@K{Q& z+5^!{OqjLb)PAavbd2xDe{%VJmrEPSQ$m+Vn=i0c$0Gn5&%Nk26r6$TyR z7C5uIz5e+7$3Ic#!{yDq+O=Xcuy;_sgz^D7zmq8|iboe1xHyo{1SQL$ky3|bKu0CfX zz&%d@4$t7+ui6}N+QLtdgd^S;JnVB__;r2CY}QE3do}d$Y4PbUD~!m>(ye1y3r?jf zu)4xIu>LGWB47|n*x*iLn+0pmW^G1X7&Ed82F;@uf6{AY^$uA%^>2Of8-N0;F~I-f z+06!r*!*is1+i=fS-D6~fXXf9RIhx%@Nh2560Kam0`e-&#i*_s99nWl>Q25*2}(Hb zQ=HGjE77AfdR2I&Bvi`7-4A{QipSZ+vz6qtsY&hPj?*-=B(Eu%*v#HG%c#u zs%@wm5TK!R%_35X?=)C-cz6=<}& z@lnR8^=va3qgvkEA@4rBhwI0Xh?g)Bc*UTqWRiczeCwK=`AZUcO;0JRE0cmC#nvFE z$Lms5$qj9g!Ey1+@5-Yumz-AFO**HNkZarrKTBkLfq@#Z8OF%as(}ZC321H%sl2@Ht`p zf`)>}O1fX%y7Lnx0t)w}qX;4vePYXiFLWWx>Z@ige5r{wKM)%5Cc?!;39p*Kv@{4Y zzvaW_!)G+aC4PJnxAH`894)dSX?HnDz)47BNc^iZk_aG< z=Gu(7#s7wfwn+q`U-;UL&+&KUTze=PJKB@izSZayHtS8M4T{cm}o2V5*HEdM&(Gmj9QzX(MoezKWq zJ?0Vm>?aSZkqt=LET&=nVp)dZF^fwY8-=m$Si53^m>3glS^)^OqsuLE?`3aA$dgK#XO=s!-jo}mT{qO z&NB*w&vBW7-8diZk;;yu=c-CK0Zn%q#0v?c8oJPp24J0k_PY$suF5K-xbDs9M{K-E zXrEooBHFF-ZFsiAeGpsAr{e?O*sHN?``2nk(a=rkIEr7jXqh+gX?GbM9{dxCB4PHb z&q%gWPcoj?YuO|p6motb03-r8gnqBFBqL-E5Mhl6 zEai1STp$o!a!I$L2kbd6ciHi!FC6xv=`$07)^g9VL99na+w|a7+#8_xH~_7ez9}G7 zEBEC91zzH6;k|f$up@vQkQrA8L#R*Ak8!ixdpPx*ss_6W(|+-27kxF{80-MCqo!^0 zzwm9rK0Tw&b~J@Z3ect3`mzwcT_b?=A?+2y)d@je-*PvlHgc#K$_VA410Ts!G?svd zjhfw~JlD)f0R8^wyXhC*|4ZHSi(80K&yU=b2Y1gI>M^kTf?jWp$U1biUpj0{WKKQq znspVi60Z;(ZNA?DP4S;?|Fa-kX26CG(CpocjSMlAU!IUs?sRm9wVuLJifj6C+4_eQ z#8Q91UU%`Q(;+lUBfZs>9FGbpn+FUx>t^$oqz$|O$e=FOb0d0Q*JjD6&TK+yP4+}I z$11rK*ooDqPRPiYO_-=lY%z~Z$e^mHhZ$LLw3L(3U%J?T?9I8gBU3v$bQEZo9EQJg zVdv(79w@U`$-YSTaqiKz*Tvp0<)Md%gOh_Hw(2-O9+UKve8&otEI9ZJ6o+zkLCPiw zZ^a}-@jkiJBe|EZY{Twn=UhOE=n#>6T6Rhra3RKb7o2Mf`oTPn2WY5kevEOL?kC`%V=iQpzcyog#!0U;!cG?bf`@F-J!qMc^u??plDr`jp0am?@m>w=c zOsx95LPUNR_gBiREf*Spi5rcBM~n`!kK!nUpA5%CmT&NW&tFDICx-{Tl;L0UP6i6{ zrVENexOMwAZU`js&~QJoY?rE$K)AG{w3p-mVa?F$rxFB=DJ0h{?Lx?$gAXJ zjPg9%GCNV7$H!;WP~nB$^EUIl+l3Z6zHU&v@w) ztcRYd3)W&jk7Nd>#Xa_!&!#j@VdNHGJQ%5sPP)>^YxGlK3`^+<#(kbZ4XxP-AQ(?! z!dBC^CJse-W!xTqk4}Ny#;w)torYWBIWp!ytW&$;Z8p|zxEULynuuWzr;=~W#2=*j z;z6-Swu^KMT*tGbm3%KAO= zqGont>S~oPB!%f6P*x3r=+`saN(qrFH|5nEDJ;@(t^n`OzD6`)oc7?I3oX6I%N{PI zPBn@G*0PAg13+)S_5if$8=a>#NVWE7sA1JW3;sQujIj^FrQ^LWa@xV* z#+)j^-SM7oMj;{75uWWqu#NUdnkF7Mt&zxF5~W|Yu`{H-od5$8xc4>8#1Lbosn22= zis8a4WtoY%wbS#({&@EF($>zOMl|IKV`Aj2nc9Hl6V)&r!$JA@SJa3>ZaLcOet0e4 zdt(Vtt+GzbT!ZMBJCe`O#Kl&~UT7pMC{Z&C>}CRyy-CgycSktj8Hn*czZ%W0RG|6V)#}IZ^mJMk;H1x zq4O3~j(iYpH}SRS)*MQCy`~^ZTB$c6c@R}V^00JkCa+ip?;%$q2wRG~qycP}lV8?& zVKZzx`2oW&dG7;y6aSsJBAniWf9G$_we1b^mx73gun|(LWuH7)bW9YG<0qAjil`r zS`-ox^34wFyRdgv-}ZrE5aZF&`;dacN35iFTaKfy5vlcpH+?H01|Sut@3#u`)&L8} zD@mq~(a~V05uQuJ4(=wk-UIepdZ!#nEExL|m~8o`-oQ0YTnYMsB0=G$U27=9i*P1O zjn-(N=@L>P7Saz$xx95=XIQ4jqi;?#aThd7^>{len}L`pd6jtXvIt^o`QqFQk56cM zzT9l7?;btUM4*aaT}3kZWXPW~m{GUet8r|S83g~VNk+OfV8sbg2kp%cf!@|*ioD&# z?paDmyAk@(T4-sWDLc{CE&1#{bh3=Vh$iHyq>4nPGD+P8ViXJywk6Eb>wfrIsx|fg zf>6lPy_H2N)RcZ{@sQjHq?y|&`Yyo-YC*1sK4lpv(8k;NM6Uh~2}s^L4ieha)dZ1H zbIA@rT9%t}(722ryU&q&fCuQJZgHWB{ga0cDQ5v4mXbt#EsKgJ8wNMTXP(H5nMKvJ zHeQ4@9Mez|cB0UE`1026SAKu=WCT%!s^#{Ea-E0vj~sD{<|Z;AUGr zaiF@GcA2nd{0VjpkwKkpe78mhW(iEOR^-E09)s;J89qC>eXed%R0_F51}q%FIL?-t zgIs#CSbL899A#cvLS<)tQAB({AhM?SnIkSF_nPCk3Oat8tsTk0l%Pv9IXj;{<7F2Z z!e}|4v zEiIR)|LfF~AiqVxJV-s2oO?+~zKcj^=8q}_rKWCj0wS1V`nB#q$JqT*HDpGktEQNy zYTvfBCPIvaeP=5(ZI>ez(v494UreA}l!8Eg8IiX@6yRRBxG3#p>DVr)&=#XG%wwlU zYGJ&rdNGODk||X3U@=lD<Dm>znAAqHMpj(Z0L|{(U-Zp zI+{E`MV)!#O^>ZmZkja^z3gSo9iY?HSSx(jWT4q$`i1MXnQ2@>qo16f^ijlo$nr8M zebxCu%=~^Gd~PB6m*>;7^Qpv9#9q=Y0EXoW=>kgFPtV6E=GEigUh)^B3xw5b#PtEU zBa=!!(Lfv!S*9^mrB~9IFbFIY-o}8!wK~!jk98ka`{|X$<^eGm8?1S(%9_wd!++76 zS`umQf+ShNa$DTz2AYc(sesWs!R`Kp$I$QZOiqu^r^8yB=rMthQr>8wE~nu5FeOoO zL17%#zb|yx;V`uF_MsArfScsKqrinN><}b{W<~h|w_=qpGfoPm zSsKfWBr2S^0qI~rKjNvz#HVMbcW6Uyh!$h&-lc}pqjTO33FvbL#b(5qc&g3)SUA?k zoR1=c^#sECbo{<}+8WoQ2kld1Ze{t`V*JNoQvQ5&n*ZK4w5H!0b>{HfdiI&z>^yrJ@PgF1 z)0-dr$O~tt*MYH!j!Ti$pxBfx)&Y`oU25k(TdB< zGp}na9=i=u(lJ)qCD*egep>I>28ggqf$8LVkC%DFs2Gkl zcmxd}g9V^`5mMLcdL-(Mz=QiVv};;eA1r|&8X6sVH!^_8ywJbVin%hF+|*Jm5YDeey3EToS#z|=aP5vS&bB^ITvU@6mO z?;5|+Weyq&galjora9J|7A4IbQ)t=pN=(o?6S6cGi4NKK)A)*aiUhj@n8$ymyie0{ zf|XM?nu}=IJO8WsIN`~jg#R+mq%6??{ttU^-WS)EoR9ynS8>hY>BdrnHqUq%TM`0f zk->rh^27nTKMgdZW9TN`pk-|5wSS+dmb2a44R}fBmtT~OfqU=S>QvRK?Nl{1okC~c zq@hI_bTH%7=fQA3<0Ey^*ar>c)%^L>KW&IUm@)8c%~CoIIJ0hH5ZM493N?t~&)+>M)9(HNWE`Uh4O#`I>7 zqjQp@c=XR!s~%hWHgx!^d81k$iI+pLYq4kEuOOnf+;wB&jZ`A0AdlP+ei?l`d-t{NndDU%46D) zZzy%6q)GiTOGzQ05hOnw+3235Yp&OC`{R%JADIEo@AjF}KEHAdPSz~k9y_HY53wDkAd%|5vZZx*I{Dw${f&#fbuyJ(s?y zyJo7pw!sy7m3p}i780g>9#hdbO&r2d=_LO;P=@yR&9msv!Rn2t}cT77fQ>wW_ zm`z32Q^udzEG3J8{!Va+#;$)ZF9+df+HAr+f#0grr4aB%YIpxFtAxJwd#^LC8Sy{9 zTCBIE!PDNcNk~y_2icfjXgk?(1Shyy{!5W|D%kD%lvO zf-S~pJ~M4@{09_b?F0WI9VfD@qj}c9j+~Ta(B*cX)RYr{K~9?G07BYSF_l4B?h%s7 zhki_EhREc7t)sw<5@8X2`W-nA;Z-~2m`NYtfN^6}bRh;I+{_-rMGOIC)(XU0nsCj3 zw)?~2;sP=t1gQxW0q)t1+0!#v_o*!+A-MLYwRSQ_ko2Zns;5q&l#yE7xV`6-w}1s4 zeZ6B&#!XCO3ae0I@=F?0pYWEVZ*>aA7@ALnqcQEvC23H#zeHDb0!drmP1U#lbOC4f z&EUo<5~D(pafI#&Ku?Mx^UK^E0}{l*axa5r&(2^2X(K)#_w^?I4k3&G@e}lKhDYv7 z{}^y1*6Bzn_)ev*kB5K(J`N|x4ffpAw)?K5y-;cIEq8V>l+Kwcry4?s$V6=`OciR#)+6{p`>NZ!Y@~454$0n0yPC;h~NFNYGHgsUP`{vz+ux(R&QGD^g(*E}lP4luW|3_8>?4V3oeyjL?GS@#+y=J_U85dt6_QU& zlvrguCErb-A{!16@Spk$W}?~6cu8vmzK1dGFG6xWE=h>jPe$3SpN75TW3$INQ`GaZ z^MpVB8TNBqW1)bha^Qp6aLM?Q(xosruMPA|&z9MIrnQLc3IHhVdBdYV*m{ErALV&V z!j^cHlMzG?7VRwqJ5Wb$)|TK*QeM@}B@5n7iV>R#p^=KU$!QiPJUP)+s{0>2)NBHB z>ixH#YYJDTG6hP)Debh<=g0#k*6d$(X4yJHE&5w}a{V)(T21pVQG6B#gOlbQTMrQJ z_Bw8EOz^pYKXBz1c{*$=h^zFgp6NEqZBNwCMIwaOU-VxGEwn+_qsW;V|CGsD{|KKi z-X+H(!W{Z1gAWvot{B7PI3j~T&?HkWj0uK-|H8sT^GeYj@y)tfENW-}h}zkI`SH&` zFWlu7n%3e6%2??lmBMe$u2>rx0 zcA*;uOsStK-WV8kTBX$@_DTiiK{bG_k5OGAn3q9TDo6rm8NA;ua98#5{s zbE}@`RQ8n0pm2%^v<218Hwk&y9e!YnCWg6pqb7{O0W%N$#X8OVqDi zf6Vk8xK%E5B}bo-rTG5QIE2r#a1ky~b|mXfvl`|7`Lm+OMvpQ8=2kJzoPz{kQ!szI zuKQe$Ek7A(h|Z!%Y{yKN-xe2XqLme`6ZTOf&N!RDs`Mi zS5iH`(7(}s_Pt*@MA>tD0X`#$QT}2w|h!jNyQa3++DC_sd7)C&sc z1r}3ZtO11A%916&KZSh-rZ;NjiH?iPe!-0_svbdJuCp|<-!YqGVwS8FqxT+VKUab9n) zt?aF*!)P&^>7x`aILNFf7y~QWd%c^E!QjKI$@DRb793+%6O6I)dT(oUYh|sw_WGM` zhZydnjwthVj<3!ynqgLN$H8vUaA#|4ue-gn`nQ#@!*0pAY|q>DNKYmrly5;YzV9

FcK68aJyBZg`S9a+%Cz1#47m(GW_NbzqnV`A3pd~)6<8K^(kEpcY@7R`+oGW zsl{XD+^hHgxN*$SZA?uj`wKQFJB;M6WqC)e5T{m~;{JkoXNCgB83N)IRv4gB4*BwPt(Kuqr z0Zn3uPDo^c#L~*!GngmUv_cl~1&G3e$9HLk)`=z%wD$&V;)0cz!7 zg2k5aQZDRZNOq`tSMcWZ1jF z9l1-E-l7FkMs8d&7ie9ouGS0~eC8vG=v9K8H}Vw*NRX$$a3j?wHS8*t`kP-c_h{Fi z5%gpx@5&}XJE8qb|I_&uWFTBUM`=)msVsv%5pcqc2sT`&4?vS8dRuP8txn zG;H{5k03?!RYR(UH)7gxOCIP~sM`MCZ=DZ@or_acT&liAVFLi}+x4B@jjdPRjkT26 z?R&%Y1gTg&;wZ6A@8OmM+}{&A7OW9tn_Qczdhd{dg!n_dO!aYeHRh<{Q-m}mlL#KB zoAG*bbigI!@{Jt4yk`+vI5U{M?4RN2<=GvK-lEJVe;)Z=j1s$2u6@nLorGW4bVco7 zu-`^%;MNl)Fz9o_?fLYgTzD)4$Y1ElOulvksnweO*LXBLRG^t@8>l8I6Z#dGsxU`J zwfO*9&%hIjA!}#|ZVSy843>?1!^wM1t*`T-pQktZVP$x1j=VmuGOUe=Cva~d<7!Pk{rl_Nnlw42#7L)2ZT&afT!z7%$0cmwA%k}_y%&&op9j7YTY{n$LG8TL+}}Z zM6hlkH2}qvMp_46jasm+A(G&v7VlH@Yl3z~BcD=t4(Mnprp8Bu3 z*%p~WxUC|*ATeu2@y)0+JOFYtBU2*sl^0!;SC^w7`a@t@uvUj|hzQ_hz+W7_ThNJL zKJ2cnuCDLycK5daw*IR7dS??2^-RKf5pLk{JXt~-nf}tKhpl+H)H~vQx-wL%6V+tD zTX3%5GFb-BJ}wrEAuivJCmQoIn4=3Gqg*b1h7DOIaAez&TuF<`IuVP2 zSLJB|g4rVu1Zmq{E;>!wwEb4Lg?cf>BvMQCo6Dz-t$YRzlOUdeb^fEjMdHm!)`W$? zST#duhHS!NYy#1(sBw|kRLjP7GejMVS`xd|#VWc2H!O>xFsFD#Kxm%IXduLY=tz57 zjZPsk<=F&5RyJLYFu=iDpFY848eBLZ#{K`ig4nns1hpsI7Wp6%*YYj@2hs!)7n@Nn z7X@PnkfLEmSYDn&>?Zg5p>hY2)fi4z*kMk$t8pKZOHa)Bt?XJJn3~@@?_-w{TZnI3 z+pswCh8uuSiBuoHg&X>^%ezNk9Z5N_ZgRxpYZm^Y)amGLyo;;t9`}!ESD5D?rZ-Un zut3Y%aV(m`YL)FZ9_sOZNrPwOOo{w(jYV*lScE!$5F|kV?-1{KyqL!9Pw8n7kr0^T zusZD_N#EuB5pH+%uz(4&;{{Gp4*y`W`VO)PTm==e%a1w)xxUAROGHovOs;Scjp@Ny zx$BLD)$JOCSz?Ct@bFM+vSV%`szQwQ+JQ6Ehkx&%rprpo=~X>LB%DB^q>u$$8)D8hyI_;iMGywaQz6JA`gpN-$%J8p-jL-xqm-b+K?iv%1C%;P zXQQj*&cjDv+<*Az$B!N?o?f1xQOS*5H<>^vOZj4S2JyH8C5XSA*|&h*5L1U*rIK?a z+qkA1i1lh*2M+sAEmj+spekhKhV4NFQy|2!n)`GzClde36|!XZNoK?fS1@U?u)zoi zGd%IMSd~9rsY%p<>GNx?18Fb@)en$5@UK_RtvYQ%2Aiwe6pSH(^*{8Oi;Zxm_6z$i zGzkdnKw(+Ka3nSqq~ne_4U{9U&KB;%b^vcd z>5g#*L-26LUF~kR$VQ0QML*{r!f`f+k#pp%+XtRD4TLu89Pr^RE~Ei1tIC^9+s>42 z+~tdB1`nxbR@&0rgXl;lA!C@6D-l;upj&+e1?mmMcygh+h&}8_cJ;;)S0Rmn>N|)Ny-kw@*w)%m1=p)@GGx~NP9w)p z2@R`vg;`O=3}=F9HyO`2+lbiN>#dBQvWX#lP!c$PGZ>FY;*!vKxiXx<>|U=~nDJb` z#jijPi^pJ;F8(rq41NoB)9^c0ewr`1o$-Z;BjGc^MM4kcUa3^O^4zAM{q$-0>eB@+ z2NNDxDjBOd4Dvod{yZInDC@Jr5U{pmwIO;OmKrmBAu7N5fRYb|dMKNj(DxT~exjeS$L3b~iA*)3>J#qOkA z@Q3f)PnYrk;+1^*6KrV<^czanrvuUU`0QEoV0i)MmX=U%X-P|!JzYLnWV5I9f1u8! z!^sA#yB!a(C#|=q9WZk(JMjQ_zLvsYu^t&wC!d^5D>Y0Xh-G@p43@5$!{OoebD-Ns zrz|rWdkTUd_rLFT{&BzamoBc2>%DHKBTx-V7ELTu?rBteJq``M6zcKo@!3sKM+Q<- zyEUr3e2YP5(RI=}=-3w_=0sx*U*87XNR_u@SeeUZTchj_HWBCdPm!;Wu6aq@ZZ1XG zCxVadHom0L52Fo=hPxNX9}f7R&B0NBIAO(k`{#U|hrUff8My}tNtkpgbkio!X%x{p}&*{G*$O&zqyXMQY16wnM!#+4Tfmt5ii20fFDKFJf zQKC7z^TYul32=LD?{j&1bLHQH00RLfT?#z?0XL0KZC^!%uaWqd`;T*`PFWKWo?Su5dfHR z4m~yPTbvBpbnv(DsPas8^>~IK-;VtjlJk=i>=K=)Ch>r#Y2QVlHGCnEgWNCDnDtM9 z5rPaWfZ*G|?2SLdp#hlzm7-(@{K9HtHT1bZj}G6+Qpojy2inT^6&5^AsFo797njM$ zWZ^%YlZuYX`g!|ULAs#XK&E{2Np0weyR3Uy>sXIz-wi$h1Fo3OPDT& z5~|(pwSTK@l~qiRK?hX7@^gCvUM45DeK=WS4I$?RdHA6>w8Z7&yFcNXUkh8rhh z2cC>hE-@(y0)kN)=E}}Ql*fD0{=7#Me!n`HOLpvFj(h9fp;Qt{Hx!O-TDyf41Zv2@ zz#Fo@!pTMdXmB!M!4%kHVObr+S;cfoa1+rAdyI&Z(PH%iJDtrIPi-Aswa+@8bT((0 z%7Hz^M7v2ECv5_!Unk9Hig>9I{DfJa!NxtItUZCfdCH6t6#|D976a!fcZJ4xlW?Um zRfNeYRtF=zp2oQKx)>)|r&YSCFg;>wD#rb|ztZ{lH&UH5uExFIKN8 zkh>_KLQD9^8_1pPXW=LNsXz3U)c!=Fr3VKGk2(hj4-XC=Qy8rc2L_!FmPF>HZ$QY! zVN}qC%vF2{MY0@1@sJ0}h1Bgr2Jd!%@$F(I(LX4>4m@6>kFRr#Na_A$VX^u<+!6`^ z_es$;!T58WK#SGN1g~*S5Peb?fCRrWj_K;~oERxVP02n{J`BXJ(8i-=^`>7C*XmnI z4aT-I=|-P|Emol>fM0{?k2OdrL(4g$rQ;MEH?||T9rv;AI1k}-tt?)NH?PM7xG2SO z0E`;lw3kX3VKdN%m?V!3KLmUR=f0vCGx%S z1qB%FD8#&M599fs5~#2<9vdUzq9ojc`;Q^cn2NzsUmID|g`-li)5@~QO#zV;5(Ykl3;uh~9Fycb; zO!EWETm8Pt1VnLNnJ8rI-L-XG@mpRJNE-#7MmaPibBpm)4mm`7U z0rDxl1N=1F51Yvz!yi_oO1f>w(26w^vLMz}uSSr8`iRtkQC3F_;qDx0v9*jh+}ko` zAXl9XG?;wrx!P|sh22YrlU+hK?l0$&97&%^gpg7#W(46#>+yBZdl&5xP1&CBaac~u zSQat^bK3Brz_qh4MOp0xF}p;B2Nxec(G`c**|G)P)|@O9f4k^2p@P^Jfs_T~`r*v^O1nK;cwlt(qLb3(8<#dts zVK83DeH>^mn4_3?A)l$7qTAx-6cMD3ngDO%`sgDSdRuyqkJd()vSsl(G(&`w-&UR$NX{#K8nMm?ZcEEQ^TC3y;@7fDS3B({k%j&)~+jk+)+j6jFRZ z24ws10OaEU`Q~AgR*lo}r3Q15Zhjc>cLi_MLTnh-L2$o3uGLVqO)Y)+c7Zwy-gDKF z<252n4NI>EyE^U=X*scw-VDFixqQ6bp@CM=jHa4awH z{%vEsySBBrOBNJdSFeNN2#W4iycG%FPZ-=mdWkv|uO?^x{zd!2ef~pt1nSMmcm8)< z5sXuV5?Br+|A~~8F!l!^`D6b{|Lex)TKC1q&Mrm3#MXqq$>BvT09XWz_y}#sq1>TWFlTBxzb@6QaiD?WGI5uO~ z_-YEPCRWViq{!pyTo($H%2Q&%gk_1UVhH|d&vtK9|&uc{0Xr;95nNVf3TXcmCIXmxZ2R~O+fM;ZJJEM;(c z3T3Wh1(avn5AqYJj@x>p)Z92{88^2%?71NtP(8kq>lQpfVIF-q7+=XXYT6p?(1GKD z8JemMoRA-2qB|QM{UCN!d_r`qG&dw#eAKI7C_w-5AN}Jw!SPx9?>f5RQ>2-|yNwl> zyKAJHZ*KbV7jI+M$^UIwP2jrWhSfRS(dgnMjPk=B36E~iyU!bCFW@$~bO|{eNBUlL z5H_th0Kw#}*jfZ7N|5hn2bWJK95HLo>*5)cp2NSznYFvV`63)ywv;LC-#|3$WHT`G z6asUxzrZNgHek8i+uHfAySu)X0p7$tkBCM9#{l<8kKP*b8ac zPf6XfhQkr^p037Q@D%g|SWqK?UZktAsSSrE@=_M1;y`(>^49>nSa`B3%D= zg8X$@9r~ThnBnH5CyLIdM4KBGaJZee- z{USF7r82rsF3Rb^!Y0{L#1gyFuDuKQR1#Hv$9D=7NWdO&hB9jtEY9C4ApSqM_=sBZ zWHVI15#9tnq&)s;yV%7QTBd0t9YJWt4Iz@&2>)?8Z6Ho~vqPEKXu}UCD~FTO8A1?Z zbGdl*$&?Q+N5YAdqukt6E918kQxHsTa!}DeJYzhdZxICph z6>_>S!xc$B09~9NoeZE5dX0$?UYyLHSd9^``<4p<`jOOAKnbqeU}KGa*11TCJ(5nT z@2*RZ79)iKY2`rtR;_g5kwMIqfYu`7!jT@vI2K;&=|s|JEkxtmNIz?7wqbmdSbz4+ zpmG2we#-|MEO(#6C;rmNx%h?1q`A38Z7m2Tmmu$?H?{L6Z8fjYxEVHK#6g@Pg^Tiwlm{ryGc*-X6MRge zinyU}7lKr`E3rdeNNtWD266n1uAjyeGgis_71 z-AKKcG7F+WB4^b!@HsIA#afnPD_fisG-a3WjtQOFnoP2D?EAPICnJ-#&nV#}^Gw%M zE~gN~9EOGVR0~h}|36j!=g$MH0mpqs*d*?$sQ81PwGf>?+(qtmP5 z4;+OPfxzOdmydZiAe_XdZQw8ZmQ`yI%EbuYC)qQMtiyiN7QeDTR(5Wk!cFGSIaA>= zI;tN(K4>d_ARv%Y^h%D**vFF@>)FM|M7l&}Qb?n2XjlVIWKg0I`C^FMHIM-J6oMJ$ zm$4~>$>vDP22p!*%yn_@a5O?H=K|SC7`U<7e)`GM(!x*j6F;6RX6@5|x%B3zgT;eI z|DA8_UgceTriF8LJfSQjsd9GoCm^8{<2Sc~ba93Rw7;o)1-T9&jZ8dV@T8;FCck7i z4wj!TSq)!%G5=sWVwB9xU{UsE-^T*L0y|wxNC)dSl;|>0W)pZ1S?$d%bD>G%|5^L} zjdO)VX`UPrkcE3PkRok>Q+-P>fGp-*lBRbdU=nm{btC5$98Hj%pG^ZJ(LoI~k_pka zrZIzKs!VuTP}mN2iZuE?%tg|OTVx>FsbUW_QbZ7d)NRvBE$AsGWgZ!MVaH^7tl%(* zN9JAMq8%VVh(G~;%>e~R zpCgXW-XR7e0KuNrgGT~@W1n#a7*F@tJ4~4B6vV2J5N#|UW#7E7UpqP5UPmeKHkl$E z5M}0<>)}9Dra^~uC8rI}i0~r8bK&CrT6Q;uyXh?6$c+rzWt3}mpVDp2yR^LxJttUH zlFWaEK%YP>{wS=ak+iv?y`L-d0+-q{MV0fSG0&-ng_LI4pp_gppx)%Jox&d=@lAp-J+A z#$9X#;tM1})i`fUBI-|6PWUfXN3D^Gw9uB;NR~|-0T*3Ndb^pVv zhT4R4xg^~(`Yv?qJZLK2maqsj!W@+a-4K<#Ciz=aZQ-Nj8Gh9S?dd~Gt{QC4k7tx5ECT&m zoREnL!DT;k~*23LPg#G{@I(m&-TAx zesgzu|9hyQZ|=$^b*w<7jd)bdsCk1}4P5bOQoD-D^u^4E4rqepnL8Ltwgi|%P?8HD z;4=5A#4O46B~;30jO_60{E$guPjGvpnp>U|>m4pC&9Rr)h^5u};?B~uBtPu(!-vQT z4KW@U5&nap?2o4!*M|?@z=&OY`VbdSl}W+GWrcNkJD|W_$RBMk z`}FFNHT|Ui&V*J~KXlc;7rQ8tt7f`nR$a0KpbkPa=E}b3LMbK2qVX!{z3MGCm$-+? z@SZ&^AwCncyvc>z8sPJ^U+h5d1)-a-2bV9e4%bHKJ>0L~>A&rNaDhvSc7e!=-T6#% zFVypXSs-1w_e}jW8U@1i#5Ul65w{4n77*S{bLlHx)N4s7MyNa^fdoS&eh2NuCo!)W znS}JCO#^JC#nMvJ=D{02OP@$D%0p(?Az-fbE`s34UD^?vIHOMixHhjBV2-!0E>R)I zh=Z$FVMPvb2cXyiTODOB-Up|t3?jEY{o(sRkUClwFX!j{UTi9IB~V9|cwIufM&7l< zz_WD*i<0J71=Do`2^>L~W{h%ZB?7ga0vUsRH|WW-f^8Br$FzOV5fMQ)8vabOK(iaU z6Uxo==7b2EJKzXs5VcH^EbH(inceQ>^u=hb*bQRNNrFgqdnzD%db%KK@GNS8!_xZm zNl1(@D4nj&TriLn#H*u$P1hrlE=W^thC@zvv0-_T?=|M2#DsXYAcQSV!$`f?`@4aO z`xV>^!ZB^G%)yI_k1I)egb~6L0>hT0ANvI+Mfs4hzm;80|M#5V4|2UerdfE54WNBb=@QW}kzxC1<3dD?d_zSws{ykUuK zsg6%w^h0v@7PD2-dq#i%O{vai4oDG5vkwCA7yzC|Q<2L<1qz;_oW?wbRy!o_mE4z1 zyZPBd+jDOI{F)3C;Nx~W9Wg*Kv`UEM`QY;K>gWfiu<>n1vuo(X@YEaJfG7>ENKosM zIKtq!qmOJIzm7y^c6AdfnO9PlB<4i?qC;T6yrQHN06aq!eE?CkEHac1p>%1%3EWd+ z;cW;Ghn~=MNm3I2l8-FhboTO?rUrqOy2LSvxk^Qr9vIJ3aUK*bVuYDHCgbBEWlNw{h*$TNTl6pwQHk`Ru8j!U@ zpeo@OHxh@vfC$dxTS^w;oZivBkZvd|V?KS1q#7pw0l(szGZ?hX)EwEtjD#$+DzlOZbg`m;sk$!)s_T)b-;W671H9p*l0+nPvjT1ph9~5$ze-32Ci(sL}df=dG z1%6)dV`X@pmIcxdx2<9sI%L^5oQhIaf@_V)q693;DfE68WtDPtVnK0U&b0(QQ_d0& zAVB7K5+H>=**K37=k!0yXcIbVs7w-xT97W4pABb`P|bD&qB3@ZAk_-Rq$y}l+!cK= zRXWVuL9*d)v7Kub51Di)JjLgUJwrm3gs;>LxQdO@_I!=G!9X$H(qP)Hm=*%>PwWNO zf$4yCy9gEmGQ;#`&ZYT#@^N^ig%q4oBQ8L#+RBweng?M>vy}z2m%i%=byuAZ4$Kbv zBLxP_Df_1is248uu|-nid;^=a>-474xG^3Z8$w!Ss{GWvVZmjM@!Vbuyy{@xuqO|Y zQR*?QfS7~M|dRHhf)%$Z^;mFgx>VVUcvYBlM8yKwWt_diF@L9J z-=XGY^?FEF2V})XYre9Pt|V6+z51$);1z(LSg6@5Oq2m^$pc~nY276qkOM`z%>jv4pc@z(m6X^AHEA^f0L5;-Ho;C%j)y{sDer=2Fukndz0QtzbkHc z;V3E_(I0C|sejmoP>ssv$4I07u8WXz9Lx|rzuwsEuI&D88_CC1fJ&TYb$><}l`b*M z(kGYQ!i(A{F>o6K!XN=F1)hZt-!ax*>z(oiG6zd`Z zeP=)GN0Y^yik=RH8#D^iv}bwP+#z3SXGF=gr@wFQ{H?pUv%X&MFL~A6Yy6;bqMIwP zzJ9&(HF{QXtY)zAw_o8gPf@JW7Bjc@9Wsz?Zam*v!CmX^mA#jY$S>hk`^XmPuIQ2y zu;{Pr?yA+ysZMTYl+}X78@&dtpKyk0h&NLUA|p}%ZTCN~z;1X5XW}UiLwfm+)hPz3 zirl@Rw;85T=>$o*FTs2uwn9uYi));VjAlW|U23OPUQrrJLa4XDB!_zY%6*E5Yoqr=O&xqtFJ}(q$3M+msq73i0-+*} zOG^G!&h*#x3zI;yXDT_xupu3#5k1nRm&w9FU+|;l+_WaYhFBu1lagh6M?|(bFt49c zh)bU6r(^{|z2&~KmXSTsMe?C9Hzyvxu~qY?aAT~$wP+Z&X*hktkrpLvwfWyb>om}6uZt=LOVvpp zdkMkXRIaaynyIJa+K?ii9o*+198nPP^Velv|2vtyz!3i_IadzG!dau5C=OUmJUX9< zg|5Q*W3r26J&BdxvriuKDBV@s{pFX?~Y-!!ae{v%nC(e_o z8Q8)4@Y3o#d^#c0Q7pK=4OBMBCaBSbwB9`^a6j}>@?*p5qyIP5c*mMJCP1p2k=arU zJLsN#*<80Pwl>lev_tBNZo`4(wXVEDZ?< z#EwfhjjX~;Lm|>(ISOR2Hki2`Sp=P>6n>GqeT8!776ZPa^qEGCrJ0h34 zRjYpZn{|s!Cd3R`bppv{0i|0_`H$~Ex>v!lLFHo_Gi!Y*29PIiCsFjlImw{UV-)b# z*WA5iuM%$9`VKLpRHd;(SV)c`(Ox2l{_qM4%Et;fR}k`Y$O2q+x1pG_GD&mBQC_B+ zbW5yJad%$t2?Zlwaspyw;Og6*T|qhEx+>ItHWfX@FG!&7SZ9 zv5X(%TKvt5ssD0u_4a~kvw(YzxS9#WEQl!eF)81vkQqeT&EDbviL#1zU}e8pluZIl zO3uY@YtMeQFnf&{mT*WHv~DHM;>tj@r6ie9&GZltlwTmzqVL~BrWKy{Fg^ByFlzxi zLh3WZ#JOnU$I^-Ld(<8=_q{ewOP`lq{+a`oa&o;BoL}YwNQO2jhxyl`?!x$O$2BQ8d!8}S^$#E6r$rY27q)=YWgsZk@dMm{=!)uXJMKXE~th63vN*rvs za3SAPCavjVO-BaoDzBJgQ7V2+IZ^j0w!XtT-~pkKW7)aQn3pto;JzD7aMY#;7i4st z+$Z3wkYFxQZbb6%ihmq#T+QWuqo5ii`)Jf^z{Qi@(! z3DFphXwv$o_hE2;b?z5#koEp-L|3Ol5x$8JM}2e|ix3i2y|;8fh|uHLVy5hICN%|d z$!?P3t_^H&Bjo(GEr;z8`>bF_!r~>CL=O}*W7nq)zM@jvbeW8tM`f~npj?{AF$I0_ zO2x2DCQHV>h6_1n?YGpd&{c-Vc*lf+HpzHR)YT}EH7^DPJ8f*rXeG;=Wnpc(@ci}Q zaEyL$m@Ul#i3b`pU@8TAzUHY;aZIkYx!8d2Hl?BUW7Dj`Pxvh<eV^*GNQYEi z*;ei0ko3*OY|r2v=u3%L@=HWqvfeLFFHod)&=vdrv7|WF179tJC>$~zLcYM2BKy#u z$d_J_gm*GS&t8pC9%epBT9&ZkKGoJeiXzsZ2pKUqiq{nygBP#fj6MC z>qm8(!JNR086Cv(_AA6RAZP$=PZRrdI$Y~*ky8=#)o6P>`tWg^=lysH?8HCFR_wHc(jt$&2r2+~Xz`*;C@d@IJ(`HG#Nx*`8b4KbYKmb9afC)4WC8u=)_pSF8bB zJQZjgCRWV~|KyRto5a&U+uqudnjh)*qJ^jz6BJEF*@E`8A$C_^u79&GpGt)vWuD7n z%rB7d=@9G%3CZI7Nh^Nv`0=Cgg~Zgwi=Zl~DbyHc!`~?Nah~!Agu<%znS>XV$G1B8 zuxQg1?`RY+yLbtp)q{7X^S|0>Of#H)BALBHVhZ` z4oFQ*jYr{F7Aiuq_%_j#^oKX$Yao_O!7hr2Z#ZVw+Ewh>BEVD`!(h0&v9|M^l`M2S zbVBIS&WJb|FtvwDd#vw>9TShY8pt^+*1%9jUcUmDpC3ZXMm-Gq&Yc<#dH+)+e`&YOCiBAn-C{9<(Hu!bJ8rWaw7Nr|)YW+C z&7WYdoH8*&JNyz)INe*HUN{?Pha4cESW!n4ryOs$9@tO9$^#3wrQG#VA1INWe3U@4 zsDX>eNEJT1ES{~N4Iuay52>0Ik6i62AA_QbN4u9eH_nTPI?OH#z85cKPE=Kj=g*-i z92E;t5M&=-4F^z390EED?Kas*@3nv&t6^u)O)A-ul5K!!0zJknq<_;^bE7;kP*yKS zP{AbUi*ODQ8uwy)cm+NPMegoZMU+M&ha}4oy79tzAU-{EFCLRQ@f{_H@EC#}?I{RY z+r~UBqh@%EMH8{4l>^vSkkF7R0DV~?K!4Ao16FC)g{qQ$sjQf`Cp<8{8-S$&6h@K~ z6k=#Ut8Wh^0q`w1Tb4SMkhgjSQ)Fx*^xgg#Mno+o>Nj;1R30FYCFKuf+ce?=FwzT7 zejZrhK+z&exN`Bh>Rjkm2Riz3!on~>T(F-?Hk?3uI z$b!H9*Hb8q9t$}tQ?WIXCVmXmXA@hnW7^9KEi2-t@O+rH z6iUdH$^8H=Dtw5o$;Qdc6g`ryl6S>Atdj(vE*|SZoU^{2GQ;apA5$|9Wa7RE`xJub zx=dR_8zKp$Vfhl;e{~TLLRH$83OKc}S^9~3234qP*;j}FlE7X46MPuRvi(@UM;9hm zhWk-Tr%KcKRuyqXJ)viSJn10vb-Qd8zO{3bALz(D$FnK>B=|xm&hv6tigah+y$Hu> z@s-~XLy{d~3+%|?7?k;7c%3#s?2bV(JJ~UVxjD=o>EbU@v_OiSIH@Stj=a}l2hiw# zWIbUG)U@tMydtJ2dtA`MQA99Kwk?H z@U@ME#Lxm8%Y);?>qD*uel?<=zmR$1fn|pA$IA7w;M(~(yC(xtxu@EwaaY4a|Bf&z z$Y*da^>H<15_dS+M!E2>uK-6}_qRAy`y@39`4seP8QLCs9S-m3J}6!~n7R`=izcmH zJdV7UIA?Gp?G&)Y`kb5b)#AnD#R(WRjr%^uw6ke2qF9n5n2-3FFB%(xT%?ismNHV| zV@~>NT2t{Ck_RB6ltd*I%D2;sGy)|8(5qD1lQ7?G5m;3qi=-7po}og{Xyve&b_nY} zHnYnph0arOa!Jo7cI{$lX4H+EJ~+g8E5M$H@|LGKzgl6MML^1pfxK$DzO@NL(k?(r z(WV`~r$L)u3<3AapnrDE!^Qh(eZ`|!-Us{=7Ru$~jW(eFMNDMUNy!@Q8%ZT3u3_2l z2JSGg$s|Dpp-H-{{?=JV_(&33C)^_RqY#sJSbQLDdBAPOIcVgr3T-n6;TB~zFZ`K{ z{>`?`hqm3Z`IJhAQN61vLHa7r9e*z;kFzl60sfLhK!lh~-jBvfQGpuK^TzDo*PM?# z*?Yr<3T8sdQ{K>*QF-CwSCaP*Bt@NDxa5AcDgip(z|Y8-HIyJ>SH2XoEblMb+4!hI z#&T^42Zt*SMn*v7t4Uu^1oX^sGvUpUD)mb|B>y43rq7Z;Fxk%4)OE0>V0{=yC}l=p zM@3)hamC)algaFoKR6RJwgiaMjMf&{#_Tn?wvd9HD-oEW0Z|H43Z_n-V z!Ani8rSVK+`%$L`ph{JS;Z(8VbdD&1^acV)2^HUxJf*P%Ge^}^XJELJMI{{o7kuV+ zu+ETYW_JXXaU${$8-$_YJ4qsalH{a7JHyg=x069VR0TjDpc1Vguge!{sktMM6#@#5 zD{?YxU0-cviy`o`WJ?H{7$Oz9W2H)8s}jDU&m!RjaF&RGC>2qStIhyXe_RPzO;8f& zD!8KO4i5MubOGr^yH~w@K@#@p0!n-Vb=0RL;He=ov!xLEp2K(Qg^hRmCw)+4>^urR% z^r!$<4a_!KJ5*0#@w)%O$f8=!(&VqQ|X;eYn3P+xq4n;;I8Pr*KJ+C zU)m}t+m$<~j8`7$1@A~EV9%80@FIAeTqf?Jua%p6*Q5xnOqXaU^{hFOe+;Fy+J z=>#YzJenK_eV!t)73UvURJ@|BQrFpVBCz7&;DWkKsI)cEU>=JQsp#%A5K(uSv!ABPh`a=fX-!DkwE?EpCwOOfOw}zFuUIlQ%)|Lhg9z# zlFHKtAP8fn0-u|Zk#>gXT*HJf9_w6Xin^njbwY*RSY{^5h${BhRFImMW8$)53&ge2 z`w89M(ms1yLJbpcv;$C!6DsjFgD0T^Y|hzhr*wp^g|-sJE_+6TD1{H(Hh!$SH$I)E zzSl(BMiFjqeLO}MsWV92ywf-sYAFE)$5e4wwvZIxu#rIORXa;I;E+XWZQh|qwtcy> z00mo;#!;%5^M=PgLID_A9M_IMlIQJUeqUsiF0BViXFxM9^^>L^xwX+i<=Qbj>3SuM zhIxd9V$G{dX9lZ+RDVh4BXTS4g9&}fYUFfmXn+OWNXKC+bpXi_PGv*gr<1I@l-1zN zlwPf+8+c*|sfCAh8+*fFO`q%hNq+{!bcgFy&UsDZZY8w-Yd1|N@({yAxe-gb?hBwe zME2h+#I$no_GCfs?Fb_f*rQ3RENVZGn)*@kDAzO}z-j3?@KHwW=KeppIYO#|<~h^pjw-BVGzi$_;S!!x6Zuevdxl(}i9~lgR*%e}9Up zhG1xAF1lyAREH3qyxG1HnLE?ppr0t=yNs1$=S?sPn+zY=B$vqC!B516ye7x=uf{9v z-EUyK=33ughn2qrG$|O-!4j4t#tlf>m^rjg7?;% z??0BzuJz5(-eeQ}Q`YTPB)Jh0D;a=_Q%%kHQJ4l{|67taCVGP{m_O@Hc*;VxA&Pk; zxg?OhLr?Wx2rbFUVn45NKM-BT2WTuAqXZ<1mh(ph);U- zmQcRE&L>iygd`1cZ*hq_(?zvN`z-Kmp^+)f)Tm3QvLBrt%`Awh`%DChf_XY8mTpKY zHN`@M{mk`D-g%QPkr*Tf<^|*IKMAjzvJv7e|8*O|V*{MK9vc9}2|)MA0P;Sknh-!h za>)TKR0Nspr}j zJ0jxR1AG<{!HSP!v`09oZ-{Udo}mycuE`*gU1k8r<9otIQ-ItEFZJjkX6ObLXc=%s zziVW~*wYtOF(iO6_;BqCqxLE)h`blA&c=#QF+L4CNjJ`g)Lw(7B+6geXXC68PLXsM ze+4T`tF~ryu!5_%z1tYBc39Lp4kP_%j-@in9?A#UgDR-h3P~}e$ar_nU za*5$q;?JYLWUCl-#ZMbo#a*iQTWBq8u4i&W^79 zNvlTB46FF&5K0*Bm40o6`zYQ{8aUD~1cG^s+Qyt8c6BJ-6~xVI()ja3JjN)CY|$OT zal||D1`jzila3QS2>nTPBaW*taP82V8F+L&c`x9-74O5S)eSDc-RLux$%Ed-Zy{&^ zsrutueqZjrUL@bnFYsVfqIl`sLl+&wJFzv7?&E@V^2Pj7?*f+v`x;h|-7-spC9>yw z#jFsDX5{NNgb-m!&#PD;+>ni{ag1|RfMKeCgoO4%cDz^p(Q?6Bk6=)-ytbblc3NTx z@>U;@6`j`lhgWYcG?&TuDa7IML1$A=gCgC`?& zI?F}C7WYF(!fBC1nV%7Udk9Lov-a5t^(lR0>(YOIzePn!IiQ- zi>H%xn;)0XxcCuEczA~aDxr;G?eg;a&W`vJWCpVD-bv?4!U1e%(e4xdsKxiVH!q7w zK25Aor6`|LM1-N!%e);yDFn+Npr<%Z`R9aR*#+@9?M)7ku&-sO2+QfQ2t9bh)j7rX zI5&fw9JgL6YQ;WF8gRz6`!rx$qZ=rHSAPSFYw8rL@kITecV3?uFMZZ5@MU>Itgh>ng z-}IRJB-n<-XI8Ff?~JBnGunlhGBaj{vsz3^_ngs7O=y-p4dv&OXUBb{5<^2l9|A9nio7}I)43C{nO%xH!jR@O~GR~jB7U;w8q@|Gc;$$T9i!m*uEpbJ42 z$vPp@ciI3zS8|Orh-Ok+lgENig5pL9@6gfGo+Wf!SV>6S7kifFczh{- z9$lf#$UCqA?Z-ak(GbS6SBmIdl%sqqp9U5aBvhoPM-}sc>FClh+3MO1B0%ls*aRp93$smNBBL{z$2m*kEB{8&v(3ZM+AW56B{`R}gSJ5rNBbjQ$ zEVp!bQUx~O!MM<}H4YMQo#S?byaq(yg4^K>AY6_)m=KS(+831nc*@wMQ}hL4gota| ztFkj}(q`l+4ZUUj#`nAI1={|xGNo^OXIFjAeu&I3%BQg99vK%d(r|-PPnmsM@$!)V zPFk)jHP98<7gUjh$!BergTha%!>8>qn%Je(=;>E{^qCgL5pu70{c>+_dr5RFp8==9 zEH}lh(*Y6)`Z#g$i0NQ)1WbZ~*i(u&2R!+#JHJuY?=|IQpnxdTCL(;KX~JZ5PA{RcKHP6Mp<{Z|2vlI2bff0ieOPM6p*4@{gQ&b?U2g@I5!j1>NMxyj zYVqNczM8I$1q0^g2c>h^@V1%s7L)dbl}+76LdNm>;Unq%DIEWrOp7{HC%l6D|d z*go^#a$*Z>JJ>0qyl^OCuMzqvHb%iBj_yVo8qS3tcOXT}AL2bt+S8dTQ03^>hM5!z7 zA^j#Kigse~;RHTO&bMTUVt41#O>2Kie+OOqm8Ucd!ZHCL#>A{@i= zRFj&CMDon;FH^P*AtQFcLuOJ=(?uK+&H3$ME1*~5n7aS@1W=*Dul+~u&&J?7WVz|U zvbgjC4~;pLZsi=a=6sR*u~4`7d*X}~lM*-KxVXa$*qy}Q#AvAQhgzz0yKj95CI)UoNL}FzETi^L$ zA~J8{b*3)jo^&gB{wFPW?w|+<(`0hif-pmaB+{`X+H4F1BU1S5NsAK%x(-9jMItD9 zs=f;>72z|$m_H87i^idKFv-xB!j{ zEcLw?W@GHG2{VbzN~5AMy;Rc*!d)5=AAn#LJ@AOc_K=W4`n0jVu*93r+IdrcYf;DF zF^ZIEe+`!gHz}E!W?9Uc40&&e#em0i^u7z7uPe7fwX%EuJ@{tvRds%a@JrM+>JRB4 zl_E%uE;- z`GKhQenN2q@SQD0d&y-vNs)M5!P#f))Pb@-UAAl%HsG)X2Kf1w$ssCr-SJw91cT(W z2s2r1g+htAE^H+gcjzer{-ahb7ksRwTe zlHj4*>Ue}`j{f8n*T>P_;`#9(yt58FlSD4(*Z|4qnoJ}ManglhAeX6w)dvTKz6>%; zUFpxhp@}6t@N-|VKf4Ny%gDiq2bO2{6*~(oeOx>woDnyXRv_c7!!YaN@wK#xoy`YM z8Y@>&8*C$INX|EUkL_Hs6nO!RDv?5RI=sTwd(k>tcM_Z*OtvK`2N!*w%NH*GgoAs5 zpS#E#HlyRh?&3cN7t&+46g0{0N)+&yPA|{T4i43jlq?*XIzV(aRw7JGaiFx!$oKjR zJAHm3HCk7O$Aa9f#3lPq6E{rIy@?tL`gpC@3JNMLeX6{mv?XBfDUKi$I^v(mC(@@# zbM)vhe<`1mEj+saSRVV#%|JpKLLa4`Iax^a49&6^7_7Bc8PQkLd}x3SY6dbq)2@^x z3{Mi&Ef$DnKW<$1=YRQ2$s;qyY4dantT9-95KT?VUQ!fibY7xt72>!^wNF@`wtwtA zr6z-utfvTNtl2ULPN_R#*u2=9(^@cy2(lPT95u&Y>i(F)PK_i84#3j}pp9@OyVe8q zhUfZD{akH0@v{L`#L?6SHL{td069)lY{!1j(1>Pk!yID294~?KdL%;MwIe3q9#MRQ z9`)e<{ZFI%gs8E(V*3m+t$R!oi~|WvVQR3#MeWu<&IX67de+!dO$(P*HV~L#o$;Xc zA+#(Wi4Z6qF>)tZTk=a+s4qtlhM@43Ujm6#t<%0Z zS=F@26AQHgg!=h?kgBZ6ccmhX!qs?vO2T*dIQU}tw63$sHGRCFEV3I&0xS}?oEBFb zL4mmJsQu=zETi$K$M;%``b$@9tvQOAEnj6So5{6eI)f!3=ArzOL6Z}q#0A|(Pr7SF zblpdnNGQAs5R;rBE>)%9VAC>(N?{vtiHoJx4A*ND#R{b;rNl!1pFrjm<;Q`XKH_y% zLXYCKBP57~@|UK&B5sNwP3Sm3G>B0xB_(Scw|E-=Z{+^_Q9Rfl6%xrUh&@x3_G zPz?nrh?1tF${~X0KA!64ny{7W9l#x$d;S@%IfxWhBP-%JW?6H53*cfF z>tBK;(!gU!6m+J<(6Ty&^!p-YuO=K|DurNT#+GoPBz?(&p?s1iouoGex_Jf!41LTR)c9uWGq5m^Q{Vz2|-56bzRP*qjMx8_mqJc(W z3GsiB#H?Oua5E(y+xC3w-6dKB#V5?Q2=0$x&!H9c-ik{~f3r^{eo9Q?xWItO6MX6+ zRg8`E3Z1pyNvBeCb9ZlNedU|(tF66_7vFVXtZZz)-dSH3ilQAtH6I9DT6#BqiPbFB(tV7vnqHGU0~=p#F%G-+I_h!Hr`n~feez`1n$)lC{~XYEWkc$Fm(BD829EGhwinPCN&g?{RE20I{*5M$%-m2!_?6i4erO7Qc5zBBjxCckj zvZI}-b(nw2Uht?i(F*fY`#7)zT0JuP@vvr|3b2c0-&CaFjtz#mqsP2Fwxe|^LcbL! zNZX=AJI5m?en1u>O9m*wGit<$bc-*dioCOd6Zkw%sXJs0gQ=k|&M?cqmxJzAUk@%{ zUL96jq+6&T&>YcB?XkKrp{~cU2E#H2^+dS~%;I_%2+XS_0^1*OIqxDYN?0Cl$B|Tm2Fs>UFRyDVZKWyVA1pbnWW0dA z#fZtm4oTWG5o*rU+^6t`3OH3pfGy|{)e-35WTts~0hP8@4A6rTY!flA4_`#(o*<)nlwwsME0@MM(eLx8Zbr9I)MZi`J0gBxO-_V-JW%{+ocRJ|%c zgjX5Z#*?`9LGi4^x9~qeUT7i>s1Xv72LXGmL+cC8J7Rf2Bt4#7%4_c? z5EHhwoL_hp3B z5!k)?OG+e?UAp$Pk6oMLj7+$G;)mVN4t-i4@qN3IN#nFkxQ0mt2)u+yFjXG7Mi{^> z)Kh<3oD*&Z5jt&uQ;y}lh4|7a5zOqhk!vNp=z7d=A{4~UOh$-!60p%6jghCjyhfZ+ z6|BQ`uo*(S^v@i48+RLroJ!D$> za!Fb&NQFF!U*S2Gi&kKTZ5g(bVtV`l=1VebKO}13!-wz^DVtY@6FQ}>@wUla*7$bk zMwlBm+lS>8w3pPxYmYJ&PTtFLD_LNpEkq0^n7%q|XKsIMXMJzyJ9F4ID5sD>xAa0n z)Agj9eO)ekUY@~KLmvVCF{+bdQRZ5*g%nqJ)j$m`Ht`g6i=bb1Z+DTAMT(OYNTA0y zFN9#w8)6D3ManP6l!yU@DUtf=o+<;N9Igqg6fc%>5t#wyAqc*H|B`AUvZirveABxa zd8Q*2J6oce}4Px=3~f_1UVc6Rq`gToX%t zSA4E`ca2lapyrqEBsFQ1hb-+p1Y*;fq=_PIuxndlIQx_=FNu9(tUWP&%0-aoM$udL zdcv3%UU+ix8WTW|1py81h2 zU70e|#(m58H|xE5qY217_?6%tw6rLxa@dn z5K<>HZbuF`_o?DDq5Yd=0f=}@*}1&Y3g*Gtxr~uUqy!o@M{ah_=@PY-&bsCam1}Ab z7MZ*%Tgj^3Tp10QE2)s9tKfi!;kZ{aj&~`Q&(wyi-J`_vaht_lR0%FNSObC}rhQX7 z;nu~Yq`?dj+bD1{ZX@1+@CA*7P*6tfw}Tj@!I4FU@Ih)ckZletQ}*Ro7C9mPp5Rcm z56ioW87WKzd#h{8PBDygHpPI%O_*ZZkkvd?H8b1K6)XxP{zIJ9%hhv)uE3rITiDFm zm!MESUALd)xU;>p@$JgqdS`FzZ|kqb`Pnc?0;EE(mqFsic?J?2fLza_Uj5t^=H=xr zx=&K6y@q;y!VLrO?9_nbKHTaK}00(#V!cakY#^QMh<$6Jxu1SWkm zQ+l9=+J`x@l&`TouK_Z5FoqGGN7djQm%CLkihRmPX2C{&sNoT?y7haov_orFs2Ax% zruQS;W37b$xVzIvmS=8d4=&CkY+6>Y`bT-y%&xgMuxXIDg*>^7!f%zolr==2h6_en z5*1N&>I0$LsohE!1SIArf}OwA&hLrjn53@IlL7ht$12Wn^t=fm5!lxa`W4-xsaGWx zwK5{I3an=OIIO-S+&TY5?(b3Bq}cHCwlXZ5rOPaDEFw!qn<*=PdbR)^YTSR@{m&~n zq1rwc6ehf1W>S@>NWZ+izjW~Q4YDskotGPp2M=m13XI(OgM-o~=4+<2w1Au$Pv3lo z!496@dL%zo{qji|Oxcuf2aE)o&ExLf-$#IK>8%OMkUmz`{`6`Akyea&FJ{3fj)~U^ zgEjewIWa;WiAY7^Ar|OFuEPT3T|3GSU!MVHQyV1AnR`l!H#~q=DE?WnT@zjgbQMxb7KpuNfmX z-YeSi(ct0~!Fkxn!TH4*B1qcc8J;}gPHTO2?d5uBXLqHuvcCJ^;TN5+SHJ1(zFc|u z`JWh9t*`Ol*&kSYH1hV3zIfcwzW%|?(fM+1cei2qPP*01lqow$xv+qXJxwpIh%qKL+6DVUwqg_<@J~ z#E{0IU|+`Z?W^noaSabR@A%sO;*ZspZ`LXUx> zZT;W2w|4gAA-#Z6t}K`3W>;rBAGdQC%k=yHu>@l^7esW7PVApNmcY zjBjaXvD>%lx0ak%`sh*@PhKwEV?9|>ZA-CKE`IBA=F>OBkFTOht1dnT0}s-sb|?%S zxsNr!sU|Xw=Lyn#Gj71{Z5L@y;gSgoX~+{G5Eg~P7EsIp#*iM5lPFiinbr8|MwNOt3`Y5y(roWJD!JVTX z_q-#uQ)Do_UA*X@!^WT{4UXzn5t4x8d2r)TQzq4=VtC zj!tp^7~(;Ah60g)`qQ8AhL*;uJ1BS(+PJ{S&4;!`5V-I!kZtHjx0v1Nyu1tgSQk=;_-nIHh^NMvBad6C@o z8Bo#+b^A9h%`mSN6<_P$U<~i?(!YY7+t^8_bGrE0y&SO=2mVB9jAA6@3iV3e@&--) zf%eG0EXz^7EpSBsH3_Dx;6-+kBV34qz8TL6k1cH40=u&lok<7Jd&l1}1=sN^(ow8` zph1|yP0_;s=|o+!L~Yy{hT#yo9)?uZEBaU#DbRMi->mPw+*<2)?P7@C`TsyS&aQw6 z7Fu49Z_L7#P2Yv+kU}Lr<9Pz6f6qFZYbU=*{rBJt0-xux%`KqMhxn?lD^cpzih_wh zGYpaGwZ?~#@2v~H%KG6_=t+@NQL^$))=n@2_&@URW$?3zzNoepeJUX&2@PxZ;nd7lP(;yd; zxhh8F=ZDO2-#Sq@T|(m)5x8O*{F|se1_uMExQ{H-HmW#{FU5^^HT+?SQ~{OR$CkxB z;&ToppFoRvmi##hQv*Rrr_e$;WdoP`}Nz-lQvmyGrEgR-bP#`> z{v)^y;WPAmVhC2ibjzvQ-?AKT!L6!8$dRWN7=J#rg;z2dE+kFK)&hGZg>Uy_JWi@RF z2RNdccuyF{4HS96H4EHSn?01+3`9e@2V@t7ju=Z^!at+TI6=)?ay+ThNn;OhMfBOc zR!!yB0Wl_gOuaK(P?H>~{6VJYbq7i$qmZD|l$`6q^JT}=RvF_|egKZBNe7MF$a!EU znZSdh6rf*$kJpkiQz)RofYeAJbaw@a90AF}q)}2IWu;x-km1SDIyxJ)R2MWMunYc! zjEeg$rqb`?9@xKqN7D?|!14>qCS6$mB~`3)H~9en0H>|>QuE%$AlRP4IeBI!Oi5OH=)>8822aFXkdjM^MH{_gdFoBT4!#b!0o zg;zD$#pxugt4(}33R5CRB+#nM*b3Jk@EyiN)w&NK6Fg&BL&#b#FJUc&2EpqFj1Sia@tH&mEWx{q zFbr7N^ffy{3RgR)trrGx<$dTAYbjz)-hwA9T10%ae&?3uBCPeRUtwv%sT`A4gBm>- zlYEU1V0F7!{kf^rFYeFPdLeQ`H<;4R7fszji(o(Ca0V;|DTGS59c%HDc9eGXdm5pbN=1~A z>)Fc1ml-XJxaAkPr}=t3SRcLv4&xM&qQa4P$tCFZ-M#J$I89#fY;bl2V%Zeit;PjL zj~3mMziSF=06pZ%%09CE)zXEY16Hje|4Pt0I0_sv_=Z<(ASs>U%Jm>Ks@V%n6I}vLwdp>?6h6ygj4Xv-aS>8pc)c#=a$!k{B?tqQUugV4BL+am!HFV0Cp31-$_*?#Dqe1b~ou!CMa0o9s1(@bimJ0<;~aw7=I3Y z?u|lPG~pT-@u?|3O?{1_RW~{re0T}YtRY=52K_TQs;0J*CCKoYlosKB`SROuJZHG2 z6h2;>Kclv+-)mo1OK|^_AUq zZzabu;pL`->1(4Hns)uKLF_Ey>aM5bS3y2yQL)Sx```HM@5Q z;O|~Bx)`ol2pZ|%aIzq*KJL%GJASgR;sj;Sf35JFh;cNGf=8Pf17J&YzTE2MlFLw?qe z;XZ)Hg6oz13x9_hk5!l9$4{#VfJz6U@2pASP)TA$e~sF$WlSk{ z`F1weRd*Y}yCHysY2qr(H!J_v{nx$kx{?W8Xd6kgK1pELlrhsFQ*O6=O|MT)Ahu<3y`fO`8MDewy_@J{# zs=Ts4{3~nbrG-URX4u%0%b+KS8(jwbB8P`Yku->2r4*dhD<;%1Y_DW=CPHRXr_%h6EyTH=I-gk&d2t| zWpf2GYCd5Ec~xw$f1;(cF@#*Kx7CqjkR?eSYrU>7S@#1YA#C|UYsc(M3~YryMWs>2 zTUTJcel^tB2Y--ud@^p&j{?&R}rGzoYO<;!J%A5o=b zI9jOWYSI|i79ZXO%TMBl1|3bOe>+8Iu7LUxzhqU2Cc0RPG$L#DV& zjsOqkbdV@s0+5ccV68X#+))yrM=muL8RFNr_&Pm^S}2H-pAU#N9vlUKX?yM!j=Fb4 z;T7;iDwjefgcM$83z|uC;wPB-h1vXhoXEfC?Tujj?^szKZ2ynFw}F!ExX#0R0Z;^& z1c_sFWzwXqhtVu&7TBKOU0|_4z|79fVh5T(%*-x+b{4~)={GYC^mGsVXMgaMP=V+O zSs|tqTXu33T0C$fB}a0cSdQplfr28^Hf7VY&B&2_gd~i-I%JONV;zyUXidKF*3YX~ z@4fCfyNd;Avv+&CyI$3;TeoiAx^?T;EmsOf%$S0!WiyQ@Nv*;tDk|vbt|}CJAi&AD zk!}%T1096yb_UT3Md)nCCTPeOC&M|!=!kWf2BQ(^*a+~c!`5htaYoQR=}mxK_{*Z7 zLPSo#((9UmP2E~lP(iCZ$FVid(Nr_1n~d@gq?MglAD(t}UZb4qc0tYw<)mgY-6_zK z)HX2D$T&>SO5BG+R>yR!nk;gaCMYQ5D#VG>Z}k}9l&b2vARx5$R+Gz$CR{CNq>9?` zNR)FkA~*^-^&JjA(Yx+O zJ?&*GSA*#C7RV|{RCXOSj$&VHO}3vr2#bjPcM@4ukU%0*R?ur7#MU>eFc@!>2Ti{v&O zonTrBw=H=hZg=`SEQgwo(q=_T(-xu~jn9aW%!rLhqk1!|JG*v)mZ;c>(fcND|43sQ zs0l4~I}S{5x6ln^cDVjQL(sdyt5=Q=J-jL+G{z0FduT;9)6$d0(Yq?x3%7*13RM2Z zaq+;(R0;Mdhu7VmVS!C_H9DpYd0ElL^kfVtyI&5`VKLN2jW9_96oYVcr&_@T^WsYL z{=<07!6$`W7ogcV@_-Z;Y075+p@(+dYj`;v*qtP7+!cUNVAGhKThe9$EzO)!QiW2# zZSP=K1&+wg*pYgyi4XDMwOB@wgO+~&=Pdzq07c87RJu^KK{OHuv zl1N{hP|Tew)70E#S{BsAUIr{s5`B-AnY0M$hl743h#AaDqy4YZIutxWc?yqSbfP3+ zLyOrZ#b!Sg6wMZy3zvvN`rRM&7sY!Mo0m@95xb|YP~#9Zyt!2x=9Y0oF^cI|R0ndR zB5@_Qx@6kDH0Ux7tFr(TMl>>3N51yqXN!L_#^p-2yk2U+mC}$F2aLog+myrTyI;;9 zMT0Xc#X)QBVYW%m4_V_VAdqcW(y3P zsTkV^Wg`wpgGYE2jvnSwDVfg<&QcZbHEJ~Bs;S`utdkv~HPExl)d7T1%b|FI3x=WM z$>geGIGdTiRCSrgEQXum6;vhiRtMB@@V6v3`A~=1S?|YD(fjF8b)i&zVW{|U@!|t) zeGD>45Kll=Jug1{Vm$HXUit4MCxvzsyS>pSAh3dZ80`r%8tck*t)dPCVZR3tm#%Ja)N5B)Y|r7F#OPd6Hgo-9 zcf{C_#+mr3a=6ysJS!)p=2WV9t4~c%oj&&% zcLhy zWv;F4?@Lr@?7E^$V-Clb_m!3eLqD%9zOXzfRuqP?e|%cWYC|?3d+Eh5t<@1XUc!LG zh$L{`BDJWn3 zG=)S9uT(Hu$c0qONUK+fs*}~=lNOJ)Yt>5adDFA{iSz_vkS#4FgI6RE?2z@?h8MgW z4-t+c@z<9C>f*0YXMF3=#OJ4{=de+(_SE#$8?DcDf^GrMw*a;*165;;1d6c3U@?-% zBIvU$^$M357&zinjZ5kmuo=dIWF`6W;}}2VIE;h-ae@jVi=MbmA)E{g;DO@WVay{l ziuUV;cXO+<0qUB^((~>(lo+7PA|#z$(L`;ma@atds$HsJH*Op121cw6ED0`%Lr)Mk z5-M6Pv($2Hm&Jp?;(5QO=~iPwR2&0ZFir&K$o|y05gAzG6b3k(iMTsMarJOP~@Zws)7aaz2tBM(% zUdX5j+R2IDVMs~ll@*3Y2g@lb&i*zSa=V|P2FR~d^D{C;+^2oQnGD^$Dkq~MGk_4m z_WO@;=dpzn17VK(n~rFn!fmA%O5!Qt^dUn!tnF~S-nhCdSID@yxalq;!hNw(`Q-H6 z46_NTi^Pi|bT8-LcMY#r)-*TQYVq4anw4MN8|A z3NDuic;`q6=7LMgDrOpHoFuUMrVVIfKT!Bc?LbBC->Jg|$`8+JWt!CLG`gd4DFTb* zQq7%?43IMk*^`>g07QK{NglL$m25uedH+}+=~uQ@M>BZUmRba zUvvdeo*JS#Tu*}nTlbjOld|9#rAN`(*BU@w;p(pHfSTMT%CusXNMDd~u4$B*w?*rY zk)n@Umw;3Ho0YLCb5fkUbbnIKFf!I&cO|>W#S+Azg-+*L_j&B0J5LaxG{RBfl+Mh1 z945o{IXM(i&Z%}@B?H88O7V0 zbdpcd=tL2-!405*xeZegCl|*Zqfj5b}p z_7g9ie5C&nJcy}-SPxVk#fH#jgbcnL~My)lT5!a55KU=HCNiJr*~0#NYZgTM|6 z*SB!Bj6E2}k=fpngJD=3FmgK&F{6G&1E71Zx)kJ-SXxPFF5r#DR<-&`I~5Ngh+7y2 zJXOKdb`U@&FQY9Cn&<_C;>N_%jkc_0n~Z6rq0wm)v#-=(0pq+qR|~`Oo>K%oVQeXb z_wR?Hav6>{qH*yIHm2bg?{78$2atoA`qvKybG+<&941Ok^tIq*4Tc7O9W!4B%Hbo- z%h%)eM!6E4u5WHuO0{}$^0|RE{e29RLU~|KvNHD>IB4xf+@R z4JXUdQ0SBxf6zJY4M3MFSlhE448v1%OH%|zT?T2TmIQ!;b_VnB8XSPj0YiD>2^y$Q z0-&OD$hHlp*^ySpHSa(;4Bjv8pWq?&8QNAF5<(MaLTOAa1q(}yMR=w1dHB}&ar^_k4r2vi9M1(_cV~~i&IH=a^%Da(S6k50v6`Uczja_<> zK-A%VEIyzKW80*H@j*DqSfh}wu7PB1B1_uN9E^&Q5{RcoP#e5=6@j(5$O|?rm)Ifg z9mC!tN;HX@#1Lcss+_NrAY6p68y}pj;qJ;tvs9gkMwS6Hh8Dw(I5<;Us{q9X_>5AX zHiDBIk}}p*M%)2#u1FkHAfMC;Ac8_NGQ}>W?&ZsuIY|_8YC@#$PO~wHcX3FFmoxJb zW**chPMkJj&SO;=3?d@rhgm=2m;(-xU6~1Mo3L;!G#f`Sw<*n;BM~nzCycnAvzgvZ z%jn`Ulm&86mY761O~0pgBfd_e(c+!otBWX60I@I0JmzxHLC7x zz-P3~VE!(EeWuw9=p1-^;E!|zFAM^v>w>jmy%;#r1lSYz2RLMiIZurf7)U6=$jJho zf|QB~=Ye3mw4*u?qJc&IUc&5bQIcMbprx^&W)xE)HBv$~FYmD#$;OzXy4n;5+nKLg z*bt00r5l+1Ov1HA6@{4TWkE$VjYF$Nt+7y9qP~pjP;@3cy--ocbb`m2Pay8qs5%^k z(AlUf@O6Js+M!;}=>}Pm#r)FK7A9IKuZ{3hrQU90&S6ai$~J3=m|X;vfv7u?N+O`D zmzKx@l;Thq^Ri1Xy`&3?t9KJcnO(K2fl$G2)4bx0hM?;LU3EKxC$|vOl2doxG)?B> zvfL&B*%v~u<13qs;M*X=2)#<3h!y|?T#Mf{e!5SW@)=21p}M^PjCg2<@J64V5j4Xo zoSENwDPNvL2F=tw8Q>Fn9lm7^sl3Yv8SuREkpSPJD0H)wSG;YBEIzuCTqi6gklpcQ z0VrPspsTw}+zU+oGA-$?Ggp|aY3YJ0d+~xUsOT z>BLDnYj)j`Cjpx6x9}*oKolO5Mf0{dHQ0sC+cPyB zQ8Y9{0iVt=W@_qC=7XC+H=}5B;If_$gO`tebZvVS;#o4@DD2tNy;E10#6KpZpL?Of zVpR%J23<#aK8k|jVM@$6^ptOhnnW&`vc{E-Ep%Vb>CLP`(cd%9Lz+c#GSJpF1zm;r zhAyZC02*Qaz?$@eDIqkdoZE71RLZGIiOFAQ^=-(A`8Ap_IqfQ1L~}C%#egI_B4x%j zwMSjbK}|i@SFva;P)`!W6DM0sRUCUNU$v${m^Up6;$yWdinnfRO%T9xFq`c#P3lNQ zgU3a`e<$19nOezV-C;<(?G-}go{xu;6o@a26kYE}@6&UFGheBOsB>IC+{y*e-qt_S z@`tgs+$Lyr(E*R zZ}~KtF{YN=4czgL?avN7LbUvdaZ4<5&5Xr{DEK^3=oqUte`d-nZv)9I?K7YjE%7R- zP{dP|fJ)906PWGI9VRdhiEA;<&@EV-oJ}DBYf4&dSIiVJBhJz!OwvOegGrE%1b|W5 zBY@xHxLG-0Gj)${YorZhH^0G*!Ig!@XoCfz8)v{VPzA(|&##ys5OJIZJ!=Ug3p5hLJjdOY%bD5-l zIKoCo|0?mXmta{Y_y~%Sh@0SiWf&z2|BMSYD!}&vb1IC(%nW)SY~?mP|uF6Ir^` zsEJE${3uZcPLz{Q3`dyJb2LflJsTwxW&efBFL#$XI|?Y>Re>wJ)Noz~H7OCqIS0ox zg!N?&b-B_EJ9e?{6)n^Ko&{9ptWJTg?C}U~P(FhlK~utC?(1n=W#QH#C}uf@e4|Gh zc|eT?CT8uo;YhEhsyN3$GLj$!8(hXBkq%O30>q%v{^c2j)^yH6WS`brA_()V%{1&v zk>nzl^}%?xZ0$;NpMFwTG?rjIP@IElOHXu`dhCtZ-iYL!E$v)TZAxy3#1$AnyH9?0 zg!H)TX_GxRjfrEhy0u5;iaRTG!$e4!Rj)8EeMmK{MMBeOGUZeW-wr#c7)Sr8%zz>a z^&+Cvs@fM#{Bg7})( z9`WA7tQ}FqX+Iezf5%CO(Tgw9#Zh-@phfCb_aNxG2zSQqHSryT{w%Yp8dBvt z=qd^TeRZ{z(A8*s(zzMZ&lgv=7n1Cv5jNgVMiD~4D#@A78a$!ti(fQGxnj~iuC1Lf zfH3?a5$>y0rQ07(2fz;^iZKDCp_p!A#6BFrjd*GTM<$+*PEYW5UDo7qAQ%B>zbW9Q zaAG?)CUMj%CT3-p;z`lNkP*bs0*<(cB)<#?!q9}tk}(fOyR}f4Q~0fhie{iesP-g$ zk_g7B#e_w$h2Y5AtARSK%Tj#xsuefU(ThJA(fpd(AGq{S#0rk?y8ztpY#7Iy(a^0#01{x66z)N) zp|Zz;B(-d@s4INkYW(lX0psW3??~3VPm`(fl<1cuHS8#Y=uqCFXrT5%0dT*2OfJ(w zS^aWDvDpO}us8jof($obdEoFScrC!MiHMSf{kw5|zv&L5h|Kt)_5@M|6HHDVy zOy=NFWc?4|61W%;b}pDogjZ&1TNHv|HAAjNY@TWsClA1%x>&6|Z}dgBFosK7gW^&( zt8yz;4X--fmmxlkX+A@G`b??DdAyJ?rg|U|cF5If!h;x~cc3GH&gca@OXzL*ME9Fk zL`V>H);KV^`FShG#84(arl#!iVRG;@8Y`VsJd>r;lz!gJcnea4KNq)h(HX1di7GcszD);7>*|RLo3n6=?FVrpfiX( zQ)691Am{@vOE_wSH85)CVMBAE0vaCnjPB?5Gh8uwz-3(m{bLm=tr_HrPMi^_Z<`XZ z%~JVh!NL7jaZ4^~wI948mym#`$X*nI(xdJIF7+U)vG&S;u8Hk`xqYpq&O$Xx8?EBd zkwKOABRy(Iwx^6Eg**|A!$B-Pj@1Z8coW#mfgxH1%#o0QC*b7FxGBtN_AFIj5sBsKToWOZdvK_X zA-o&SB2KXj1i|Q{ZaN4;3yu#LJa$$2QdIh^qwCXl=WDJ zJ=(-+K8d#t`Z?n;BDhDQtOoaBs9{0qBjfSTRIR*Ihw+J4WsDy9-C`rb^g$qT3&{F# zh#T0%T|9$3g9kMq9PB+v0v#M6*Y*wcSjKU9!=f5>aVEMkezdgn&>hE4N7N~rRe~xO zYG`WnrYUb4-j(t(<`eEo9aj08p-ykz9^UHKLM(hM8&?s57Z&*JM(&xlcqAl%FvEI# zyIpNnaM&yoQL=fp7GmQ{AMfOCxSHS0anR;q7K^DB%q_!}yJ?z8I60||E{J%JLs$s) z2(e=^Q_qC^AN|y+g9w7(xO(stWP?)&`S)NJ!k0@8T54_*<8es#W)r9WAc}bUlc(FT zajGLxtzEUpS*cf)a5_{fr9D)i`O~LDVGu>MizYfHEW3kPhO-{Z5REWPDCrbKyT^*E zTR5x@OgXp*%PrQK{v>9{)eVZ)tnsa@j_8)5UnKMAcZy%_s~sJSeoZeEprxdpzFdGq3Bq+-x^1%vD*p<4DPe@@NiqHT6ol| za1W-21uzb7wt^;<7iSz9oSa@UNj+vaEKXxR52n&sD`a~qAi;+67l16-z!~&b{t{YP z<}W~k#dBM>vP?`Gc_hnHqiA!r<(x8x`B{~HPPjdoi#%PI$$WJiu4*9pm`TZ46#@)@ zx(zX#j|cbTH0i(wwv-a#8Rb$DiWEu%#G#l;0u{0)8Z#k7d}$z(0?NA?)%dD$i?0<- z4S9hGSqLK07(Xpt{<#A4dw~tK;7ZC+5Hzf{x6#!Xf*!Y3sl*|9Q~H(3-8)WjaOM-1 z;w#<<>GZbRWrEm7B&Tcn-DaZZR%ztugNc!>5W7)CBR zSu=XZ>ISia*?Hpi7udQrvhIPilGp&agW0E+(?zx#m$56`r9*g!vrUDbfOlJh)wQXJ z^fD*A;A>J@Ym^#S`(#TMQ#yjt+a(E1$Pyt0w*wV-s_o4R!jr?!8L01A1N1p#P(4zo zHnetz9LQY?*4xb%>_)6O=#QchA2Qpo0rd3CP~WlW<_TXFPDl14H6We=hF$@ek$Pfr zYr0?0p0+1q^g$$}uvLU*HZgeo?wFifz((Ns^3>#U2B?7zOpF``5!eSXIP3xq-?W>B za%6#79+kkLgpeB`V{2<5SQZZ@Mg)Sfjnp&x4m(+$95&=q1F&3|eOCtGD=8BTRDX$) zqX3?RpFQAUDN_!b*lxgOTdMOK^=1?ORBGYrwy0;YgUN#J>J3pX&OaV=d1Q|(mp)P9 zk%-_zhK3H@41PvTctn*c$BkQ*G=|f)?aHgQg20!_GO=>0OLocOUEAvn7^53>jf zsf0nA96cEecaz=H-Q_B0eP*A>Mq#@8V?TNKX^I9xJx-3u!xH^b}+fE*>^J&Bo z1c;)QH7p-&L>Mv54 z!p2Erkf>~q@=Y)O&9LF^soHE#?*W!vpR7Sst7;<5G5yr3z3Q3~crjdW%dYQXt_e%n zhM{I1j1CP2{YSUaLFktl=dm(J<*;?5U(F^f?y+qpH}7mt!@4{S5|(N?8#9el267;S zinpb_S<->0gyV4Vqb?q1YGFp7Ff0|+VlF^PbbOVqezio**D@LaaKn38M*Ad|Y}bBr zl}OD=7o#PKAp}2tg?<^0)3tEZX{%n2+A0lH$gqn*@OYn*79ys!QXz-ballUuN5~2j zFM?4blQs@;C!5OMiz_fyx3D!@TS3e4L)we*kKszs%Q<^rZ`;c4&iQE6v$yV#dYpG( zH0g0tZ&t9{SH=l#(16K9FoD}KnVO3=-2cLvP<*VztGw4nXW`*QY(|Rdqhy!J;%6h- zZ4Fm>B1?A)_9&HIK@E8Xh7fEdW@()yF0Gk(xN9Nyl@S`P5$`23aZaLc!aKjcR>W9s z)z>ixb>+L%#kpZOpVh|2Z9%kqQ-NkH89S4Wg=cHtML`Gn; z9mFBa&>A-v8EmD*Eu~|%bo=RuI&;twxyZNQLv)nRzK#4yI+~LoS=@MQs2JNFHO!c% zKWN!mY~_)2dz(8Z9mVF8vJdgefdKjh16EQig+!(x56&(xFD!A+h}2g)r0J+fd@9ob zA_2)Xix}&*uq6s`l`99F*j;a2l^uJ|7;tT%9gs7|R1MRDEG4u_nqfI{{+oidQE5Ie z;80|4ei;)RSQnAbaibiknh;iR&dqS3k$DLCs)NCp(_nzy+F8{%fkrpH0ZBgKojExt z8bKy3(s0^%^9gLwlNxWL{N^23Y-#6BZIKRZ3PPl1}8*3zuF>(b5`R zYY}@MUwIdoz;wK6n=s*G5yKT7osd|Nnv$8cE6|SUW}z++Sm~JwTUaztsXkrlA*WDz zD8UsMM%JaJnSj?oRyL?>(5n|uYhgnnVmhtFt8{D7MK0x?^VduC?d{$RsRmXvzLHZD zxd#mpjwjzhK^NA?cf&suqsehoflRc_5CkZJ6=wwn^@6Lj^ zMmvJ)Lz4iNCAIt*7#L8BBk%O%W|WhR<;X2phB`Jw)FyYk+9E5y;u=HWmD`L%Dc!{g z5X);QkxBemZcm*{r%bFwih;mX8EuowM0c7zz)=WM$x$f2{s}{X3p`~~cOPSosk-wv zw#`&|^KHv)d2-poMkiBKsDqs*Y`PUmtomk9z!Q!R((tI^WSnoc41`bI!07%q3%zbPi#J(>53t0fYLrB@Q(_Yfy=^$qIb< zc$d^lPfk`@tJG56OWu|ipt6!!oY(^18OxIbE|(kh2az*(6L`wy_Ozi6J#J^K1g{nz zAd|QaGL#do=%w2P#&mdPJ>0fpbs^PRH#U@8rnYV-hv$ z_OzE=C)KI83Qs1x>^~W#xd8UWurVmP(Qvf*el$u}W-C1hXXYoyXT}#6CdZe@53Z=2 z%#mGXyo`_uuu1Nz`O*QmhX|^#`(LQ+*o73g@4>;+*hJsI*lT;}Qxu6}tBw=#m%QDM;iE^7jtp^| z>cIf6lhC2^?o=rzRRi#CYJ^k~Lta!ZsWU!dPDnc( zB58Ujry{w`m9B*RLuEK<@`AWSiUOo$j696DZ>0xYiL;f{xtWXeIi<1q&!yY4tt+~m zVBtcHPMTSZur%dFRiIroxdp;*Fq6CA0B)IydW%nMk_a~2*trc6N1ToUddF==5Mf?Z z0aov_FTRFIXTg(do{$o_i|vY>=73j$SUz&IFYHz8@IWoDZt?nVTR*fO{M#siNNa!d5RP~i{mCJp>lkU^B3Q!4*jFs9= zLPgR+Q&PrsPGT^C+S}Xb5fxI#p6YTHyP0j4@T!zX7|L|*!njuRSVrht#rPy~TI|{z zCyP?uifVtjyX3yX?R*WMt(5lG%ntN6FW|+V5=_nbFJg_Wf?TW{Z}bTRrL~oee}03C`}JBYft^VM+tpxw{{% zLl@UZeiP7zQ-=5-$R4)L0}^_m#5fnqBEr2zt&V8RQD7RCbLgQ4?A*R1JjPw+3?9=5 zk-%y*$dt`rcGLM?!e4R$@}&@wt@VkH42|@L&16QsQWa^NEVN7|f+(9|IHmKk+~$ij z-f`)(($gyCx0RXJyge4bcvJKyqr`j2(G2YQq%n9*ZHuhxjy1gpBD%pKM)9682I9Sk z-D#{<01!rO0jxsM$YiONe36^*)3;~wC`0W9*2bXBLCjNkW{%d!_;5QZ_vvtmzl0-x zbWiLHaGZk5Gq&rgibO$3%zX-!Xukb8%Wh2RWNGG7$*4F;`eCqyHAp- z4%3^AJ?!5j?_mF`@VKeH%=lj4@7|m1UbFP--ouJ(GscCt7`6l0InNjGLd??esF$qR0E21`d znF4{EhJ0)UN8{5Omsr=}ri)8SH9awDUyM`2m$AxTV-8_M-{uTzW?)^3o;cwA-l)L6 z3zSdHrQN0nY=TwWDaXI8d@(X?FYM*GUn}Q(eK3R4oDxA-Do#fF%mSQ^=$ntNHggdp zx&yeCXq80QY?m6(-(Zi(W`TC6PjFeHTVQcbmH&SKjElEed{%dBqLK>>gxwc*r@cfk z<({^>2aictc`RmEQ=?+Bx1GQ!*0L5wMvX0v?s^_I43gY8B0) z!_d%9g*L=RIyyzK_H%evP6IsfXPxjN)+h|I1nr#)I^0&fl42w1@Ac3 zVme%6XCE%Ch|zX9Se(GQ7x5lUj3elabV@~2dY(nWm!#ZFEcQ}k{iVeT+}_NpdZWWp zE=knkAfZT#o2ucR3-)wR7ka>+DGsH|3)|ovNd#joSJmPS7DXelc5=|lj)j0w1$&`z zA2D8;iSAQw#V~#G92bdbi0(*Dx3Sm}>d@nR6M@dm8acu;7%`1R*j+HYPqI7Iyt=Kt zOVIn|0_Ed^K=_OTtj;bbS2-Qzh?$@?lUioyV2Fl>+jM(QnYY>A*s#EUYBWf)iRqV$|GH;;QJ$Nds_1SH0BiGJ2LI3UgBq za?$x*TlLMLQHMK0$F&fP=WtMY|IrOzhDqy24y^K!Ck9WoT3p{|T;{md#R%7^AzCFu zJT?^#*lUq3;3?np>o^{PK_Q5XeALy_pt8;WgazBQ4~t@@hSTj1dz6UR$!I1M)Vhx! z*T*R7E5R|3Ja9JO;)!JqDz6F-A=A38tyld;1HrqX@$ghtTfMit<5jJuv)-wGzm08= zy8WGRI(i+Wz142s?p{~5zSF&a8`~cB`a9or^t#rJjJ&&Dl0G_!%}q)*)6ik__6hjg z-Eh_K?`*rB{#GLA9qDdW^*hqpx2e@BeSMo7?e;YGn;>f7ICq=0xQNGb`U0vQ7HaSt ze<#Pdt^RE%f#37*Bn7|&W~Ec4bJyG6ct?vj6~jXd0-m+sUg>V?zda3)=5$lwpn0(Z3iALVH)>x~8b=%SS#OT3Xj>*dw!d0T!+e#3` zKKG1i`EW;637V*6F;Dn#<3xx@xCv(+42)2Z#gH{vq#=$X5~97cO=fR2ckmYQw-VpW zmFFvOADIdLR$^OeO=|CUBjgWv4r1(`=Pyb&Q&X zWRF_kWmCX!`?$DBa)XD#2t;=Rd}@)-9;I}Qo)rNgV)&kUSN z#8*)PLr-W=An`C!`&OLjb>~Db_oze#v=MnILE`v_9z@u@IV?vpSWxa9;H82$!l;>E zidozB%7Z=DVwnPr*AF`Kg1(F^W%s6=CMcjfI^|0aL;sHRjpX!piq7zW72zAddDX=Z zbF!3qrKnAO)J0vDEH{i{Ty3ReFuK@a85cwbO{p-N50x^V=pp9HYEb622eB${xJ{|o zA?&G5sakEP7BvtpHX&1PO>4$wAY-4IbwI3059xJc79%BkBbq6t752H)L$MpzYzAgG zvN2<|AIglkZLQV;K+l<6@+$n+}Kz ztLT>_vT$13$JCpP_LtkaY4rB@BD?gmyGBu2v7&A_62U;aYCKvKM%Vf} zapd3KV-W6vlXuFvX1%~a0U|XsgDl|Iqy$@=(P8mOWpA4+!z(^^4$x#y!x{U(^~O&*5%#&tFwRtP5yu{C1-sUsx4y=v?V}!gJ7`S#2 zw>Ux!#}LntgV|n9fG{S$)FD*H3@5fS+v<+lRYPMKcFYeK2b|PeHLSI&ZV6vC2AO=7 z?i_)8wA|pCH@}Iznt^BzS>Vry?`mNnU7pUF@5H14S={NovzZTi^f7~2Cta{dHz0)= zy@+Bc)?cQ0%5=ggwi-GiiZ^RDIwgYcdOcn)Ihc#7J)nh->G&qPR> zUUZ<^5`v9-vx+tsdmNh1M@Bw7I`XZco(AiOpSv|0rv;umcbKPsGt3C>un00)GS@Pd z8crZtKj6$0k%W`(Z(wS$ifpIFxXqk>&cXdguk`H+aA7g% zzDDe-0_7)J$3PesCCfCb^Rjrxqn*3x_#O?6VJUE1J0-98dgqxuw%C>)#A2KFT71|$ zu>DoHZE#*At7eUCHH{E+F)TBtjA|zHC8%eq@G8qKswg7PZK-a%(rDDx#$rO>HtiAu z+y)7X+ZS$HgUsZ+EV1!@R_n0zFdT48hENlZ^c~Jn-O{?=F}98kB2S>?+PzUK$jkd* z-mj!u&*-s-i_mj$RTz94py@jpbVsK7ZnmBJ_D*`ejabzSz43m*K1PMYUdzmg2uSqsj43dY- z#LV>4LJSul3Nv(qFj?8GEFzY2@yz_vQUod9B9kd#reLPBj$rm-kkMb^M7 zcf#mQ>GJbov$)uX$G~gl5ODgsBDtg zcpY@3)tL@wkY{mueksB|3FoniiB4$Xv8BLF1#SQmp*(X0#G#E2497{@85*<4#=&N- zU3{)|34ul{)d)!sI5S3Hdz4vfS^ z@Z=SJ=~mQ|Vq!4ZOb~Z!VA=Kr(NNg`vYzaQPA^Ty$$^C6nXpl3$T`XU46NfS0vH^;Nahwq6smgqi*5qm~Zj8ILq)tu2Mb3N0xM#dvUki)4P47Vu2-AZl zbw3#x8&{1Zz@kK~T*960z;A-l6wM?tz6miWNB<&|NmgZ#Rj=;Q(aJ?xYLf>5i6eGN zGNwSAT=EIIU3vk*kU5Vbx=lch1Cv-YAG9+w7(1S<9AAf1Ze!6oL4+L`QN z0p+O+nV?3xflBax1}Nt1g4IaQ1~Ok4P)VgKomR}(3Dm~+PN~u4_JYd~I@OTefsn!n z5Rgf+H@>mKVfh$?V}chJ+G|zxqv2O{p^H`q&_o*_!$j8waJTkWX)E;<<#Cm$xGauo z=a$b-F3c>9d$gOK#BsKr>P~4D{`>>B&0Qe*LuqFR=iz3jmkr6f6kix$^3V`=xXPoM zvvrA4zJMJVoEi_t8#utr>2hhk5<=2714`A1M^ZXtZh2<0xZsK2ai#`K^^MkLp3qpV zY;FPY94ez9tw=d8+ET#5d1fn2VHrs`4VD=)7Z_h?_+UDZC523KkjJ5P8{7-yXJ-8_ z6rqZ!(Jd8pHb55BxX$!R9Z%&FmUy_mCeEK8m6Eb>VJU5J3U_HpdOk5jGd?o2048Wo zcFh9VxdRl2zPRp)VP~05fGPc%jbXRWg%@NKT%E$P`ibe*uD#(v!ttYH-jGd!7F-_0 zF(qm?#~hwX_KFL5A(Yb)t~^hJ+|PW6azRZ}(+Ajvgr`ee38h1&8PYj;Bqhda*!JVJ zVoJWESRztBdBt^VJ`k1Ga=p=jZ+(&jPTOaqkwYAca%Kio*Lf}~57^$Iik@Y`b_Z4p z)#yys=1r{*E1Xt|2wW}Zh0bJOnkJnU~WyK$pev5-xSz z15r!`MQ2Ttq`cf~jL)N0C#v)djxz+h(qb>q_e_rB%;`CA@|!7L#(=>x z<#gD1KCI$mtvQM1162^$5Q@rydlVX#5-dDdBIz|vpI>TK?ARUOG>;v*3g&o{7$ ztCgyTljp1HT%68tri$XZi3IXR^DyYgP2A#9hr6tyrcRU~Lh6kgOfyS6;X1_0x;s0i z*R;^6w-8b=VfzM6gXmfAo>$_#$hCC>aU7rE zHpM0|fqSOg2#!QkO?|xrlSngIZnT@c%!5WT(@lm1U5o`V{hoe0Ij>LfdJwAHsBRmF zMFXe9y^cyUrh=JJnmWRaD^S@my}s`Pvvb!gJQJ(zjxgiv>nuEvV0jivg7RBxh8(@=a&R#<;d+Ru_mVEsvwd|!U6Sd^&S=6$(&h=`^*Vm|JZ{6+H zlDqfkCU1@bpb1POdfg=%*vfO>C=CA@lbbBNS?6HJn*Hz&`a-kV|@uio5TgnDm^ zk-U0y^VQP$&GFUJ7VTf-)ynf>ex^dDH^o$LrMWpuD!mDgidUMGolxmbv6EYAZeBvA zH^obCr8yaC@6zc;#L?bmQLL7ntioFM#wT7axp-smawb+wPG;DtT1CEomn!zs?`|u|G2OK|RIxW{;8u~Z$5F-J#=cuc_wF9b(bKXe z&Ba%s*SICPwszPOnV6hRStqKs2e|Q4Sgr5K)$821nS?dkn10Ms%A0r+7B^jiXJOo1 z1)Z&p6H^n@Gii+onXdNM5=&mYZ_Q2JE{gyI-)}>1__^>;PRcpqcO790|HKyXL~i&l z{>ctj;7@xZNPrhlcO!pi=2W0PQ;x#BIB|2JtJsZ{%@q&tDr|$aCv&$_o*PE*jOpCs zbn)@4I9KTH@J`@CpdRE0w2%czJxBqR>=h!UvKP5cjsXXcMBXCnwU_v5u&xnWU-6ASZ#ptqGM_Bz-CdG(005ILB+JBcYxhG$vxa=jrz0j&7$nJ~$wLppau1719( z>QYN?o$&fS_ADQ`bK}MNRGOU|59T*uHMJYDrk$Q_O?E+h&F4p?d()F)8yn_`(~Mo83S6L!f5A-Ug|PNQM@p9~8ymcdJ&uq`&9%=kIq!)%B;;#N zz10jq<%yk1m6#8}(&R!M@kylQDq6l&*(q;q$4l$PTmYsfQbzVvxxH@oY&aef&kBb6 zygWsaiMg~3$oLeRVACn_>+&?UA$7qigo9JX+hSE|1M9dj zIw8Q~!Z0aurfRTfH$AhtPV}L58`e{4Qep~W$ab)4o50M~zLKPGR0WtflT0>=5;>p#ia{(Vbh6nDr_cyAO*quvlX`8XQN;RtBumpQZcI^lS({FGW znC=XkL=%M(_bWb3r&OY0-H!6@OsV2+-ZCYnXrk&f4MdH>M0O>)cCn8VbEAVx3cxU& z-Y#u=^9Fb7CD|vp$E!)kKeKdZ+>?Juf>jN^gJEMEH$!nZMoS+Dvs6guyxnL*ItS=La&g81Ji8FhRd;#~Zo07`7OMv0m%EcX?*nM-i0cAv*3_il&mMsgIqJWuwQW z^pa#p1e5M9I;Ppa`)rbc8mgy8b%)wnm(oy2<0S14b+lt=KUPuhhaOa&YTDcx#LUQo z=RElslkyd=BJygy^3n3fmEbX^=Y-I%UhzOYR%KeQ;y53&Qe>#4K=i@RF6)F|7tM*yhKv*cTqNzah=84h0i(_DXwbD13M=Tn|>-a1b4}vp5zxI?5nMPRHe>s&4Vds#{bD ztXFm#aH4CeUwf>1r`m4b%Gx)eG39T4n(Uov!&cDpCxP?08i5vo_>w;%O=lEDU!bLe zn~KQ)sipVx(GD|$`cuxpj=szXcR#uRGSP?%oh#jEKo&s&w735 zYBk(&f(`gΠ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%0Da~=Lksv!yU|KE+@g9Sd8E3p>Uj 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

\n"; + } + +} diff --git a/lib/solr-php-client/Document.php b/lib/solr-php-client/Document.php new file mode 100755 index 0000000..7b9784a --- /dev/null +++ b/lib/solr-php-client/Document.php @@ -0,0 +1,367 @@ + + */ + +/** + * Holds Key / Value pairs that represent a Solr Document along with any associated boost + * values. Field values can be accessed by direct dereferencing such as: + * + * ... + * $document->title = 'Something'; + * echo $document->title; + * ... + * + * + * Additionally, the field values can be iterated with foreach + * + * + * foreach ($document as $fieldName => $fieldValue) + * { + * ... + * } + * + */ +class Apache_Solr_Document implements IteratorAggregate +{ + /** + * SVN Revision meta data for this class + */ + const SVN_REVISION = '$Revision: 54 $'; + + /** + * SVN ID meta data for this class + */ + const SVN_ID = '$Id: Document.php 54 2011-02-04 16:29:18Z donovan.jimenez $'; + + /** + * Document boost value + * + * @var float + */ + protected $_documentBoost = false; + + /** + * Document field values, indexed by name + * + * @var array + */ + protected $_fields = array(); + + /** + * Document field boost values, indexed by name + * + * @var array array of floats + */ + protected $_fieldBoosts = array(); + + /** + * Clear all boosts and fields from this document + */ + public function clear() + { + $this->_documentBoost = false; + + $this->_fields = array(); + $this->_fieldBoosts = array(); + } + + /** + * Get current document boost + * + * @return mixed will be false for default, or else a float + */ + public function getBoost() + { + return $this->_documentBoost; + } + + /** + * Set document boost factor + * + * @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false + */ + public function setBoost($boost) + { + $boost = (float) $boost; + + if ($boost > 0.0) + { + $this->_documentBoost = $boost; + } + else + { + $this->_documentBoost = false; + } + } + + /** + * Add a value to a multi-valued field + * + * NOTE: the solr XML format allows you to specify boosts + * PER value even though the underlying Lucene implementation + * only allows a boost per field. To remedy this, the final + * field boost value will be the product of all specified boosts + * on field values - this is similar to SolrJ's functionality. + * + * + * $doc = new Apache_Solr_Document(); + * + * $doc->addField('foo', 'bar', 2.0); + * $doc->addField('foo', 'baz', 3.0); + * + * // resultant field boost will be 6! + * echo $doc->getFieldBoost('foo'); + * + * + * @param string $key + * @param mixed $value + * @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false + */ + public function addField($key, $value, $boost = false) + { + if (!isset($this->_fields[$key])) + { + // create holding array if this is the first value + $this->_fields[$key] = array(); + } + else if (!is_array($this->_fields[$key])) + { + // move existing value into array if it is not already an array + $this->_fields[$key] = array($this->_fields[$key]); + } + + if ($this->getFieldBoost($key) === false) + { + // boost not already set, set it now + $this->setFieldBoost($key, $boost); + } + else if ((float) $boost > 0.0) + { + // multiply passed boost with current field boost - similar to SolrJ implementation + $this->_fieldBoosts[$key] *= (float) $boost; + } + + // add value to array + $this->_fields[$key][] = $value; + } + + /** + * Handle the array manipulation for a multi-valued field + * + * @param string $key + * @param string $value + * @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false + * + * @deprecated Use addField(...) instead + */ + public function setMultiValue($key, $value, $boost = false) + { + $this->addField($key, $value, $boost); + } + + /** + * Get field information + * + * @param string $key + * @return mixed associative array of info if field exists, false otherwise + */ + public function getField($key) + { + if (isset($this->_fields[$key])) + { + return array( + 'name' => $key, + 'value' => $this->_fields[$key], + 'boost' => $this->getFieldBoost($key) + ); + } + + return false; + } + + /** + * Set a field value. Multi-valued fields should be set as arrays + * or instead use the addField(...) function which will automatically + * make sure the field is an array. + * + * @param string $key + * @param mixed $value + * @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false + */ + public function setField($key, $value, $boost = false) + { + $this->_fields[$key] = $value; + $this->setFieldBoost($key, $boost); + } + + /** + * Get the currently set field boost for a document field + * + * @param string $key + * @return float currently set field boost, false if one is not set + */ + public function getFieldBoost($key) + { + return isset($this->_fieldBoosts[$key]) ? $this->_fieldBoosts[$key] : false; + } + + /** + * Set the field boost for a document field + * + * @param string $key field name for the boost + * @param mixed $boost Use false for default boost, else cast to float that should be > 0 or will be treated as false + */ + public function setFieldBoost($key, $boost) + { + $boost = (float) $boost; + + if ($boost > 0.0) + { + $this->_fieldBoosts[$key] = $boost; + } + else + { + $this->_fieldBoosts[$key] = false; + } + } + + /** + * Return current field boosts, indexed by field name + * + * @return array + */ + public function getFieldBoosts() + { + return $this->_fieldBoosts; + } + + /** + * Get the names of all fields in this document + * + * @return array + */ + public function getFieldNames() + { + return array_keys($this->_fields); + } + + /** + * Get the values of all fields in this document + * + * @return array + */ + public function getFieldValues() + { + return array_values($this->_fields); + } + + /** + * IteratorAggregate implementation function. Allows usage: + * + * + * foreach ($document as $key => $value) + * { + * ... + * } + * + */ + public function getIterator() + { + $arrayObject = new ArrayObject($this->_fields); + + return $arrayObject->getIterator(); + } + + /** + * Magic get for field values + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + if (isset($this->_fields[$key])) + { + return $this->_fields[$key]; + } + + return null; + } + + /** + * Magic set for field values. Multi-valued fields should be set as arrays + * or instead use the addField(...) function which will automatically + * make sure the field is an array. + * + * @param string $key + * @param mixed $value + */ + public function __set($key, $value) + { + $this->setField($key, $value); + } + + /** + * Magic isset for fields values. Do not call directly. Allows usage: + * + * + * isset($document->some_field); + * + * + * @param string $key + * @return boolean + */ + public function __isset($key) + { + return isset($this->_fields[$key]); + } + + /** + * Magic unset for field values. Do not call directly. Allows usage: + * + * + * unset($document->some_field); + * + * + * @param string $key + */ + public function __unset($key) + { + unset($this->_fields[$key]); + unset($this->_fieldBoosts[$key]); + } +} \ No newline at end of file diff --git a/lib/solr-php-client/Exception.php b/lib/solr-php-client/Exception.php new file mode 100755 index 0000000..e6bc4f4 --- /dev/null +++ b/lib/solr-php-client/Exception.php @@ -0,0 +1,50 @@ + + */ + +class Apache_Solr_Exception extends Exception +{ + /** + * SVN Revision meta data for this class + */ + const SVN_REVISION = '$Revision: 54 $'; + + /** + * SVN ID meta data for this class + */ + const SVN_ID = '$Id: Exception.php 54 2011-02-04 16:29:18Z donovan.jimenez $'; +} \ No newline at end of file diff --git a/lib/solr-php-client/HttpTransport/Abstract.php b/lib/solr-php-client/HttpTransport/Abstract.php new file mode 100755 index 0000000..cf9f76d --- /dev/null +++ b/lib/solr-php-client/HttpTransport/Abstract.php @@ -0,0 +1,89 @@ +, Donovan Jimenez + */ + +/** + * Convenience class that implements the transport implementation. Can be extended by + * real implementations to do some of the common book keeping + */ +abstract class Apache_Solr_HttpTransport_Abstract implements Apache_Solr_HttpTransport_Interface +{ + /** + * Our default timeout value for requests that don't specify a timeout + * + * @var float + */ + private $_defaultTimeout = false; + + /** + * Get the current default timeout setting (initially the default_socket_timeout ini setting) + * in seconds + * + * @return float + */ + public function getDefaultTimeout() + { + // lazy load the default timeout from the ini settings + if ($this->_defaultTimeout === false) + { + $this->_defaultTimeout = (int) ini_get('default_socket_timeout'); + + // double check we didn't get 0 for a timeout + if ($this->_defaultTimeout <= 0) + { + $this->_defaultTimeout = 60; + } + } + + return $this->_defaultTimeout; + } + + /** + * Set the current default timeout for all HTTP requests + * + * @param float $timeout + */ + public function setDefaultTimeout($timeout) + { + $timeout = (float) $timeout; + + if ($timeout >= 0) + { + $this->_defaultTimeout = $timeout; + } + } +} \ No newline at end of file diff --git a/lib/solr-php-client/HttpTransport/Curl.php b/lib/solr-php-client/HttpTransport/Curl.php new file mode 100755 index 0000000..7cb7743 --- /dev/null +++ b/lib/solr-php-client/HttpTransport/Curl.php @@ -0,0 +1,198 @@ +, Donovan Jimenez + */ + +// Require Apache_Solr_HttpTransport_Abstract +require_once(dirname(__FILE__) . '/Abstract.php'); + +/** + * A Curl based HTTP transport. Uses a single curl session for all requests. + */ +class Apache_Solr_HttpTransport_Curl extends Apache_Solr_HttpTransport_Abstract +{ + /** + * SVN Revision meta data for this class + */ + const SVN_REVISION = '$Revision:$'; + + /** + * SVN ID meta data for this class + */ + const SVN_ID = '$Id:$'; + + /** + * Curl Session Handle + * + * @var resource + */ + private $_curl; + + /** + * Initializes a curl session + */ + public function __construct() + { + // initialize a CURL session + $this->_curl = curl_init(); + + // set common options that will not be changed during the session + curl_setopt_array($this->_curl, array( + // return the response body from curl_exec + CURLOPT_RETURNTRANSFER => true, + + // get the output as binary data + CURLOPT_BINARYTRANSFER => true, + + // we do not need the headers in the output, we get everything we need from curl_getinfo + CURLOPT_HEADER => false + )); + } + + /** + * Closes a curl session + */ + function __destruct() + { + // close our curl session + curl_close($this->_curl); + } + + public function performGetRequest($url, $timeout = false) + { + // check the timeout value + if ($timeout === false || $timeout <= 0.0) + { + // use the default timeout + $timeout = $this->getDefaultTimeout(); + } + + // set curl GET options + curl_setopt_array($this->_curl, array( + // make sure we're returning the body + CURLOPT_NOBODY => false, + + // make sure we're GET + CURLOPT_HTTPGET => true, + + // set the URL + CURLOPT_URL => $url, + + // set the timeout + CURLOPT_TIMEOUT => $timeout + )); + + // make the request + $responseBody = curl_exec($this->_curl); + + // get info from the transfer + $statusCode = curl_getinfo($this->_curl, CURLINFO_HTTP_CODE); + $contentType = curl_getinfo($this->_curl, CURLINFO_CONTENT_TYPE); + + return new Apache_Solr_HttpTransport_Response($statusCode, $contentType, $responseBody); + } + + public function performHeadRequest($url, $timeout = false) + { + // check the timeout value + if ($timeout === false || $timeout <= 0.0) + { + // use the default timeout + $timeout = $this->getDefaultTimeout(); + } + + // set curl HEAD options + curl_setopt_array($this->_curl, array( + // this both sets the method to HEAD and says not to return a body + CURLOPT_NOBODY => true, + + // set the URL + CURLOPT_URL => $url, + + // set the timeout + CURLOPT_TIMEOUT => $timeout + )); + + // make the request + $responseBody = curl_exec($this->_curl); + + // get info from the transfer + $statusCode = curl_getinfo($this->_curl, CURLINFO_HTTP_CODE); + $contentType = curl_getinfo($this->_curl, CURLINFO_CONTENT_TYPE); + + return new Apache_Solr_HttpTransport_Response($statusCode, $contentType, $responseBody); + } + + public function performPostRequest($url, $postData, $contentType, $timeout = false) + { + // check the timeout value + if ($timeout === false || $timeout <= 0.0) + { + // use the default timeout + $timeout = $this->getDefaultTimeout(); + } + + // set curl POST options + curl_setopt_array($this->_curl, array( + // make sure we're returning the body + CURLOPT_NOBODY => false, + + // make sure we're POST + CURLOPT_POST => true, + + // set the URL + CURLOPT_URL => $url, + + // set the post data + CURLOPT_POSTFIELDS => $postData, + + // set the content type + CURLOPT_HTTPHEADER => array("Content-Type: {$contentType}"), + + // set the timeout + CURLOPT_TIMEOUT => $timeout + )); + + // make the request + $responseBody = curl_exec($this->_curl); + + // get info from the transfer + $statusCode = curl_getinfo($this->_curl, CURLINFO_HTTP_CODE); + $contentType = curl_getinfo($this->_curl, CURLINFO_CONTENT_TYPE); + + return new Apache_Solr_HttpTransport_Response($statusCode, $contentType, $responseBody); + } +} \ No newline at end of file diff --git a/lib/solr-php-client/HttpTransport/CurlNoReuse.php b/lib/solr-php-client/HttpTransport/CurlNoReuse.php new file mode 100755 index 0000000..1454958 --- /dev/null +++ b/lib/solr-php-client/HttpTransport/CurlNoReuse.php @@ -0,0 +1,196 @@ +, Donovan Jimenez + */ + +// Require Apache_Solr_HttpTransport_Abstract +require_once(dirname(__FILE__) . '/Abstract.php'); + +/** + * An alternative Curl HTTP transport that opens and closes a curl session for + * every request. This isn't the recommended way to use curl, but some version of + * PHP have memory issues. + */ +class Apache_Solr_HttpTransport_CurlNoReuse extends Apache_Solr_HttpTransport_Abstract +{ + /** + * SVN Revision meta data for this class + */ + const SVN_REVISION = '$Revision:$'; + + /** + * SVN ID meta data for this class + */ + const SVN_ID = '$Id:$'; + + public function performGetRequest($url, $timeout = false) + { + // check the timeout value + if ($timeout === false || $timeout <= 0.0) + { + // use the default timeout + $timeout = $this->getDefaultTimeout(); + } + + $curl = curl_init(); + + // set curl GET options + curl_setopt_array($curl, array( + // return the response body from curl_exec + CURLOPT_RETURNTRANSFER => true, + + // get the output as binary data + CURLOPT_BINARYTRANSFER => true, + + // we do not need the headers in the output, we get everything we need from curl_getinfo + CURLOPT_HEADER => false, + + // set the URL + CURLOPT_URL => $url, + + // set the timeout + CURLOPT_TIMEOUT => $timeout + )); + + // make the request + $responseBody = curl_exec($curl); + + // get info from the transfer + $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE); + + // close our curl session - we're done with it + curl_close($curl); + + return new Apache_Solr_HttpTransport_Response($statusCode, $contentType, $responseBody); + } + + public function performHeadRequest($url, $timeout = false) + { + // check the timeout value + if ($timeout === false || $timeout <= 0.0) + { + // use the default timeout + $timeout = $this->getDefaultTimeout(); + } + + $curl = curl_init(); + + // set curl HEAD options + curl_setopt_array($curl, array( + // return the response body from curl_exec + CURLOPT_RETURNTRANSFER => true, + + // get the output as binary data + CURLOPT_BINARYTRANSFER => true, + + // we do not need the headers in the output, we get everything we need from curl_getinfo + CURLOPT_HEADER => false, + + // this both sets the method to HEAD and says not to return a body + CURLOPT_NOBODY => true, + + // set the URL + CURLOPT_URL => $url, + + // set the timeout + CURLOPT_TIMEOUT => $timeout + )); + + // make the request + $responseBody = curl_exec($curl); + + // get info from the transfer + $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE); + + // close our curl session - we're done with it + curl_close($curl); + + return new Apache_Solr_HttpTransport_Response($statusCode, $contentType, $responseBody); + } + + public function performPostRequest($url, $postData, $contentType, $timeout = false) + { + // check the timeout value + if ($timeout === false || $timeout <= 0.0) + { + // use the default timeout + $timeout = $this->getDefaultTimeout(); + } + + $curl = curl_init(); + + // set curl POST options + curl_setopt_array($curl, array( + // return the response body from curl_exec + CURLOPT_RETURNTRANSFER => true, + + // get the output as binary data + CURLOPT_BINARYTRANSFER => true, + + // we do not need the headers in the output, we get everything we need from curl_getinfo + CURLOPT_HEADER => false, + + // make sure we're POST + CURLOPT_POST => true, + + // set the URL + CURLOPT_URL => $url, + + // set the post data + CURLOPT_POSTFIELDS => $postData, + + // set the content type + CURLOPT_HTTPHEADER => array("Content-Type: {$contentType}"), + + // set the timeout + CURLOPT_TIMEOUT => $timeout + )); + + // make the request + $responseBody = curl_exec($curl); + + // get info from the transfer + $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $contentType = curl_getinfo($curl, CURLINFO_CONTENT_TYPE); + + // close our curl session - we're done with it + curl_close($curl); + + return new Apache_Solr_HttpTransport_Response($statusCode, $contentType, $responseBody); + } +} \ No newline at end of file diff --git a/lib/solr-php-client/HttpTransport/FileGetContents.php b/lib/solr-php-client/HttpTransport/FileGetContents.php new file mode 100755 index 0000000..5e01775 --- /dev/null +++ b/lib/solr-php-client/HttpTransport/FileGetContents.php @@ -0,0 +1,216 @@ + + */ + +// Require Apache_Solr_HttpTransport_Abstract +require_once(dirname(__FILE__) . '/Abstract.php'); + +/** + * HTTP Transport implemenation that uses the builtin http URL wrappers and file_get_contents + */ +class Apache_Solr_HttpTransport_FileGetContents extends Apache_Solr_HttpTransport_Abstract +{ + /** + * SVN Revision meta data for this class + */ + const SVN_REVISION = '$Revision: $'; + + /** + * SVN ID meta data for this class + */ + const SVN_ID = '$Id: $'; + + /** + * Reusable stream context resources for GET and POST operations + * + * @var resource + */ + private $_getContext, $_headContext, $_postContext; + + /** + * Initializes our reuseable get and post stream contexts + */ + public function __construct() + { + $this->_getContext = stream_context_create(); + $this->_headContext = stream_context_create(); + $this->_postContext = stream_context_create(); + } + + public function performGetRequest($url, $timeout = false) + { + // set the timeout if specified + if ($timeout !== FALSE && $timeout > 0.0) + { + // timeouts with file_get_contents seem to need + // to be halved to work as expected + $timeout = (float) $timeout / 2; + + stream_context_set_option($this->_getContext, 'http', 'timeout', $timeout); + } + else + { + // use the default timeout pulled from default_socket_timeout otherwise + stream_context_set_option($this->_getContext, 'http', 'timeout', $this->getDefaultTimeout()); + } + + // $http_response_headers will be updated by the call to file_get_contents later + // see http://us.php.net/manual/en/wrappers.http.php for documentation + // Unfortunately, it will still create a notice in analyzers if we don't set it here + $http_response_header = null; + $responseBody = @file_get_contents($url, false, $this->_getContext); + + return $this->_getResponseFromParts($responseBody, $http_response_header); + } + + public function performHeadRequest($url, $timeout = false) + { + stream_context_set_option($this->_headContext, array( + 'http' => array( + // set HTTP method + 'method' => 'HEAD', + + // default timeout + 'timeout' => $this->getDefaultTimeout() + ) + ) + ); + + // set the timeout if specified + if ($timeout !== FALSE && $timeout > 0.0) + { + // timeouts with file_get_contents seem to need + // to be halved to work as expected + $timeout = (float) $timeout / 2; + + stream_context_set_option($this->_headContext, 'http', 'timeout', $timeout); + } + + // $http_response_headers will be updated by the call to file_get_contents later + // see http://us.php.net/manual/en/wrappers.http.php for documentation + // Unfortunately, it will still create a notice in analyzers if we don't set it here + $http_response_header = null; + $responseBody = @file_get_contents($url, false, $this->_headContext); + + return $this->_getResponseFromParts($responseBody, $http_response_header); + } + + public function performPostRequest($url, $rawPost, $contentType, $timeout = false) + { + stream_context_set_option($this->_postContext, array( + 'http' => array( + // set HTTP method + 'method' => 'POST', + + // Add our posted content type + 'header' => "Content-Type: $contentType", + + // the posted content + 'content' => $rawPost, + + // default timeout + 'timeout' => $this->getDefaultTimeout() + ) + ) + ); + + // set the timeout if specified + if ($timeout !== FALSE && $timeout > 0.0) + { + // timeouts with file_get_contents seem to need + // to be halved to work as expected + $timeout = (float) $timeout / 2; + + stream_context_set_option($this->_postContext, 'http', 'timeout', $timeout); + } + + // $http_response_header will be updated by the call to file_get_contents later + // see http://us.php.net/manual/en/wrappers.http.php for documentation + // Unfortunately, it will still create a notice in analyzers if we don't set it here + $http_response_header = null; + $responseBody = @file_get_contents($url, false, $this->_postContext); + + // reset content of post context to reclaim memory + stream_context_set_option($this->_postContext, 'http', 'content', ''); + + return $this->_getResponseFromParts($responseBody, $http_response_header); + } + + private function _getResponseFromParts($rawResponse, $httpHeaders) + { + //Assume 0, false as defaults + $status = 0; + $contentType = false; + + //iterate through headers for real status, type, and encoding + if (is_array($httpHeaders) && count($httpHeaders) > 0) + { + //look at the first headers for the HTTP status code + //and message (errors are usually returned this way) + // + //HTTP 100 Continue response can also be returned before + //the REAL status header, so we need look until we find + //the last header starting with HTTP + // + //the spec: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.1 + // + //Thanks to Daniel Andersson for pointing out this oversight + while (isset($httpHeaders[0]) && substr($httpHeaders[0], 0, 4) == 'HTTP') + { + // we can do a intval on status line without the "HTTP/1.X " to get the code + $status = intval(substr($httpHeaders[0], 9)); + + // remove this from the headers so we can check for more + array_shift($httpHeaders); + } + + //Look for the Content-Type response header and determine type + //and encoding from it (if possible - such as 'Content-Type: text/plain; charset=UTF-8') + foreach ($httpHeaders as $header) + { + // look for the header that starts appropriately + if (strncasecmp($header, 'Content-Type:', 13) == 0) + { + $contentType = substr($header, 13); + break; + } + } + } + + return new Apache_Solr_HttpTransport_Response($status, $contentType, $rawResponse); + } +} \ No newline at end of file diff --git a/lib/solr-php-client/HttpTransport/Interface.php b/lib/solr-php-client/HttpTransport/Interface.php new file mode 100755 index 0000000..090fc27 --- /dev/null +++ b/lib/solr-php-client/HttpTransport/Interface.php @@ -0,0 +1,94 @@ +, Donovan Jimenez + */ + +// require Apache_Solr_HttpTransport_Response +require_once(dirname(__FILE__) . '/Response.php'); + +/** + * Interface that all Transport (HTTP Requester) implementations must implement. These + * Implementations can then be plugged into the Service instance in order to user their + * the desired method for making HTTP requests + */ +interface Apache_Solr_HttpTransport_Interface +{ + /** + * Get the current default timeout for all HTTP requests + * + * @return float + */ + public function getDefaultTimeout(); + + /** + * Set the current default timeout for all HTTP requests + * + * @param float $timeout + */ + public function setDefaultTimeout($timeout); + + /** + * Perform a GET HTTP operation with an optional timeout and return the response + * contents, use getLastResponseHeaders to retrieve HTTP headers + * + * @param string $url + * @param float $timeout + * @return Apache_Solr_HttpTransport_Response HTTP response + */ + public function performGetRequest($url, $timeout = false); + + /** + * Perform a HEAD HTTP operation with an optional timeout and return the response + * headers - NOTE: head requests have no response body + * + * @param string $url + * @param float $timeout + * @return Apache_Solr_HttpTransport_Response HTTP response + */ + public function performHeadRequest($url, $timeout = false); + + /** + * Perform a POST HTTP operation with an optional timeout and return the response + * contents, use getLastResponseHeaders to retrieve HTTP headers + * + * @param string $url + * @param string $rawPost + * @param string $contentType + * @param float $timeout + * @return Apache_Solr_HttpTransport_Response HTTP response + */ + public function performPostRequest($url, $rawPost, $contentType, $timeout = false); +} \ No newline at end of file diff --git a/lib/solr-php-client/HttpTransport/Response.php b/lib/solr-php-client/HttpTransport/Response.php new file mode 100755 index 0000000..3f113ae --- /dev/null +++ b/lib/solr-php-client/HttpTransport/Response.php @@ -0,0 +1,255 @@ + + */ + +/** + * Represents the required pieces of an HTTP response provided by HTTP transport + * implementations and then consumed by the Apache_Solr_Response class which provides + * decoding + */ +class Apache_Solr_HttpTransport_Response +{ + /** + * Status Messages indexed by Status Code + * Obtained from: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + * + * @var array + */ + static private $_defaultStatusMessages = array( + // Specific to PHP Solr Client + 0 => "Communication Error", + + // Informational 1XX + 100 => "Continue", + 101 => "Switching Protocols", + + // Successful 2XX + 200 => "OK", + 201 => "Created", + 202 => "Accepted", + 203 => "Non-Authoritative Information", + 204 => "No Content", + 205 => "Reset Content", + 206 => "Partial Content", + + // Redirection 3XX + 300 => "Multiple Choices", + 301 => "Moved Permanently", + 302 => "Found", + 303 => "See Other", + 304 => "Not Modified", + 305 => "Use Proxy", + 307 => "Temporary Redirect", + + // Client Error 4XX + 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 => "Request Range Not Satisfiable", + 417 => "Expectation Failed", + + // Server Error 5XX + 500 => "Internal Server Error", + 501 => "Not Implemented", + 502 => "Bad Gateway", + 503 => "Service Unavailable", + 504 => "Gateway Timeout", + 505 => "HTTP Version Not Supported" + ); + + /** + * Get the HTTP status message based on status code + * + * @return string + */ + public static function getDefaultStatusMessage($statusCode) + { + $statusCode = (int) $statusCode; + + if (isset(self::$_defaultStatusMessages[$statusCode])) + { + return self::$_defaultStatusMessages[$statusCode]; + } + + return "Unknown Status"; + } + + /** + * The response's HTTP status code + * + * @var integer + */ + private $_statusCode; + + /** + * The response's HTTP status message + * + * @var string + */ + private $_statusMessage; + + /** + * The response's mime type + * + * @var string + */ + private $_mimeType; + + /** + * The response's character encoding + * + * @var string + */ + private $_encoding; + + /** + * The response's data + * + * @var string + */ + private $_responseBody; + + /** + * Construct a HTTP transport response + * + * @param integer $statusCode The HTTP status code + * @param string $contentType The VALUE of the Content-Type HTTP header + * @param string $responseBody The body of the HTTP response + */ + public function __construct($statusCode, $contentType, $responseBody) + { + // set the status code, make sure its an integer + $this->_statusCode = (int) $statusCode; + + // lookup up status message based on code + $this->_statusMessage = self::getDefaultStatusMessage($this->_statusCode); + + // set the response body, it should always be a string + $this->_responseBody = (string) $responseBody; + + // parse the content type header value for mimetype and encoding + // first set default values that will remain if we can't find + // what we're looking for in the content type + $this->_mimeType = "text/plain"; + $this->_encoding = "UTF-8"; + + if ($contentType) + { + // now break apart the header to see if there's character encoding + $contentTypeParts = explode(';', $contentType, 2); + + if (isset($contentTypeParts[0])) + { + $this->_mimeType = trim($contentTypeParts[0]); + } + + if (isset($contentTypeParts[1])) + { + // we have a second part, split it further + $contentTypeParts = explode('=', $contentTypeParts[1]); + + if (isset($contentTypeParts[1])) + { + $this->_encoding = trim($contentTypeParts[1]); + } + } + } + } + + /** + * Get the status code of the response + * + * @return integer + */ + public function getStatusCode() + { + return $this->_statusCode; + } + + /** + * Get the status message of the response + * + * @return string + */ + public function getStatusMessage() + { + return $this->_statusMessage; + } + + /** + * Get the mimetype of the response body + * + * @return string + */ + public function getMimeType() + { + return $this->_mimeType; + } + + /** + * Get the charset encoding of the response body. + * + * @return string + */ + public function getEncoding() + { + return $this->_encoding; + } + + /** + * Get the raw response body + * + * @return string + */ + public function getBody() + { + return $this->_responseBody; + } +} diff --git a/lib/solr-php-client/HttpTransportException.php b/lib/solr-php-client/HttpTransportException.php new file mode 100755 index 0000000..83c0001 --- /dev/null +++ b/lib/solr-php-client/HttpTransportException.php @@ -0,0 +1,79 @@ + + */ + +class Apache_Solr_HttpTransportException extends Apache_Solr_Exception +{ + /** + * SVN Revision meta data for this class + */ + const SVN_REVISION = '$Revision: 54 $'; + + /** + * SVN ID meta data for this class + */ + const SVN_ID = '$Id: HttpTransportException.php 54 2011-02-04 16:29:18Z donovan.jimenez $'; + + /** + * Response for which exception was generated + * + * @var Apache_Solr_Response + */ + private $_response; + + /** + * HttpTransportException Constructor + * + * @param Apache_Solr_Response $response + */ + public function __construct(Apache_Solr_Response $response) + { + parent::__construct("'{$response->getHttpStatus()}' Status: {$response->getHttpStatusMessage()}", $response->getHttpStatus()); + + $this->_response = $response; + } + + /** + * Get the response for which this exception was generated + * + * @return Apache_Solr_Response + */ + public function getResponse() + { + return $this->_response; + } +} \ No newline at end of file diff --git a/lib/solr-php-client/InvalidArgumentException.php b/lib/solr-php-client/InvalidArgumentException.php new file mode 100755 index 0000000..273f3d0 --- /dev/null +++ b/lib/solr-php-client/InvalidArgumentException.php @@ -0,0 +1,50 @@ + + */ + +class Apache_Solr_InvalidArgumentException extends Apache_Solr_Exception +{ + /** + * SVN Revision meta data for this class + */ + const SVN_REVISION = '$Revision: 54 $'; + + /** + * SVN ID meta data for this class + */ + const SVN_ID = '$Id: InvalidArgumentException.php 54 2011-02-04 16:29:18Z donovan.jimenez $'; +} \ No newline at end of file diff --git a/lib/solr-php-client/NoServiceAvailableException.php b/lib/solr-php-client/NoServiceAvailableException.php new file mode 100755 index 0000000..eead732 --- /dev/null +++ b/lib/solr-php-client/NoServiceAvailableException.php @@ -0,0 +1,50 @@ + + */ + +class Apache_Solr_NoServiceAvailableException extends Apache_Solr_Exception +{ + /** + * SVN Revision meta data for this class + */ + const SVN_REVISION = '$Revision: 54 $'; + + /** + * SVN ID meta data for this class + */ + const SVN_ID = '$Id: NoServiceAvailableException.php 54 2011-02-04 16:29:18Z donovan.jimenez $'; +} \ No newline at end of file diff --git a/lib/solr-php-client/ParserException.php b/lib/solr-php-client/ParserException.php new file mode 100755 index 0000000..29283db --- /dev/null +++ b/lib/solr-php-client/ParserException.php @@ -0,0 +1,50 @@ + + */ + +class Apache_Solr_ParserException extends Apache_Solr_Exception +{ + /** + * SVN Revision meta data for this class + */ + const SVN_REVISION = '$Revision: 54 $'; + + /** + * SVN ID meta data for this class + */ + const SVN_ID = '$Id: ParserException.php 54 2011-02-04 16:29:18Z donovan.jimenez $'; +} \ No newline at end of file diff --git a/lib/solr-php-client/Response.php b/lib/solr-php-client/Response.php new file mode 100755 index 0000000..1a35e56 --- /dev/null +++ b/lib/solr-php-client/Response.php @@ -0,0 +1,247 @@ + + */ + +require_once(dirname(__FILE__) . '/ParserException.php'); + +/** + * Represents a Solr response. Parses the raw response into a set of stdClass objects + * and associative arrays for easy access. + * + * Currently requires json_decode which is bundled with PHP >= 5.2.0, Alternatively can be + * installed with PECL. Zend Framework also includes a purely PHP solution. + */ +class Apache_Solr_Response +{ + /** + * SVN Revision meta data for this class + */ + const SVN_REVISION = '$Revision: 54 $'; + + /** + * SVN ID meta data for this class + */ + const SVN_ID = '$Id: Response.php 54 2011-02-04 16:29:18Z donovan.jimenez $'; + + /** + * Holds the raw response used in construction + * + * @var Apache_Solr_HttpTransport_Response HTTP response + */ + protected $_response; + + /** + * Whether the raw response has been parsed + * + * @var boolean + */ + protected $_isParsed = false; + + /** + * Parsed representation of the data + * + * @var mixed + */ + protected $_parsedData; + + /** + * Data parsing flags. Determines what extra processing should be done + * after the data is initially converted to a data structure. + * + * @var boolean + */ + protected $_createDocuments = true, + $_collapseSingleValueArrays = true; + + /** + * Constructor. Takes the raw HTTP response body and the exploded HTTP headers + * + * @return Apache_Solr_HttpTransport_Response HTTP response + * @param boolean $createDocuments Whether to convert the documents json_decoded as stdClass instances to Apache_Solr_Document instances + * @param boolean $collapseSingleValueArrays Whether to make multivalued fields appear as single values + */ + public function __construct(Apache_Solr_HttpTransport_Response $response, $createDocuments = true, $collapseSingleValueArrays = true) + { + $this->_response = $response; + $this->_createDocuments = (bool) $createDocuments; + $this->_collapseSingleValueArrays = (bool) $collapseSingleValueArrays; + } + + /** + * Get the HTTP status code + * + * @return integer + */ + public function getHttpStatus() + { + return $this->_response->getStatusCode(); + } + + /** + * Get the HTTP status message of the response + * + * @return string + */ + public function getHttpStatusMessage() + { + return $this->_response->getStatusMessage(); + } + + /** + * Get content type of this Solr response + * + * @return string + */ + public function getType() + { + return $this->_response->getMimeType(); + } + + /** + * Get character encoding of this response. Should usually be utf-8, but just in case + * + * @return string + */ + public function getEncoding() + { + return $this->_response->getEncoding(); + } + + /** + * Get the raw response as it was given to this object + * + * @return string + */ + public function getRawResponse() + { + return $this->_response->getBody(); + } + + /** + * Magic get to expose the parsed data and to lazily load it + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + if (!$this->_isParsed) + { + $this->_parseData(); + $this->_isParsed = true; + } + + if (isset($this->_parsedData->$key)) + { + return $this->_parsedData->$key; + } + + return null; + } + + /** + * Magic function for isset function on parsed data + * + * @param string $key + * @return boolean + */ + public function __isset($key) + { + if (!$this->_isParsed) + { + $this->_parseData(); + $this->_isParsed = true; + } + + return isset($this->_parsedData->$key); + } + + /** + * Parse the raw response into the parsed_data array for access + * + * @throws Apache_Solr_ParserException If the data could not be parsed + */ + protected function _parseData() + { + //An alternative would be to use Zend_Json::decode(...) + $data = json_decode($this->_response->getBody()); + + // check that we receive a valid JSON response - we should never receive a null + if ($data === null) + { + throw new Apache_Solr_ParserException('Solr response does not appear to be valid JSON, please examine the raw response with getRawResponse() method'); + } + + //if we're configured to collapse single valued arrays or to convert them to Apache_Solr_Document objects + //and we have response documents, then try to collapse the values and / or convert them now + if (($this->_createDocuments || $this->_collapseSingleValueArrays) && isset($data->response) && is_array($data->response->docs)) + { + $documents = array(); + + foreach ($data->response->docs as $originalDocument) + { + if ($this->_createDocuments) + { + $document = new Apache_Solr_Document(); + } + else + { + $document = $originalDocument; + } + + foreach ($originalDocument as $key => $value) + { + //If a result is an array with only a single + //value then its nice to be able to access + //it as if it were always a single value + if ($this->_collapseSingleValueArrays && is_array($value) && count($value) <= 1) + { + $value = array_shift($value); + } + + $document->$key = $value; + } + + $documents[] = $document; + } + + $data->response->docs = $documents; + } + + $this->_parsedData = $data; + } +} \ No newline at end of file diff --git a/lib/solr-php-client/Service.php b/lib/solr-php-client/Service.php new file mode 100755 index 0000000..00ee68d --- /dev/null +++ b/lib/solr-php-client/Service.php @@ -0,0 +1,1187 @@ + + */ + +// See Issue #1 (http://code.google.com/p/solr-php-client/issues/detail?id=1) +// Doesn't follow typical include path conventions, but is more convenient for users +require_once(dirname(__FILE__) . '/Exception.php'); +require_once(dirname(__FILE__) . '/HttpTransportException.php'); +require_once(dirname(__FILE__) . '/InvalidArgumentException.php'); + +require_once(dirname(__FILE__) . '/Document.php'); +require_once(dirname(__FILE__) . '/Response.php'); + +require_once(dirname(__FILE__) . '/HttpTransport/Interface.php'); + +/** + * Starting point for the Solr API. Represents a Solr server resource and has + * methods for pinging, adding, deleting, committing, optimizing and searching. + * + * Example Usage: + * + * ... + * $solr = new Apache_Solr_Service(); //or explicitly new Apache_Solr_Service('localhost', 8180, '/solr') + * + * if ($solr->ping()) + * { + * $solr->deleteByQuery('*:*'); //deletes ALL documents - be careful :) + * + * $document = new Apache_Solr_Document(); + * $document->id = uniqid(); //or something else suitably unique + * + * $document->title = 'Some Title'; + * $document->content = 'Some content for this wonderful document. Blah blah blah.'; + * + * $solr->addDocument($document); //if you're going to be adding documents in bulk using addDocuments + * //with an array of documents is faster + * + * $solr->commit(); //commit to see the deletes and the document + * $solr->optimize(); //merges multiple segments into one + * + * //and the one we all care about, search! + * //any other common or custom parameters to the request handler can go in the + * //optional 4th array argument. + * $solr->search('content:blah', 0, 10, array('sort' => 'timestamp desc')); + * } + * ... + * + * + * @todo Investigate using other HTTP clients other than file_get_contents built-in handler. Could provide performance + * improvements when dealing with multiple requests by using HTTP's keep alive functionality + */ +class Apache_Solr_Service +{ + /** + * SVN Revision meta data for this class + */ + const SVN_REVISION = '$Revision: 59 $'; + + /** + * SVN ID meta data for this class + */ + const SVN_ID = '$Id: Service.php 59 2011-02-08 20:38:59Z donovan.jimenez $'; + + /** + * Response writer we'll request - JSON. See http://code.google.com/p/solr-php-client/issues/detail?id=6#c1 for reasoning + */ + const SOLR_WRITER = 'json'; + + /** + * NamedList Treatment constants + */ + const NAMED_LIST_FLAT = 'flat'; + const NAMED_LIST_MAP = 'map'; + + /** + * Search HTTP Methods + */ + const METHOD_GET = 'GET'; + const METHOD_POST = 'POST'; + + /** + * Servlet mappings + */ + const PING_SERVLET = 'admin/ping'; + const UPDATE_SERVLET = 'update'; + const SEARCH_SERVLET = 'select'; + const THREADS_SERVLET = 'admin/threads'; + const EXTRACT_SERVLET = 'update/extract'; + + /** + * Server identification strings + * + * @var string + */ + protected $_host, $_port, $_path; + + /** + * Whether {@link Apache_Solr_Response} objects should create {@link Apache_Solr_Document}s in + * the returned parsed data + * + * @var boolean + */ + protected $_createDocuments = true; + + /** + * Whether {@link Apache_Solr_Response} objects should have multivalue fields with only a single value + * collapsed to appear as a single value would. + * + * @var boolean + */ + protected $_collapseSingleValueArrays = true; + + /** + * How NamedLists should be formatted in the output. This specifically effects facet counts. Valid values + * are {@link Apache_Solr_Service::NAMED_LIST_MAP} (default) or {@link Apache_Solr_Service::NAMED_LIST_FLAT}. + * + * @var string + */ + protected $_namedListTreatment = self::NAMED_LIST_MAP; + + /** + * Query delimiters. Someone might want to be able to change + * these (to use & instead of & for example), so I've provided them. + * + * @var string + */ + protected $_queryDelimiter = '?', $_queryStringDelimiter = '&', $_queryBracketsEscaped = true; + + /** + * Constructed servlet full path URLs + * + * @var string + */ + protected $_pingUrl, $_updateUrl, $_searchUrl, $_threadsUrl; + + /** + * Keep track of whether our URLs have been constructed + * + * @var boolean + */ + protected $_urlsInited = false; + + /** + * HTTP Transport implementation (pluggable) + * + * @var Apache_Solr_HttpTransport_Interface + */ + protected $_httpTransport = false; + + /** + * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc. + * + * NOTE: inside a phrase fewer characters need escaped, use {@link Apache_Solr_Service::escapePhrase()} instead + * + * @param string $value + * @return string + */ + static public function escape($value) + { + //list taken from http://lucene.apache.org/java/docs/queryparsersyntax.html#Escaping%20Special%20Characters + $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/'; + $replace = '\\\$1'; + + return preg_replace($pattern, $replace, $value); + } + + /** + * Escape a value meant to be contained in a phrase for special query characters + * + * @param string $value + * @return string + */ + static public function escapePhrase($value) + { + $pattern = '/("|\\\)/'; + $replace = '\\\$1'; + + return preg_replace($pattern, $replace, $value); + } + + /** + * Convenience function for creating phrase syntax from a value + * + * @param string $value + * @return string + */ + static public function phrase($value) + { + return '"' . self::escapePhrase($value) . '"'; + } + + /** + * Constructor. All parameters are optional and will take on default values + * if not specified. + * + * @param string $host + * @param string $port + * @param string $path + * @param Apache_Solr_HttpTransport_Interface $httpTransport + */ + public function __construct($host = 'localhost', $port = 8180, $path = '/solr/', $httpTransport = false) + { + $this->setHost($host); + $this->setPort($port); + $this->setPath($path); + + $this->_initUrls(); + + if ($httpTransport) + { + $this->setHttpTransport($httpTransport); + } + + // check that our php version is >= 5.1.3 so we can correct for http_build_query behavior later + $this->_queryBracketsEscaped = version_compare(phpversion(), '5.1.3', '>='); + } + + /** + * Return a valid http URL given this server's host, port and path and a provided servlet name + * + * @param string $servlet + * @return string + */ + protected function _constructUrl($servlet, $params = array()) + { + if (count($params)) + { + //escape all parameters appropriately for inclusion in the query string + $escapedParams = array(); + + foreach ($params as $key => $value) + { + $escapedParams[] = urlencode($key) . '=' . urlencode($value); + } + + $queryString = $this->_queryDelimiter . implode($this->_queryStringDelimiter, $escapedParams); + } + else + { + $queryString = ''; + } + + return 'http://' . $this->_host . ':' . $this->_port . $this->_path . $servlet . $queryString; + } + + /** + * Construct the Full URLs for the three servlets we reference + */ + protected function _initUrls() + { + //Initialize our full servlet URLs now that we have server information + $this->_extractUrl = $this->_constructUrl(self::EXTRACT_SERVLET); + $this->_pingUrl = $this->_constructUrl(self::PING_SERVLET); + $this->_searchUrl = $this->_constructUrl(self::SEARCH_SERVLET); + $this->_threadsUrl = $this->_constructUrl(self::THREADS_SERVLET, array('wt' => self::SOLR_WRITER )); + $this->_updateUrl = $this->_constructUrl(self::UPDATE_SERVLET, array('wt' => self::SOLR_WRITER )); + + $this->_urlsInited = true; + } + + protected function _generateQueryString($params) + { + // use http_build_query to encode our arguments because its faster + // than urlencoding all the parts ourselves in a loop + // + // because http_build_query treats arrays differently than we want to, correct the query + // string by changing foo[#]=bar (# being an actual number) parameter strings to just + // multiple foo=bar strings. This regex should always work since '=' will be urlencoded + // anywhere else the regex isn't expecting it + // + // NOTE: before php 5.1.3 brackets were not url encoded by http_build query - we've checked + // the php version in the constructor and put the results in the instance variable. Also, before + // 5.1.2 the arg_separator parameter was not available, so don't use it + if ($this->_queryBracketsEscaped) + { + $queryString = http_build_query($params, null, $this->_queryStringDelimiter); + return preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', $queryString); + } + else + { + $queryString = http_build_query($params); + return preg_replace('/\\[(?:[0-9]|[1-9][0-9]+)\\]=/', '=', $queryString); + } + } + + /** + * Central method for making a get operation against this Solr Server + * + * @param string $url + * @param float $timeout Read timeout in seconds + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If a non 200 response status is returned + */ + protected function _sendRawGet($url, $timeout = FALSE) + { + $httpTransport = $this->getHttpTransport(); + + $httpResponse = $httpTransport->performGetRequest($url, $timeout); + $solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays); + + if ($solrResponse->getHttpStatus() != 200) + { + throw new Apache_Solr_HttpTransportException($solrResponse); + } + + return $solrResponse; + } + + /** + * Central method for making a post operation against this Solr Server + * + * @param string $url + * @param string $rawPost + * @param float $timeout Read timeout in seconds + * @param string $contentType + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If a non 200 response status is returned + */ + protected function _sendRawPost($url, $rawPost, $timeout = FALSE, $contentType = 'text/xml; charset=UTF-8') + { + $httpTransport = $this->getHttpTransport(); + + $httpResponse = $httpTransport->performPostRequest($url, $rawPost, $contentType, $timeout); + $solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays); + + if ($solrResponse->getHttpStatus() != 200) + { + throw new Apache_Solr_HttpTransportException($solrResponse); + } + + return $solrResponse; + } + + /** + * Returns the set host + * + * @return string + */ + public function getHost() + { + return $this->_host; + } + + /** + * Set the host used. If empty will fallback to constants + * + * @param string $host + * + * @throws Apache_Solr_InvalidArgumentException If the host parameter is empty + */ + public function setHost($host) + { + //Use the provided host or use the default + if (empty($host)) + { + throw new Apache_Solr_InvalidArgumentException('Host parameter is empty'); + } + else + { + $this->_host = $host; + } + + if ($this->_urlsInited) + { + $this->_initUrls(); + } + } + + /** + * Get the set port + * + * @return integer + */ + public function getPort() + { + return $this->_port; + } + + /** + * Set the port used. If empty will fallback to constants + * + * @param integer $port + * + * @throws Apache_Solr_InvalidArgumentException If the port parameter is empty + */ + public function setPort($port) + { + //Use the provided port or use the default + $port = (int) $port; + + if ($port <= 0) + { + throw new Apache_Solr_InvalidArgumentException('Port is not a valid port number'); + } + else + { + $this->_port = $port; + } + + if ($this->_urlsInited) + { + $this->_initUrls(); + } + } + + /** + * Get the set path. + * + * @return string + */ + public function getPath() + { + return $this->_path; + } + + /** + * Set the path used. If empty will fallback to constants + * + * @param string $path + */ + public function setPath($path) + { + $path = trim($path, '/'); + + $this->_path = '/' . $path . '/'; + + if ($this->_urlsInited) + { + $this->_initUrls(); + } + } + + /** + * Get the current configured HTTP Transport + * + * @return HttpTransportInterface + */ + public function getHttpTransport() + { + // lazy load a default if one has not be set + if ($this->_httpTransport === false) + { + require_once(dirname(__FILE__) . '/HttpTransport/FileGetContents.php'); + + $this->_httpTransport = new Apache_Solr_HttpTransport_FileGetContents(); + } + + return $this->_httpTransport; + } + + /** + * Set the HTTP Transport implemenation that will be used for all HTTP requests + * + * @param Apache_Solr_HttpTransport_Interface + */ + public function setHttpTransport(Apache_Solr_HttpTransport_Interface $httpTransport) + { + $this->_httpTransport = $httpTransport; + } + + /** + * Set the create documents flag. This determines whether {@link Apache_Solr_Response} objects will + * parse the response and create {@link Apache_Solr_Document} instances in place. + * + * @param boolean $createDocuments + */ + public function setCreateDocuments($createDocuments) + { + $this->_createDocuments = (bool) $createDocuments; + } + + /** + * Get the current state of teh create documents flag. + * + * @return boolean + */ + public function getCreateDocuments() + { + return $this->_createDocuments; + } + + /** + * Set the collapse single value arrays flag. + * + * @param boolean $collapseSingleValueArrays + */ + public function setCollapseSingleValueArrays($collapseSingleValueArrays) + { + $this->_collapseSingleValueArrays = (bool) $collapseSingleValueArrays; + } + + /** + * Get the current state of the collapse single value arrays flag. + * + * @return boolean + */ + public function getCollapseSingleValueArrays() + { + return $this->_collapseSingleValueArrays; + } + + /** + * Get the current default timeout setting (initially the default_socket_timeout ini setting) + * in seconds + * + * @return float + * + * @deprecated Use the getDefaultTimeout method on the HTTP transport implementation + */ + public function getDefaultTimeout() + { + return $this->getHttpTransport()->getDefaultTimeout(); + } + + /** + * Set the default timeout for all calls that aren't passed a specific timeout + * + * @param float $timeout Timeout value in seconds + * + * @deprecated Use the setDefaultTimeout method on the HTTP transport implementation + */ + public function setDefaultTimeout($timeout) + { + $this->getHttpTransport()->setDefaultTimeout($timeout); + } + + /** + * Set how NamedLists should be formatted in the response data. This mainly effects + * the facet counts format. + * + * @param string $namedListTreatment + * @throws Apache_Solr_InvalidArgumentException If invalid option is set + */ + public function setNamedListTreatment($namedListTreatment) + { + switch ((string) $namedListTreatment) + { + case Apache_Solr_Service::NAMED_LIST_FLAT: + $this->_namedListTreatment = Apache_Solr_Service::NAMED_LIST_FLAT; + break; + + case Apache_Solr_Service::NAMED_LIST_MAP: + $this->_namedListTreatment = Apache_Solr_Service::NAMED_LIST_MAP; + break; + + default: + throw new Apache_Solr_InvalidArgumentException('Not a valid named list treatement option'); + } + } + + /** + * Get the current setting for named list treatment. + * + * @return string + */ + public function getNamedListTreatment() + { + return $this->_namedListTreatment; + } + + /** + * Set the string used to separate the path form the query string. + * Defaulted to '?' + * + * @param string $queryDelimiter + */ + public function setQueryDelimiter($queryDelimiter) + { + $this->_queryDelimiter = $queryDelimiter; + } + + /** + * Set the string used to separate the parameters in thequery string + * Defaulted to '&' + * + * @param string $queryStringDelimiter + */ + public function setQueryStringDelimiter($queryStringDelimiter) + { + $this->_queryStringDelimiter = $queryStringDelimiter; + } + + /** + * Call the /admin/ping servlet, can be used to quickly tell if a connection to the + * server is able to be made. + * + * @param float $timeout maximum time to wait for ping in seconds, -1 for unlimited (default is 2) + * @return float Actual time taken to ping the server, FALSE if timeout or HTTP error status occurs + */ + public function ping($timeout = 2) + { + $start = microtime(true); + + $httpTransport = $this->getHttpTransport(); + + $httpResponse = $httpTransport->performHeadRequest($this->_pingUrl, $timeout); + $solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays); + + if ($solrResponse->getHttpStatus() == 200) + { + return microtime(true) - $start; + } + else + { + return false; + } + } + + /** + * Call the /admin/threads servlet and retrieve information about all threads in the + * Solr servlet's thread group. Useful for diagnostics. + * + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function threads() + { + return $this->_sendRawGet($this->_threadsUrl); + } + + /** + * Raw Add Method. Takes a raw post body and sends it to the update service. Post body + * should be a complete and well formed "add" xml document. + * + * @param string $rawPost + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function add($rawPost) + { + return $this->_sendRawPost($this->_updateUrl, $rawPost); + } + + /** + * Add a Solr Document to the index + * + * @param Apache_Solr_Document $document + * @param boolean $allowDups + * @param boolean $overwritePending + * @param boolean $overwriteCommitted + * @param integer $commitWithin The number of milliseconds that a document must be committed within, see @{link http://wiki.apache.org/solr/UpdateXmlMessages#The_Update_Schema} for details. If left empty this property will not be set in the request. + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function addDocument(Apache_Solr_Document $document, $allowDups = false, $overwritePending = true, $overwriteCommitted = true, $commitWithin = 0) + { + $dupValue = $allowDups ? 'true' : 'false'; + $pendingValue = $overwritePending ? 'true' : 'false'; + $committedValue = $overwriteCommitted ? 'true' : 'false'; + + $commitWithin = (int) $commitWithin; + $commitWithinString = $commitWithin > 0 ? " commitWithin=\"{$commitWithin}\"" : ''; + + // TODO|solr4 + //$rawPost = ""; + $rawPost = ""; + $rawPost .= $this->_documentToXmlFragment($document); + $rawPost .= ''; + + return $this->add($rawPost); + } + + /** + * Add an array of Solr Documents to the index all at once + * + * @param array $documents Should be an array of Apache_Solr_Document instances + * @param boolean $allowDups + * @param boolean $overwritePending + * @param boolean $overwriteCommitted + * @param integer $commitWithin The number of milliseconds that a document must be committed within, see @{link http://wiki.apache.org/solr/UpdateXmlMessages#The_Update_Schema} for details. If left empty this property will not be set in the request. + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function addDocuments($documents, $allowDups = false, $overwritePending = true, $overwriteCommitted = true, $commitWithin = 0) + { + $dupValue = $allowDups ? 'true' : 'false'; + $pendingValue = $overwritePending ? 'true' : 'false'; + $committedValue = $overwriteCommitted ? 'true' : 'false'; + + $commitWithin = (int) $commitWithin; + $commitWithinString = $commitWithin > 0 ? " commitWithin=\"{$commitWithin}\"" : ''; + + // TODO|solr4 + //$rawPost = ""; + $rawPost = ""; + + foreach ($documents as $document) + { + if ($document instanceof Apache_Solr_Document) + { + $rawPost .= $this->_documentToXmlFragment($document); + } + } + + $rawPost .= ''; + + return $this->add($rawPost); + } + + /** + * Create an XML fragment from a {@link Apache_Solr_Document} instance appropriate for use inside a Solr add call + * + * @return string + */ + protected function _documentToXmlFragment(Apache_Solr_Document $document) + { + $xml = 'getBoost() !== false) + { + $xml .= ' boost="' . $document->getBoost() . '"'; + } + + $xml .= '>'; + + foreach ($document as $key => $value) + { + $key = htmlspecialchars($key, ENT_QUOTES, 'UTF-8'); + $fieldBoost = $document->getFieldBoost($key); + + if (is_array($value)) + { + foreach ($value as $multivalue) + { + $xml .= ''; + } + } + else + { + $xml .= ''; + } + } + + $xml .= ''; + + // replace any control characters to avoid Solr XML parser exception + return $this->_stripCtrlChars($xml); + } + + /** + * Replace control (non-printable) characters from string that are invalid to Solr's XML parser with a space. + * + * @param string $string + * @return string + */ + protected function _stripCtrlChars($string) + { + // See: http://w3.org/International/questions/qa-forms-utf-8.html + // Printable utf-8 does not include any of these chars below x7F + return preg_replace('@[\x00-\x08\x0B\x0C\x0E-\x1F]@', ' ', $string); + } + + /** + * Send a commit command. Will be synchronous unless both wait parameters are set to false. + * + * @param boolean $expungeDeletes Defaults to false, merge segments with deletes away + * @param boolean $waitSearcher Defaults to true, block until a new searcher is opened and registered as the main query searcher, making the changes visible + * @param float $timeout Maximum expected duration (in seconds) of the commit operation on the server (otherwise, will throw a communication exception). Defaults to 1 hour + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function commit($expungeDeletes = false, $waitSearcher = true, $timeout = 3600) + { + $expungeValue = $expungeDeletes ? 'true' : 'false'; + $searcherValue = $waitSearcher ? 'true' : 'false'; + + $rawPost = ''; + + return $this->_sendRawPost($this->_updateUrl, $rawPost, $timeout); + } + + /** + * Raw Delete Method. Takes a raw post body and sends it to the update service. Body should be + * a complete and well formed "delete" xml document + * + * @param string $rawPost Expected to be utf-8 encoded xml document + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function delete($rawPost, $timeout = 3600) + { + return $this->_sendRawPost($this->_updateUrl, $rawPost, $timeout); + } + + /** + * Create a delete document based on document ID + * + * @param string $id Expected to be utf-8 encoded + * @param boolean $fromPending + * @param boolean $fromCommitted + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function deleteById($id, $fromPending = true, $fromCommitted = true, $timeout = 3600) + { + $pendingValue = $fromPending ? 'true' : 'false'; + $committedValue = $fromCommitted ? 'true' : 'false'; + + //escape special xml characters + $id = htmlspecialchars($id, ENT_NOQUOTES, 'UTF-8'); + + // TODO|solr4 + //$rawPost = '' . $id . ''; + $rawPost = '' . $id . ''; + + return $this->delete($rawPost, $timeout); + } + + /** + * Create and post a delete document based on multiple document IDs. + * + * @param array $ids Expected to be utf-8 encoded strings + * @param boolean $fromPending + * @param boolean $fromCommitted + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function deleteByMultipleIds($ids, $fromPending = true, $fromCommitted = true, $timeout = 3600) + { + $pendingValue = $fromPending ? 'true' : 'false'; + $committedValue = $fromCommitted ? 'true' : 'false'; + + // TODO|solr4 + //$rawPost = ''; + $rawPost = ''; + + foreach ($ids as $id) + { + //escape special xml characters + $id = htmlspecialchars($id, ENT_NOQUOTES, 'UTF-8'); + + $rawPost .= '' . $id . ''; + } + + $rawPost .= ''; + + return $this->delete($rawPost, $timeout); + } + + /** + * Create a delete document based on a query and submit it + * + * @param string $rawQuery Expected to be utf-8 encoded + * @param boolean $fromPending + * @param boolean $fromCommitted + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function deleteByQuery($rawQuery, $fromPending = true, $fromCommitted = true, $timeout = 3600) + { + $pendingValue = $fromPending ? 'true' : 'false'; + $committedValue = $fromCommitted ? 'true' : 'false'; + + // escape special xml characters + $rawQuery = htmlspecialchars($rawQuery, ENT_NOQUOTES, 'UTF-8'); + + // TODO|solr4 + //$rawPost = '' . $rawQuery . ''; + $rawPost = '' . $rawQuery . ''; + + return $this->delete($rawPost, $timeout); + } + + /** + * Use Solr Cell to extract document contents. See {@link http://wiki.apache.org/solr/ExtractingRequestHandler} for information on how + * to use Solr Cell and what parameters are available. + * + * NOTE: when passing an Apache_Solr_Document instance, field names and boosts will automatically be prepended by "literal." and "boost." + * as appropriate. Any keys from the $params array will NOT be treated this way. Any mappings from the document will overwrite key / value + * pairs in the params array if they have the same name (e.g. you pass a "literal.id" key and value in your $params array but you also + * pass in a document isntance with an "id" field" - the document's value(s) will take precedence). + * + * @param string $file Path to file to extract data from + * @param array $params optional array of key value pairs that will be sent with the post (see Solr Cell documentation) + * @param Apache_Solr_Document $document optional document that will be used to generate post parameters (literal.* and boost.* params) + * @param string $mimetype optional mimetype specification (for the file being extracted) + * + * @return Apache_Solr_Response + * + * @throws Apache_Solr_InvalidArgumentException if $file, $params, or $document are invalid. + */ + public function extract($file, $params = array(), $document = null, $mimetype = 'application/octet-stream') + { + // check if $params is an array (allow null for default empty array) + if (!is_null($params)) + { + if (!is_array($params)) + { + throw new Apache_Solr_InvalidArgumentException("\$params must be a valid array or null"); + } + } + else + { + $params = array(); + } + + // if $file is an http request, defer to extractFromUrl instead + if (substr($file, 0, 7) == 'http://' || substr($file, 0, 8) == 'https://') + { + return $this->extractFromUrl($file, $params, $document, $mimetype); + } + + // read the contents of the file + $contents = @file_get_contents($file); + + if ($contents !== false) + { + // add the resource.name parameter if not specified + if (!isset($params['resource.name'])) + { + $params['resource.name'] = basename($file); + } + + // delegate the rest to extractFromString + return $this->extractFromString($contents, $params, $document, $mimetype); + } + else + { + throw new Apache_Solr_InvalidArgumentException("File '{$file}' is empty or could not be read"); + } + } + + /** + * Use Solr Cell to extract document contents. See {@link http://wiki.apache.org/solr/ExtractingRequestHandler} for information on how + * to use Solr Cell and what parameters are available. + * + * NOTE: when passing an Apache_Solr_Document instance, field names and boosts will automatically be prepended by "literal." and "boost." + * as appropriate. Any keys from the $params array will NOT be treated this way. Any mappings from the document will overwrite key / value + * pairs in the params array if they have the same name (e.g. you pass a "literal.id" key and value in your $params array but you also + * pass in a document isntance with an "id" field" - the document's value(s) will take precedence). + * + * @param string $data Data that will be passed to Solr Cell + * @param array $params optional array of key value pairs that will be sent with the post (see Solr Cell documentation) + * @param Apache_Solr_Document $document optional document that will be used to generate post parameters (literal.* and boost.* params) + * @param string $mimetype optional mimetype specification (for the file being extracted) + * + * @return Apache_Solr_Response + * + * @throws Apache_Solr_InvalidArgumentException if $file, $params, or $document are invalid. + * + * @todo Should be using multipart/form-data to post parameter values, but I could not get my implementation to work. Needs revisisted. + */ + public function extractFromString($data, $params = array(), $document = null, $mimetype = 'application/octet-stream') + { + // check if $params is an array (allow null for default empty array) + if (!is_null($params)) + { + if (!is_array($params)) + { + throw new Apache_Solr_InvalidArgumentException("\$params must be a valid array or null"); + } + } + else + { + $params = array(); + } + + // make sure we receive our response in JSON and have proper name list treatment + $params['wt'] = self::SOLR_WRITER; + $params['json.nl'] = $this->_namedListTreatment; + + // check if $document is an Apache_Solr_Document instance + if (!is_null($document) && $document instanceof Apache_Solr_Document) + { + // iterate document, adding literal.* and boost.* fields to $params as appropriate + foreach ($document as $field => $fieldValue) + { + // check if we need to add a boost.* parameters + $fieldBoost = $document->getFieldBoost($field); + + if ($fieldBoost !== false) + { + $params["boost.{$field}"] = $fieldBoost; + } + + // add the literal.* parameter + $params["literal.{$field}"] = $fieldValue; + } + } + + // params will be sent to SOLR in the QUERY STRING + $queryString = $this->_generateQueryString($params); + + // the file contents will be sent to SOLR as the POST BODY - we use application/octect-stream as default mimetype + return $this->_sendRawPost($this->_extractUrl . $this->_queryDelimiter . $queryString, $data, false, $mimetype); + } + + /** + * Use Solr Cell to extract document contents. See {@link http://wiki.apache.org/solr/ExtractingRequestHandler} for information on how + * to use Solr Cell and what parameters are available. + * + * NOTE: when passing an Apache_Solr_Document instance, field names and boosts will automatically be prepended by "literal." and "boost." + * as appropriate. Any keys from the $params array will NOT be treated this way. Any mappings from the document will overwrite key / value + * pairs in the params array if they have the same name (e.g. you pass a "literal.id" key and value in your $params array but you also + * pass in a document isntance with an "id" field" - the document's value(s) will take precedence). + * + * @param string $url URL + * @param array $params optional array of key value pairs that will be sent with the post (see Solr Cell documentation) + * @param Apache_Solr_Document $document optional document that will be used to generate post parameters (literal.* and boost.* params) + * @param string $mimetype optional mimetype specification (for the file being extracted) + * + * @return Apache_Solr_Response + * + * @throws Apache_Solr_InvalidArgumentException if $url, $params, or $document are invalid. + */ + public function extractFromUrl($url, $params = array(), $document = null, $mimetype = 'application/octet-stream') + { + // check if $params is an array (allow null for default empty array) + if (!is_null($params)) + { + if (!is_array($params)) + { + throw new Apache_Solr_InvalidArgumentException("\$params must be a valid array or null"); + } + } + else + { + $params = array(); + } + + $httpTransport = $this->getHttpTransport(); + + // read the contents of the URL using our configured Http Transport and default timeout + $httpResponse = $httpTransport->performGetRequest($url); + + // check that its a 200 response + if ($httpResponse->getStatusCode() == 200) + { + // add the resource.name parameter if not specified + if (!isset($params['resource.name'])) + { + $params['resource.name'] = $url; + } + + // delegate the rest to extractFromString + return $this->extractFromString($httpResponse->getBody(), $params, $document, $mimetype); + } + else + { + throw new Apache_Solr_InvalidArgumentException("URL '{$url}' returned non 200 response code"); + } + } + + /** + * Send an optimize command. Will be synchronous unless both wait parameters are set + * to false. + * + * @param boolean $waitSearcher + * @param float $timeout Maximum expected duration of the commit operation on the server (otherwise, will throw a communication exception) + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function optimize($waitSearcher = true, $timeout = 3600) + { + $searcherValue = $waitSearcher ? 'true' : 'false'; + + $rawPost = ''; + + return $this->_sendRawPost($this->_updateUrl, $rawPost, $timeout); + } + + /** + * Simple Search interface + * + * @param string $query The raw query string + * @param int $offset The starting offset for result documents + * @param int $limit The maximum number of result documents to return + * @param array $params key / value pairs for other query parameters (see Solr documentation), use arrays for parameter keys used more than once (e.g. facet.field) + * @param string $method The HTTP method (Apache_Solr_Service::METHOD_GET or Apache_Solr_Service::METHOD::POST) + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + * @throws Apache_Solr_InvalidArgumentException If an invalid HTTP method is used + */ + public function search($query, $offset = 0, $limit = 10, $params = array(), $method = self::METHOD_GET) + { + // ensure params is an array + if (!is_null($params)) + { + if (!is_array($params)) + { + // params was specified but was not an array - invalid + throw new Apache_Solr_InvalidArgumentException("\$params must be a valid array or null"); + } + } + else + { + $params = array(); + } + + // construct our full parameters + + // common parameters in this interface + $params['wt'] = self::SOLR_WRITER; + $params['json.nl'] = $this->_namedListTreatment; + + $params['q'] = $query; + $params['start'] = $offset; + $params['rows'] = $limit; + + $queryString = $this->_generateQueryString($params); + + if ($method == self::METHOD_GET) + { + return $this->_sendRawGet($this->_searchUrl . $this->_queryDelimiter . $queryString); + } + else if ($method == self::METHOD_POST) + { + return $this->_sendRawPost($this->_searchUrl, $queryString, FALSE, 'application/x-www-form-urlencoded; charset=UTF-8'); + } + else + { + throw new Apache_Solr_InvalidArgumentException("Unsupported method '$method', please use the Apache_Solr_Service::METHOD_* constants"); + } + } +} diff --git a/lib/solr-php-client/Service/Balancer.php b/lib/solr-php-client/Service/Balancer.php new file mode 100755 index 0000000..cad57e5 --- /dev/null +++ b/lib/solr-php-client/Service/Balancer.php @@ -0,0 +1,914 @@ +, Dan Wolfe + */ + +// See Issue #1 (http://code.google.com/p/solr-php-client/issues/detail?id=1) +// Doesn't follow typical include path conventions, but is more convenient for users +require_once(dirname(dirname(__FILE__)) . '/Service.php'); + +require_once(dirname(dirname(__FILE__)) . '/NoServiceAvailableException.php'); + +/** + * Reference Implementation for using multiple Solr services in a distribution. Functionality + * includes: + * routing of read / write operations + * failover (on selection) for multiple read servers + */ +class Apache_Solr_Service_Balancer +{ + /** + * SVN Revision meta data for this class + */ + const SVN_REVISION = '$Revision: 54 $'; + + /** + * SVN ID meta data for this class + */ + const SVN_ID = '$Id: Balancer.php 54 2011-02-04 16:29:18Z donovan.jimenez $'; + + protected $_createDocuments = true; + + protected $_readableServices = array(); + protected $_writeableServices = array(); + + protected $_currentReadService = null; + protected $_currentWriteService = null; + + protected $_readPingTimeout = 2; + protected $_writePingTimeout = 4; + + // Configuration for server selection backoff intervals + protected $_useBackoff = false; // Set to true to use more resillient write server selection + protected $_backoffLimit = 600; // 10 minute default maximum + protected $_backoffEscalation = 2.0; // Rate at which to increase backoff period + protected $_defaultBackoff = 2.0; // Default backoff interval + + /** + * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc. + * + * NOTE: inside a phrase fewer characters need escaped, use {@link Apache_Solr_Service::escapePhrase()} instead + * + * @param string $value + * @return string + */ + static public function escape($value) + { + return Apache_Solr_Service::escape($value); + } + + /** + * Escape a value meant to be contained in a phrase for special query characters + * + * @param string $value + * @return string + */ + static public function escapePhrase($value) + { + return Apache_Solr_Service::escapePhrase($value); + } + + /** + * Convenience function for creating phrase syntax from a value + * + * @param string $value + * @return string + */ + static public function phrase($value) + { + return Apache_Solr_Service::phrase($value); + } + + /** + * Constructor. Takes arrays of read and write service instances or descriptions + * + * @param array $readableServices + * @param array $writeableServices + */ + public function __construct($readableServices = array(), $writeableServices = array()) + { + //setup readable services + foreach ($readableServices as $service) + { + $this->addReadService($service); + } + + //setup writeable services + foreach ($writeableServices as $service) + { + $this->addWriteService($service); + } + } + + public function setReadPingTimeout($timeout) + { + $this->_readPingTimeout = $timeout; + } + + public function setWritePingTimeout($timeout) + { + $this->_writePingTimeout = $timeout; + } + + public function setUseBackoff($enable) + { + $this->_useBackoff = $enable; + } + + /** + * Generates a service ID + * + * @param string $host + * @param integer $port + * @param string $path + * @return string + */ + protected function _getServiceId($host, $port, $path) + { + return $host . ':' . $port . $path; + } + + /** + * Adds a service instance or service descriptor (if it is already + * not added) + * + * @param mixed $service + * + * @throws Apache_Solr_InvalidArgumentException If service descriptor is not valid + */ + public function addReadService($service) + { + if ($service instanceof Apache_Solr_Service) + { + $id = $this->_getServiceId($service->getHost(), $service->getPort(), $service->getPath()); + + $this->_readableServices[$id] = $service; + } + else if (is_array($service)) + { + if (isset($service['host']) && isset($service['port']) && isset($service['path'])) + { + $id = $this->_getServiceId((string)$service['host'], (int)$service['port'], (string)$service['path']); + + $this->_readableServices[$id] = $service; + } + else + { + throw new Apache_Solr_InvalidArgumentException('A Readable Service description array does not have all required elements of host, port, and path'); + } + } + } + + /** + * Removes a service instance or descriptor from the available services + * + * @param mixed $service + * + * @throws Apache_Solr_InvalidArgumentException If service descriptor is not valid + */ + public function removeReadService($service) + { + $id = ''; + + if ($service instanceof Apache_Solr_Service) + { + $id = $this->_getServiceId($service->getHost(), $service->getPort(), $service->getPath()); + } + else if (is_array($service)) + { + if (isset($service['host']) && isset($service['port']) && isset($service['path'])) + { + $id = $this->_getServiceId((string)$service['host'], (int)$service['port'], (string)$service['path']); + } + else + { + throw new Apache_Solr_InvalidArgumentException('A Readable Service description array does not have all required elements of host, port, and path'); + } + } + else if (is_string($service)) + { + $id = $service; + } + + if ($id && isset($this->_readableServices[$id])) + { + unset($this->_readableServices[$id]); + } + } + + /** + * Adds a service instance or service descriptor (if it is already + * not added) + * + * @param mixed $service + * + * @throws Apache_Solr_InvalidArgumentException If service descriptor is not valid + */ + public function addWriteService($service) + { + if ($service instanceof Apache_Solr_Service) + { + $id = $this->_getServiceId($service->getHost(), $service->getPort(), $service->getPath()); + + $this->_writeableServices[$id] = $service; + } + else if (is_array($service)) + { + if (isset($service['host']) && isset($service['port']) && isset($service['path'])) + { + $id = $this->_getServiceId((string)$service['host'], (int)$service['port'], (string)$service['path']); + + $this->_writeableServices[$id] = $service; + } + else + { + throw new Apache_Solr_InvalidArgumentException('A Writeable Service description array does not have all required elements of host, port, and path'); + } + } + } + + /** + * Removes a service instance or descriptor from the available services + * + * @param mixed $service + * + * @throws Apache_Solr_InvalidArgumentException If service descriptor is not valid + */ + public function removeWriteService($service) + { + $id = ''; + + if ($service instanceof Apache_Solr_Service) + { + $id = $this->_getServiceId($service->getHost(), $service->getPort(), $service->getPath()); + } + else if (is_array($service)) + { + if (isset($service['host']) && isset($service['port']) && isset($service['path'])) + { + $id = $this->_getServiceId((string)$service['host'], (int)$service['port'], (string)$service['path']); + } + else + { + throw new Apache_Solr_InvalidArgumentException('A Readable Service description array does not have all required elements of host, port, and path'); + } + } + else if (is_string($service)) + { + $id = $service; + } + + if ($id && isset($this->_writeableServices[$id])) + { + unset($this->_writeableServices[$id]); + } + } + + /** + * Iterate through available read services and select the first with a ping + * that satisfies configured timeout restrictions (or the default) + * + * @return Apache_Solr_Service + * + * @throws Apache_Solr_NoServiceAvailableException If there are no read services that meet requirements + */ + protected function _selectReadService($forceSelect = false) + { + if (!$this->_currentReadService || !isset($this->_readableServices[$this->_currentReadService]) || $forceSelect) + { + if ($this->_currentReadService && isset($this->_readableServices[$this->_currentReadService]) && $forceSelect) + { + // we probably had a communication error, ping the current read service, remove it if it times out + if ($this->_readableServices[$this->_currentReadService]->ping($this->_readPingTimeout) === false) + { + $this->removeReadService($this->_currentReadService); + } + } + + if (count($this->_readableServices)) + { + // select one of the read services at random + $ids = array_keys($this->_readableServices); + + $id = $ids[rand(0, count($ids) - 1)]; + $service = $this->_readableServices[$id]; + + if (is_array($service)) + { + //convert the array definition to a client object + $service = new Apache_Solr_Service($service['host'], $service['port'], $service['path']); + $this->_readableServices[$id] = $service; + } + + $service->setCreateDocuments($this->_createDocuments); + $this->_currentReadService = $id; + } + else + { + throw new Apache_Solr_NoServiceAvailableException('No read services were available'); + } + } + + return $this->_readableServices[$this->_currentReadService]; + } + + /** + * Iterate through available write services and select the first with a ping + * that satisfies configured timeout restrictions (or the default) + * + * @return Apache_Solr_Service + * + * @throws Apache_Solr_NoServiceAvailableException If there are no write services that meet requirements + */ + protected function _selectWriteService($forceSelect = false) + { + if($this->_useBackoff) + { + return $this->_selectWriteServiceSafe($forceSelect); + } + + if (!$this->_currentWriteService || !isset($this->_writeableServices[$this->_currentWriteService]) || $forceSelect) + { + if ($this->_currentWriteService && isset($this->_writeableServices[$this->_currentWriteService]) && $forceSelect) + { + // we probably had a communication error, ping the current read service, remove it if it times out + if ($this->_writeableServices[$this->_currentWriteService]->ping($this->_writePingTimeout) === false) + { + $this->removeWriteService($this->_currentWriteService); + } + } + + if (count($this->_writeableServices)) + { + // select one of the read services at random + $ids = array_keys($this->_writeableServices); + + $id = $ids[rand(0, count($ids) - 1)]; + $service = $this->_writeableServices[$id]; + + if (is_array($service)) + { + //convert the array definition to a client object + $service = new Apache_Solr_Service($service['host'], $service['port'], $service['path']); + $this->_writeableServices[$id] = $service; + } + + $this->_currentWriteService = $id; + } + else + { + throw new Apache_Solr_NoServiceAvailableException('No write services were available'); + } + } + + return $this->_writeableServices[$this->_currentWriteService]; + } + + /** + * Iterate through available write services and select the first with a ping + * that satisfies configured timeout restrictions (or the default). The + * timeout period will increase until a connection is made or the limit is + * reached. This will allow for increased reliability with heavily loaded + * server(s). + * + * @return Apache_Solr_Service + * + * @throws Apache_Solr_NoServiceAvailableException If there are no write services that meet requirements + */ + + protected function _selectWriteServiceSafe($forceSelect = false) + { + if (!$this->_currentWriteService || !isset($this->_writeableServices[$this->_currentWriteService]) || $forceSelect) + { + if (count($this->_writeableServices)) + { + $backoff = $this->_defaultBackoff; + + do { + // select one of the read services at random + $ids = array_keys($this->_writeableServices); + + $id = $ids[rand(0, count($ids) - 1)]; + $service = $this->_writeableServices[$id]; + + if (is_array($service)) + { + //convert the array definition to a client object + $service = new Apache_Solr_Service($service['host'], $service['port'], $service['path']); + $this->_writeableServices[$id] = $service; + } + + $this->_currentWriteService = $id; + + $backoff *= $this->_backoffEscalation; + + if($backoff > $this->_backoffLimit) + { + throw new Apache_Solr_NoServiceAvailableException('No write services were available. All timeouts exceeded.'); + } + + } while($this->_writeableServices[$this->_currentWriteService]->ping($backoff) === false); + } + else + { + throw new Apache_Solr_NoServiceAvailableException('No write services were available'); + } + } + + return $this->_writeableServices[$this->_currentWriteService]; + } + + /** + * Set the create documents flag. This determines whether {@link Apache_Solr_Response} objects will + * parse the response and create {@link Apache_Solr_Document} instances in place. + * + * @param boolean $createDocuments + */ + public function setCreateDocuments($createDocuments) + { + $this->_createDocuments = (bool) $createDocuments; + + // set on current read service + if ($this->_currentReadService) + { + $service = $this->_selectReadService(); + $service->setCreateDocuments($createDocuments); + } + } + + /** + * Get the current state of teh create documents flag. + * + * @return boolean + */ + public function getCreateDocuments() + { + return $this->_createDocuments; + } + + /** + * Raw Add Method. Takes a raw post body and sends it to the update service. Post body + * should be a complete and well formed "add" xml document. + * + * @param string $rawPost + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function add($rawPost) + { + $service = $this->_selectWriteService(); + + do + { + try + { + return $service->add($rawPost); + } + catch (Apache_Solr_HttpTransportException $e) + { + if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR + { + throw $e; + } + } + + $service = $this->_selectWriteService(true); + } while ($service); + + return false; + } + + /** + * Add a Solr Document to the index + * + * @param Apache_Solr_Document $document + * @param boolean $allowDups + * @param boolean $overwritePending + * @param boolean $overwriteCommitted + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function addDocument(Apache_Solr_Document $document, $allowDups = false, $overwritePending = true, $overwriteCommitted = true) + { + $service = $this->_selectWriteService(); + + do + { + try + { + return $service->addDocument($document, $allowDups, $overwritePending, $overwriteCommitted); + } + catch (Apache_Solr_HttpTransportException $e) + { + if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR + { + throw $e; + } + } + + $service = $this->_selectWriteService(true); + } while ($service); + + return false; + } + + /** + * Add an array of Solr Documents to the index all at once + * + * @param array $documents Should be an array of Apache_Solr_Document instances + * @param boolean $allowDups + * @param boolean $overwritePending + * @param boolean $overwriteCommitted + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function addDocuments($documents, $allowDups = false, $overwritePending = true, $overwriteCommitted = true) + { + $service = $this->_selectWriteService(); + + do + { + try + { + return $service->addDocuments($documents, $allowDups, $overwritePending, $overwriteCommitted); + } + catch (Apache_Solr_HttpTransportException $e) + { + if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR + { + throw $e; + } + } + + $service = $this->_selectWriteService(true); + } while ($service); + + return false; + } + + /** + * Send a commit command. Will be synchronous unless both wait parameters are set + * to false. + * + * @param boolean $waitFlush + * @param boolean $waitSearcher + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function commit($optimize = true, $waitFlush = true, $waitSearcher = true, $timeout = 3600) + { + $service = $this->_selectWriteService(); + + do + { + try + { + return $service->commit($optimize, $waitFlush, $waitSearcher, $timeout); + } + catch (Apache_Solr_HttpTransportException $e) + { + if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR + { + throw $e; + } + } + + $service = $this->_selectWriteService(true); + } while ($service); + + return false; + } + + /** + * Raw Delete Method. Takes a raw post body and sends it to the update service. Body should be + * a complete and well formed "delete" xml document + * + * @param string $rawPost + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function delete($rawPost, $timeout = 3600) + { + $service = $this->_selectWriteService(); + + do + { + try + { + return $service->delete($rawPost, $timeout); + } + catch (Apache_Solr_HttpTransportException $e) + { + if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR + { + throw $e; + } + } + + $service = $this->_selectWriteService(true); + } while ($service); + + return false; + } + + /** + * Create a delete document based on document ID + * + * @param string $id + * @param boolean $fromPending + * @param boolean $fromCommitted + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function deleteById($id, $fromPending = true, $fromCommitted = true, $timeout = 3600) + { + $service = $this->_selectWriteService(); + + do + { + try + { + return $service->deleteById($id, $fromPending, $fromCommitted, $timeout); + } + catch (Apache_Solr_HttpTransportException $e) + { + if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR + { + throw $e; + } + } + + $service = $this->_selectWriteService(true); + } while ($service); + + return false; + } + + /** + * Create and post a delete document based on multiple document IDs. + * + * @param array $ids Expected to be utf-8 encoded strings + * @param boolean $fromPending + * @param boolean $fromCommitted + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function deleteByMultipleIds($ids, $fromPending = true, $fromCommitted = true, $timeout = 3600) + { + $service = $this->_selectWriteService(); + + do + { + try + { + return $service->deleteByMultipleId($ids, $fromPending, $fromCommitted, $timeout); + } + catch (Apache_Solr_HttpTransportException $e) + { + if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR + { + throw $e; + } + } + + $service = $this->_selectWriteService(true); + } while ($service); + + return false; + } + + /** + * Create a delete document based on a query and submit it + * + * @param string $rawQuery + * @param boolean $fromPending + * @param boolean $fromCommitted + * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception) + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function deleteByQuery($rawQuery, $fromPending = true, $fromCommitted = true, $timeout = 3600) + { + $service = $this->_selectWriteService(); + + do + { + try + { + return $service->deleteByQuery($rawQuery, $fromPending, $fromCommitted, $timeout); + } + catch (Apache_Solr_HttpTransportException $e) + { + if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR + { + throw $e; + } + } + + $service = $this->_selectWriteService(true); + } while ($service); + + return false; + } + + /** + * Use Solr Cell to extract document contents. See {@link http://wiki.apache.org/solr/ExtractingRequestHandler} for information on how + * to use Solr Cell and what parameters are available. + * + * NOTE: when passing an Apache_Solr_Document instance, field names and boosts will automatically be prepended by "literal." and "boost." + * as appropriate. Any keys from the $params array will NOT be treated this way. Any mappings from the document will overwrite key / value + * pairs in the params array if they have the same name (e.g. you pass a "literal.id" key and value in your $params array but you also + * pass in a document isntance with an "id" field" - the document's value(s) will take precedence). + * + * @param string $file Path to file to extract data from + * @param array $params optional array of key value pairs that will be sent with the post (see Solr Cell documentation) + * @param Apache_Solr_Document $document optional document that will be used to generate post parameters (literal.* and boost.* params) + * @param string $mimetype optional mimetype specification (for the file being extracted) + * + * @return Apache_Solr_Response + * + * @throws Apache_Solr_InvalidArgumentException if $file, $params, or $document are invalid. + */ + public function extract($file, $params = array(), $document = null, $mimetype = 'application/octet-stream') + { + $service = $this->_selectWriteService(); + + do + { + try + { + return $service->extract($file, $params, $document, $mimetype); + } + catch (Apache_Solr_HttpTransportException $e) + { + if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR + { + throw $e; + } + } + + $service = $this->_selectWriteService(true); + } while ($service); + + return false; + } + + /** + * Use Solr Cell to extract document contents. See {@link http://wiki.apache.org/solr/ExtractingRequestHandler} for information on how + * to use Solr Cell and what parameters are available. + * + * NOTE: when passing an Apache_Solr_Document instance, field names and boosts will automatically be prepended by "literal." and "boost." + * as appropriate. Any keys from the $params array will NOT be treated this way. Any mappings from the document will overwrite key / value + * pairs in the params array if they have the same name (e.g. you pass a "literal.id" key and value in your $params array but you also + * pass in a document isntance with an "id" field" - the document's value(s) will take precedence). + * + * @param string $data Data that will be passed to Solr Cell + * @param array $params optional array of key value pairs that will be sent with the post (see Solr Cell documentation) + * @param Apache_Solr_Document $document optional document that will be used to generate post parameters (literal.* and boost.* params) + * @param string $mimetype optional mimetype specification (for the file being extracted) + * + * @return Apache_Solr_Response + * + * @throws Apache_Solr_InvalidArgumentException if $file, $params, or $document are invalid. + * + * @todo Should be using multipart/form-data to post parameter values, but I could not get my implementation to work. Needs revisisted. + */ + public function extractFromString($data, $params = array(), $document = null, $mimetype = 'application/octet-stream') + { + $service = $this->_selectWriteService(); + + do + { + try + { + return $service->extractFromString($data, $params, $document, $mimetype); + } + catch (Apache_Solr_HttpTransportException $e) + { + if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR + { + throw $e; + } + } + + $service = $this->_selectWriteService(true); + } while ($service); + + return false; + } + + /** + * Send an optimize command. Will be synchronous unless both wait parameters are set + * to false. + * + * @param boolean $waitFlush + * @param boolean $waitSearcher + * @param float $timeout Maximum expected duration of the optimize operation on the server (otherwise, will throw a communication exception) + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function optimize($waitFlush = true, $waitSearcher = true, $timeout = 3600) + { + $service = $this->_selectWriteService(); + + do + { + try + { + return $service->optimize($waitFlush, $waitSearcher, $timeout); + } + catch (Apache_Solr_HttpTransportException $e) + { + if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR + { + throw $e; + } + } + + $service = $this->_selectWriteService(true); + } while ($service); + + return false; + } + + /** + * Simple Search interface + * + * @param string $query The raw query string + * @param int $offset The starting offset for result documents + * @param int $limit The maximum number of result documents to return + * @param array $params key / value pairs for query parameters, use arrays for multivalued parameters + * @param string $method The HTTP method (Apache_Solr_Service::METHOD_GET or Apache_Solr_Service::METHOD::POST) + * @return Apache_Solr_Response + * + * @throws Apache_Solr_HttpTransportException If an error occurs during the service call + */ + public function search($query, $offset = 0, $limit = 10, $params = array(), $method = Apache_Solr_Service::METHOD_GET) + { + $service = $this->_selectReadService(); + + do + { + try + { + return $service->search($query, $offset, $limit, $params, $method); + } + catch (Apache_Solr_HttpTransportException $e) + { + if ($e->getCode() != 0) //IF NOT COMMUNICATION ERROR + { + throw $e; + } + } + + $service = $this->_selectReadService(true); + } while ($service); + + return false; + } +} diff --git a/lib/solr-php-client/Solr/HighlightOpts.php b/lib/solr-php-client/Solr/HighlightOpts.php new file mode 100755 index 0000000..54fbaf0 --- /dev/null +++ b/lib/solr-php-client/Solr/HighlightOpts.php @@ -0,0 +1,931 @@ + array( 'ovr' => false, 'type' => Apache_Solr_SolrOpts::BOOLEAN_PARAM, 'default' => false ), + 'hl.fl' => array( 'ovr' => false, 'type' => Apache_Solr_SolrOpts::CSV_PARAM, 'default' => array() ), + 'hl.snippets' => array( 'ovr' => true, 'type' => Apache_Solr_SolrOpts::NUMERIC_PARAM, 'default' => 1 ), + 'hl.fragsize' => array( 'ovr' => true, 'type' => Apache_Solr_SolrOpts::NUMERIC_PARAM, 'default' => 100 ), + 'hl.mergeContiguous' => array( 'ovr' => true, 'type' => Apache_Solr_SolrOpts::BOOLEAN_PARAM, 'default' => false ), + 'hl.requireFieldMatch' => array( 'ovr' => false, 'type' => Apache_Solr_SolrOpts::BOOLEAN_PARAM, 'default' => false ), + 'hl.maxAnalyzedChars' => array( 'ovr' => false, 'type' => Apache_Solr_SolrOpts::NUMERIC_PARAM, 'default' => 512000 ), + 'hl.alternateField' => array( 'ovr' => true, 'type' => Apache_Solr_SolrOpts::STRING_PARAM, 'default' => '' ), + 'hl.maxAlternateFieldLength' => array( 'ovr' => false, 'type' => Apache_Solr_SolrOpts::NUMERIC_PARAM, 'default' => 0), + 'hl.formatter' => array( 'ovr' => false, 'type' => Apache_Solr_SolrOpts::STRING_PARAM, 'default' => 'simple' ), + 'hl.simple.pre' => array( 'ovr' => true, 'type' => Apache_Solr_SolrOpts::STRING_PARAM, 'default' => '' ), + 'hl.simple.post' => array( 'ovr' => true, 'type' => Apache_Solr_SolrOpts::STRING_PARAM, 'default' => '' ), + 'hl.fragmenter' => array( 'ovr' => true, 'type' => Apache_Solr_SolrOpts::STRING_PARAM, 'default' => 'gap' ), + 'hl.fragListBuilder' => array( 'ovr' => false, 'type' => Apache_Solr_SolrOpts::STRING_PARAM, 'default' => ''), + 'hl.fragmentsBuilder' => array( 'ovr' => false, 'type' => Apache_Solr_SolrOpts::STRING_PARAM, 'default' => ''), + 'hl.useFastVectorHighlighter' => array( 'ovr' => true, 'type' => Apache_Solr_SolrOpts::BOOLEAN_PARAM, 'default' => false), + 'hl.usePhraseHighlighter' => array( 'ovr' => false, 'type' => Apache_Solr_SolrOpts::BOOLEAN_PARAM, 'default' => false), + 'hl.highlightMultiTerm' => array( 'ovr' => false, 'type' => Apache_Solr_SolrOpts::BOOLEAN_PARAM, 'default' => false), + 'hl.regex.slop' => array( 'ovr' => false, 'type' => Apache_Solr_SolrOpts::NUMERIC_PARAM, 'default' => 0.6), + 'hl.regex.pattern' => array( 'ovr' => false, 'type' => Apache_Solr_SolrOpts::STRING_PARAM, 'default' => ''), + 'hl.regex.maxAnalyzedChars' => array ( 'ovr' => false, 'type' => Apache_Solr_SolrOpts::NUMERIC_PARAM, 'default' => 10000) + ); + + /** + * The only constructor for this is no-argument. All it does is set highlighting to be on. + */ + public function __construct() { + // Set highlighting to on. + $this->setParameterValue('hl', true); + } + + /** + * This function allows you to set which fields will be highlighted. + * + * @param array $fields an array that contains the names of the fields you want highlighted + */ + public function setHighlightedFields($fields=array()) + { + // If it is a scalar value, wrap it in an array. + if ( !is_array($fields) ) { + $fields = array($fields); + } + + // And set the value + $this->setParameterValue('hl.fl', $fields); + } + + /** + * This function adds a field to the list of fields to be highlighted. + * + * @param mixed $field_to_add the field(s) that you want added. This can be an array or a single field + */ + public function addHighlightedField($field_to_add) + { + // Get the current list of fields. + $fields = $this->getParameterValue('hl.fl'); + + // Check to see if it already was set + if ( is_array($fields) ) + { + // If the field(s) to add is an array, merge it with the + // previous array + if ( is_array($field_to_add) ) + { + $fields = array_merge($fields, $field_to_add); + } + else + { + // Otherwise just add it. + $fields[] = $field_to_add; + } + } else { + // If it wasn't, this is the first field. + if ( is_array($field_to_add) ) + { + $fields = $field_to_add; + } + else + { + $fields = array( $field ); + } + } + + // Set it back + $this->setParameterValue('hl.fl', $fields); + } + + /** + * This function removes a field from the list of fields to be highlighted. + * + * @param mixed $field_to_add the field(s) that you want added. This can be an array or a single field + */ + public function removeHighlightedField($field_to_remove) + { + // Get the current list of fields + $fields = $this->getParameterValue('hl.fl'); + + // Check to see if it was already set + if ( is_array($fields) ) + { + // If the fields you want to remove are an array + // calculate the difference + if ( is_array($field_to_remove) ) + { + $fields = array_diff($fields, $field_to_remove); + } + else + { + // Otherwise, just unset that particular index + unset($fields[array_search($field_to_remove, $fields)]); + } + } + else + { + $fields = self::$param_info['hl.fl']['default']; + } + + // Set it back + $this->setParameterValue('hl.fl', $fields); + } + + /** + * Get an array of the highlighted fields. + * @return array the list of highlighted fields + */ + public function getHighlightedFields() + { + return $this->getParameterValue('hl.fl'); + } + + /** + * Set the number of snippets to generate for each field. + * + * @param int $num the number of snippets to generate per field + */ + public function setNumSnippets($num) + { + $this->setParameterValue('hl.snippets', $num); + } + + /** + * Get the number of snippets each field is set to generate. + * + * @return the number of snippets + */ + public function getNumSnippets() + { + return $this->getParameterValue('hl.snippets'); + } + + /** + * This function sets the override for a single field. + * @param $field the field to override + * @param $num the number of snippets for that field to generate + */ + public function setNumSnippetsFieldOverride($field, $num) + { + $this->addFieldOverride('hl.snippets', $field, $num); + } + + /** + * This function returns the current value for the override for the + * given field (if it exists). + * + * @param string $field the field you want to know the override of + */ + public function getNumSnippetsFieldOverride($field) + { + return $this->getFieldOverride('hl.snippets', $field); + } + + /** + * Remove a previously set override. + * + * @param string $field the field for which to remove the override + */ + public function removeNumSnippetsFieldOverride($field) + { + $this->removeFieldOverride('hl.snippets', $field); + } + + /** + * Set the size of the fragments that you want the highlighting to be + * split into. Default is 100. 0 means don't fragment. + * @param int $size + */ + public function setFragmentSize($size) + { + $this->setParameterValue('hl.fragsize', $size); + } + + /** + * Get the currently set fragment size. + * + * @return int the currently set fragment size + */ + public function getFragmentSize() + { + return $this->getParameterValue('hl.fragsize'); + } + + /** + * Set the field override for the fragment size. + * @param string $field the field to set the override for + * @param int $size the size to set the field to + */ + public function setFragmentSizeOverride($field, $size) + { + $this->addFieldOverride('hl.fragsize', $field, $size); + } + + /** + * Get the field override for a given field's fragment size. + * @param $field the field to get the override for + */ + public function getFragmentSizeOverride($field) + { + return $this->getFieldOverride('hl.fragsize', $field); + } + + /** + * Remove the fragment size override on a field. + * + * @param string $field the field to remove it for + */ + public function removeFragmentSizeOverride($field) + { + $this->removeFieldOverride('hl.fragsize', $field); + } + + /** + * This setting determines whether or not contiguous fragments should be + * collapsed into a single fragment. + * + * @param boolean $mergeContiguous whether or not do to it + */ + public function setMergeContiguous($mergeContiguous) + { + $this->setParameterValue('hl.mergeContiguous', $mergeContiguous); + } + + /** + * Get the current setting for merge contiguous. + * @return boolean true if it is set to merge contiguous fragments, false if not + */ + public function getMergeContiguous() + { + return $this->getParameterValue('hl.mergeContiguous'); + } + + /** + * Set a field override for merge contiguous. + * + * @param string $field the field to set it for + * @param boolean $value the value to set it to + */ + public function addMergeContiguousFieldOverride($field, $value) + { + $this->addFieldOverride('hl.mergeContiguous', $field, $value); + } + + /** + * Get the field override for merge contiguous. + * + * @param string $field the field whose value you want + * @return boolean the value of the setting for that field + */ + public function getMergeContiguousFieldOverride($field) + { + return $this->getFieldOverride('hl.mergeContiguous', $field); + } + + /** + * Remove a field override for merge contiguous. + * @param string $field the field for which to remove it + */ + public function removeMergeContiguousFieldOverride($field) + { + $this->removeFieldOverride('hl.mergeContiguous', $field); + } + + /** + * If true, then a field will only be highlighted if the query matched in + * this particular field (normally, terms are highlighted in all requested + * fields regardless of which field matched the query). This only takes + * effect if "hl.usePhraseHighlighter" is "true". + * + * The default value is "false". + * + * @param boolean $requireFieldMatch whether or not to require field match + */ + public function setRequireFieldMatch($requireFieldMatch) + { + $this->setParameterValue('hl.requireFieldMatch', $requireFieldMatch); + } + + /** + * Get whether or not a field match is required. + * + * @return boolean true if field match is required, false if not + */ + public function getRequireFieldMatch() + { + return $this->getParameterValue('hl.requireFieldMatch'); + } + + /** + * How many characters into a document to look for suitable snippets. + * + * The default value is "51200". + * + * @param int $max the new max + */ + public function setMaxAnalyzedChars($max) + { + $this->setParameterValue('hl.maxAnalyzedChars',$max); + } + + /** + * Get the max number of characters to analyze. + * @return int the max number of characters to analyze + */ + public function getMaxAnalyzedChars() + { + return $this->getParameterValue('hl.maxAnalyzedChars'); + } + + /** + * If a snippet cannot be generated, this field will be used instead. + * + * The default value is to not have one. + * + * @param string $field the field to use as the alternate field + */ + public function setAlternateField($field) + { + $this->setParameterValue('hl.alternateField', $field); + } + + /** + * Retrieve the field that will be used if a snippet cannot be generated. + * + * @return string the field that will be used + */ + public function getAlternateField() + { + return $this->getParameterValue('hl.alternateField', $field); + } + + /** + * Add an override to the alternate field. + * + * @param string $field_to_override the field whose value you want to override + * @param string $alternate_field the value to set that override to + */ + public function addAlternateFieldOverride($field_to_override, $alternate_field) + { + $this->addFieldOverride('hl.alternateField', $field_to_override, $alternate_field); + } + + /** + * Get the value that the overridden field was overridden to. + * + * @param string $overridden_field the field that was overridden + * @return string the field that the $overridden_field was set to use as its default summary + */ + public function getAlternateFieldOverride($overridden_field) + { + return $this->getFieldOverride('hl.alternateField',$overridden_field); + } + + /** + * Remove a field override from the alternate field. + * + * @param string $overridden_field the field from which you want to remove the override + */ + public function removeAlternateFieldOverride($overridden_field) + { + $this->removeFieldOverride('hl.alternateField', $overridden_field); + } + + /** + * If hl.alternateField is specified, this parameter specifies the maximum + * number of characters of the field to return Solr1.3. Any value less + * than or equal to 0 means unlimited. + * + * The default value is unlimited. + * + * @param int $length the length to restrain the alternate field to + */ + public function setMaxAlternateFieldLength($length) + { + $this->setParameterValue('hl.maxAlternateFieldLength',$length); + } + + /** + * Get the value set for the max length of the alternate field. + * + * @return int the max length of the alternate field + */ + public function getMaxAlternateFieldLength() + { + return $this->getParameterValue('hl.maxAlternateFieldLength'); + } + + /** + * Specify a formatter for the highlight output. Currently the only + * legal value is "simple", which surrounds a highlighted term with + * a customizable pre- and post text snippet. + * + * The default value is "simple". + * + * @param unknown_type $formatter + */ + public function setFormatter($formatter) + { + $this->setParameterValue('hl.formatter', $formatter); + } + + /** + * Get the formatter that is currently set. + * + * @return string the current formatter + */ + public function getFormatter() + { + return $this->getParameterValue('hl.formatter'); + } + + /** + * Override what formatter a certain field uses. + * @param string $field the field to override + * @param string $formatter the formatter for that field to use + */ + public function addFormatterFieldOverride($field, $formatter) + { + $this->addFieldOverride('hl.formatter', $field, $formatter); + } + + /** + * Get the overridden formatter value for a field. + * + * @param string $field the field to get the override for + */ + public function getFormatterFieldOverride($field) + { + return $this->getFieldOverride('hl.formatter',$field); + } + + /** + * Remove an override for a fields formatter. + * + * @param string $field the field to remove the override from + */ + public function removeFormatterFieldOverride($field) + { + $this->removeFieldOverride('hl.formatter', $field); + } + + /** + * Set the text which appears before a highlighted term when + * using the simple formatter. + * + * @param string $value the string to prepend + */ + public function setPreFormat($value) + { + $this->setParameterValue('hl.simple.pre',$value); + } + + /** + * Get what is prepended to highlighted terms. + * + * @return string the value that is prepended to highlighted terms + */ + public function getPreFormat() + { + return $this->getParameterValue('hl.simple.pre'); + } + + /** + * Set a field override for the pre-formatting. + * + * @param string $field the field to override + * @param string $value the value to override it with + */ + public function addPreFormatFieldOverride($field, $value) + { + $this->addFieldOverride('hl.simple.pre', $field, $value); + } + + /** + * Get the field override for pre-formatting for a given field. + * @param string $field the field to get the override for + */ + public function getPreFormatFieldOverride($field) + { + return $this->getFieldOverride('hl.simple.pre', $field); + } + + /** + * Remove the field override for pre-formatting for a given field. + * + * @param string $field the field to remove it from + */ + public function removePreFormatFieldOverride($field) + { + $this->removeFieldOverride('hl.simple.pre', $field); + } + + /** + * Set the text which appears after a highlighted term when + * using the simple formatter. + * + * @param string $value the string to append + */ + public function setPostFormat($value) + { + $this->setParameterValue('hl.simple.post',$value); + } + + /** + * Get the text which appears after a highlighted term when + * using the simple formatter. + * + * @param string $value the string to append + */ + public function getPostFormat() + { + return $this->getParameterValue('hl.simple.post'); + } + + /** + * Add an override for the value that gets appended to a highlighted + * term. + * + * @param string $field the field to override + * @param string $value the value to override it with + */ + public function addPostFormatFieldOverride($field, $value) + { + $this->addFieldOverride('hl.simple.post', $field, $value); + } + + /** + * Get the override for the appended value for a highlighted term. + * + * @param string $field the field to look at + */ + public function getPostFormatFieldOverride($field) + { + return $this->getFieldOverride('hl.simple.post', $field); + } + + /** + * Remove the post-highlighting override for a field. + * + * @param string $field the field to remove it for. + */ + public function removePostFormatFieldOverride($field) + { + $this->removeFieldOverride('hl.simple.post', $field); + } + + /** + * Specify a text snippet generator for highlighted text. The standard + * fragmenter is gap (which is so called because it creates fixed-sized + * fragments with gaps for multi-valued fields). Another option is regex, + * which tries to create fragments that "look like" a certain regular + * expression. + * + * The default value is "gap" + * + * @param string $value the text snippet generator to use + */ + public function setFragmenter($value) + { + $this->setParameterValue('hl.fragmenter',$value); + } + + /** + * Get the text snippet generator that is being used. + * + * @return string the text snippet generator that is being used + */ + public function getFragmenter() + { + return $this->getParameterValue('hl.fragmenter'); + } + + /** + * Override which text snippet generator a field uses. + * + * @param string $field the field to override + * @param string $value the text snippet generator to use + */ + public function addFragmenterFieldOverride($field, $value) + { + $this->addFieldOverride('hl.fragmenter', $field, $value); + } + + /** + * Get the overridden value (if it exists) of which text snippet + * generator a field uses. + * + * @param string $field the field to check + * @return string the text snippet generator it uses + */ + public function getFragmenterFieldOverride($field) + { + return $this->getFieldOverride('hl.fragmenter', $field); + } + + /** + * Remove an overridden text snippet generator value for a + * field. + * + * @param string $field the field to remove it for + */ + public function removeFragmenterFieldOverride($field) + { + $this->removeFieldOverride('hl.fragmenter', $field); + } + + /** + * Specify the name of SolrFragListBuilder. This parameter makes sense for + * FastVectorHighlighter only. + * + * @param string $value the SolrFragListBuilder you wish to use. + */ + public function setFragListBuilder($value) + { + $this->setParameterValue('hl.fragListBuilder',$value); + } + + /** + * Get the name of the SolrFragListBuilder you wish to use. + * + * @return string the name of the SolrFragListBuilder being used + */ + public function getFragListBuilder() + { + return $this->getParameterValue('hl.fragListBuilder'); + } + + /** + * Specify the name of SolrFragmentsBuilder. This parameter makes sense for + * FastVectorHighlighter only. + * + * @param string $value the name of the SolrFragmentsBuilder + */ + public function setFragmentsBuilder($value) + { + $this->setParameterValue('hl.fragmentsBuilder',$value); + } + + /** + * Get the name of the SolrFragmentsBuilder that is being used. + * + * @return string the name of the SolrFragmentsBuilder being used + */ + public function getFragmentsBuilder() + { + return $this->getParameterValue('hl.fragmentsBuilder'); + } + + /** + * Use FastVectorHighlighter. FastVectorHighlighter requires the field is + * termVectors=on, termPositions=on and termOffsets=on. This parameter + * accepts per-field overrides. + * + * The default value is "false" + * + * @param boolean $value true if you wish to use it, false if not + */ + public function setUseFastVectorHighlighter($value) + { + $this->setParameterValue('hl.useFastVectorHighlighter',$value); + } + + /** + * Get whether or not the FastVectorHighlighter is being used. + * + * @return boolean true if it is being used, false if it is not + */ + public function getUseFastVectorHighlighter() + { + return $this->getParameterValue('hl.useFastVectorHighlighter'); + } + + /** + * Override whether or not a field is using the FastVectorHighlighter. + * + * @param string $field the field that you want to override the value for + * @param boolean $value the value you want to override it with + */ + public function addUseFastVectorHighlighterFieldOverride($field, $value) + { + $this->addFieldOverride('hl.useFastVectorHighlighter', $field, $value); + } + + /** + * Get the override for whether or not a field is using the FastVectorHighlighter. + * + * @param string $field the field to check + * @return boolean whether or not the FastVectorHighlighter is being used for that + */ + public function getUseFastVectorHighlighterFieldOverride($field) + { + return $this->getFieldOverride('hl.useFastVectorHighlighter', $field); + } + + /** + * Remove the override for whether or not a field is using the FastVectorHighlighter. + * + * @param string $field the field to remove it for + */ + public function removeUseFastVectorHighlighterFieldOverride($field) + { + $this->removeFieldOverride('hl.useFastVectorHighlighter', $field); + } + + /** + * Use SpanScorer to highlight phrase terms only when they appear within the query + * phrase in the document. + * + * Default is false. + * + * @param boolean $value true to do it, false to not + */ + public function setUsePhraseHighlighter($value) + { + $this->setParameterValue('hl.usePhraseHighlighter',$value); + } + + /** + * Get whether or not to only highlight phrases. + * + * @return boolean true if it only highlights phrases, false if not + */ + public function getUsePhraseHighlighter() + { + return $this->getParameterValue('hl.usePhraseHighlighter'); + } + + /** + * If the SpanScorer is also being used, enables highlighting for + * range/wildcard/fuzzy/prefix queries. + * + * Default is false. + * + * @param boolean $value true to use them, false to not + */ + public function setHighlightMultiTerm($value) + { + $this->setParameterValue('hl.highlightMultiTerm',$value); + } + + /** + * Get whether or not highlighting for range/wildcard/fuzzy/prefix + * queries is being used. + * + * @return boolean true if it is being used, false if not + */ + public function getHighlightMultiTerm() + { + return $this->getParameterValue('hl.highlightMultiTerm'); + } + + /** + * Factor by which the regex fragmenter can stray from the ideal fragment + * size (given by hl.fragsize) to accomodate the regular expression. For + * instance, a slop of 0.2 with fragsize of 100 should yield fragments + * between 80 and 120 characters in length. It is usually good to provide + * a slightly smaller fragsize when using the regex fragmenter. + * + * The default value is ".6" + * + * @param float $value the value to use + */ + public function setRegexSlop($value) + { + $this->setParameterValue('hl.regex.slop',$value); + } + + /** + * Get the regex slop value (the percentage with which the regex fragmenter can + * stray in size. + * + * @return float the slop value for the regex fragementer + */ + public function getRegexSlop() + { + return $this->getParameterValue('hl.regex.slop'); + } + + /** + * The regular expression for fragmenting. This could be used to extract + * sentences (see example solrconfig.xml) + * + * @param string $value the regex to use for fragmenting + */ + public function setRegexPattern($value) + { + $this->setParameterValue('hl.regex.pattern',$value); + } + + /** + * Get the regex used for fragmenting. + * + * @return string the regex used for fragmenting + */ + public function getRegexPattern() + { + return $this->getParameterValue('hl.regex.pattern'); + } + + /** + * Only analyze this many characters from a field when using the regex + * fragmenter (after which, the fragmenter produces fixed-sized fragments). + * Applying a complicated regex to a huge field is expensive. + * + * The default value is "10000". + * + * @param int $value the max number of characters to analyze with the regex + */ + public function setRegexMaxAnalyzedChars($value) + { + $this->setParameterValue('hl.regex.maxAnalyzedChars',$value); + } + + /** + * Get the max number of characters to analyze with a regex fragmenter. + * + * @return int the max number of characters to analyze with a regex fragmenter + */ + public function getRegexMaxAnalyzedChars() + { + return $this->getParameterValue('hl.regex.maxAnalyzedChars'); + } + + /** + * This function should return whether or not the $param fed in accepts field overrides + * or not. + * + * @param string $param this is the parameter you are testing for overrideability + * @return boolean true if it overrideable, false if it isn't + */ + protected function isOverrideable($param) + { + return self::$param_info[$param]['ovr']; + } + + /** + * This function returns the type of parameter that $param is. + * + * @param string $param the parameter whose type you want + * @return string the type the parameter is. + */ + protected function getParamType($param) + { + return self::$param_info[$param]['type']; + } + + /** + * This function returns the default value of a parameter. + * + * @param string $param the parameter you want the default value for + * @return string the default value of the parameter + */ + protected function getDefaultValue($param) + { + return self::$param_info[$param]['default']; + } +} \ No newline at end of file diff --git a/lib/solr-php-client/Solr/SolrOpts.php b/lib/solr-php-client/Solr/SolrOpts.php new file mode 100755 index 0000000..2edc3ad --- /dev/null +++ b/lib/solr-php-client/Solr/SolrOpts.php @@ -0,0 +1,277 @@ +getDefaultValue($param); + + // Check to see if the value given is the default value for that parameter + if ( $value == $default ) + { + // If it is, check to see if there are any overridden fields + if ( count($this->_param_map[$param][self::OVERRIDE_KEY]) == 0 ) + { + // If there aren't, then just unset the whole parameter + unset($this->_param_map[$param]); + } + elseif ( isset($this->_param_map[$param]) ) + { + // Otherwise, remove the value so that key doesn't exist anymore + unset($this->_param_map[$param]['value']); + } + } + else + { + // If the value given wasn't the same as the default value, then check to see if + // the parameter information has already been added + if ( !isset($this->_param_map[$param]) ) + { + // If it hasn't been, create it + $this->_param_map[$param] = array( 'value' => $value, 'type' => $this->getParamType($param) ); + // Make sure the current param is overrideable before we create the array to hold the overrides + if ( $this->isOverrideable($param) ) + { + $this->_param_map[$param][self::OVERRIDE_KEY] = array(); + } + } + else + { + // If it already existed, just change the value. + $this->_param_map[$param]['value'] = $value; + } + } + } + + /** + * This function retrieves the value of a parameter. If the value has not been + * set then it will return the default value. + * + * @return mixed the value of the parameter + */ + protected function getParameterValue($param) + { + return ( !isset($this->_param_map[$param]) || !isset($this->_param_map[$param]['value']) ? $this->getDefaultValue($param) : $this->_param_map[$param]['value'] ); + } + + /** + * This method will add an override to a particular field for the given parameter. This + * method will silently not add the override if the parameter cannot be overridden. + * + * @param string $param the parameter to be set + * @param string $field the field to be set + * @param mixed $value the value to set it to + */ + protected function addFieldOverride($param, $field, $value) + { + $default = $this->getDefaultValue($param); + + // Check that the given parameter is overrideable + if ( $this->isOverrideable($param) ) + { + // We don't want to bother to send the parameter if it just going to be the default, + // unless the main value has been overridden and we want to put the value for this + // particular field back to default + if ( $value != $default || $this->_param_map[$param]['value'] != $default ) + { + // If we are adding the override without setting the main value, then we need to + // create the structure as well as setting the value + if ( !isset($this->_param_map[$param]) ) + { + $this->_param_map[$param] = array( 'type' => $this->getParamType($param), self::OVERRIDE_KEY => array( $field => $value ) ); + } + else + { + // Set the value + $this->_param_map[$param][self::OVERRIDE_KEY][$field] = $value; + } + } + } + } + + /** + * This method retrieves the field override for the given parameter/field pair. + * + * @param string $param the parameter to be set + * @param string $field the field to be set + * @return mixed the override for the given field + */ + protected function getFieldOverride($param, $field) + { + return $this->_param_map[$param][self::OVERRIDE_KEY][$field]; + } + + + /** + * This function removes an override from a field + * + * @param string $param the parameter from which the override needs to be removed + * @param string $field the field from which to remove the override + */ + protected function removeFieldOverride($param, $field) + { + unset($this->_param_map[$param][self::OVERRIDE_KEY][$field]); + } + + /** + * This function produces an array of the parameters that this object encapsulates. + * The format is such that it can be consummed by http_build_query. + * + * @return array the parameters that this object needs added to the request. + */ + public function getParamArray() { + $data = array(); + + // For every parameter in the map + foreach ( $this->_param_map as $param => $info ) + { + // We make sure that the value is set ( in case all they did was add an override + // or they set it to the default value ) + if( isset($info['value']) ) { + switch ( $info['type'] ) + { + case self::CSV_PARAM: + // If it is a CSV param, implode it using commas as glue + $data[$param] = implode(',', $info['value']); + break; + case self::BOOLEAN_PARAM: + // If it is boolean, convert to the english words + $data[$param] = ( $info['value'] === true ? "true" : "false" ); + break; + default: + // Otherwise just add it. + $data[$param] = $info['value']; + break; + } + } + + // If this is an overrideable parameter + if ( isset($info[self::OVERRIDE_KEY]) ) + { + // For each of the overrides + foreach ( $info[self::OVERRIDE_KEY] as $field => $value ) + { + // Add it to the parameters as f.. as + // described on http://wiki.apache.org/solr/SimpleFacetParameters#Parameters + // and http://wiki.apache.org/solr/HighlightingParameters#HowToOverride + $data["f.$field.$param"] = $value; + } + } + } + + // Return the array. + return $data; + } + + /** + * This function should return whether or not the $param fed in accepts field overrides + * or not. + * + * @param string $param this is the parameter you are testing for overrideability + * @return boolean true if it overrideable, false if it isn't + */ + abstract protected function isOverrideable($param); + + /** + * This function returns the type of parameter that $param is. + * + * @param string $param the parameter whose type you want + * @return string the type the parameter is. + */ + abstract protected function getParamType($param); + + /** + * This function returns the default value of a parameter. + * + * @param string $param the parameter you want the default value for + * @return string the default value of the parameter + */ + abstract protected function getDefaultValue($param); + +} \ No newline at end of file diff --git a/models/ESearchExclude.php b/models/ESearchExclude.php new file mode 100755 index 0000000..d9b4a08 --- /dev/null +++ b/models/ESearchExclude.php @@ -0,0 +1,20 @@ + + **/ + public function isExcludedID($collection_id) + { + $count = $this->count(array( + 'collection_id' => $collection_id + )); + return ($count > 0); + } + + /** + * This tests whether the collection is excluded. + * + * @param Collection $collection The collection to test. + * @return bool + * @author Eric Rochester + **/ + public function isExcluded($collection) + { + return $this->isExcludedID($collection->id); + } +} diff --git a/models/ESearchField.php b/models/ESearchField.php new file mode 100755 index 0000000..313c25a --- /dev/null +++ b/models/ESearchField.php @@ -0,0 +1,169 @@ +element_id = $element->id; + + // Element identifier. + $this->slug = $element->id; + + // Pubilc-facing label. + $this->label = $element->name; + + } + + } + + + /** + * The key for the tokenized version of the field in a Solr document. + * + * @return string + */ + public function indexKey() + { + return "{$this->slug}_t"; + } + + + /** + * The key for the untokenized version of the field in a Solr document. + * + * @return string + */ + public function facetKey() + { + return $this->hasElement() ? "{$this->slug}_s" : $this->slug; + } + + + /** + * Is the field associated with a metadata element? + * + * @return boolean True if an element is defined. + */ + public function hasElement() + { + return !is_null($this->element_id); + } + + + /** + * Get the parent element. + * + * @return Element|null The element. + */ + public function getElement() + { + if (!$this->hasElement()) return null; + else return $this->getTable('Element')->find($this->element_id); + } + + + /** + * Get the parent element set. + * + * @return ElementSet|null The element set. + */ + public function getElementSet() + { + if (!$this->hasElement()) return null; + else return $this->getElement()->getElementSet(); + } + + + /** + * Get the name of the parent element set. + * + * @return string The element set name. + */ + public function getElementSetName() + { + if (!$this->hasElement()) return __('Omeka Categories'); + else return $this->getElementSet()->name; + } + + + /** + * Return the original label for the field. + * + * @return string|null + **/ + public function getOriginalLabel() + { + switch ($this->slug) { + + case 'tag': return __('Tag'); + case 'collection': return __('Collection'); + case 'itemtype': return __('Item Type'); + case 'resulttype': return __('Result Type'); + case 'featured': return __('Featured'); + + default: return $this->getElement()->name; + + } + } + + + /** + * If the label is empty, revert to the original label. + * + * @return string The facet label. + */ + public function beforeSave() + { + $label = trim($this->label); + if (empty($label)) $this->label = $this->getOriginalLabel(); + } + + +} diff --git a/models/ESearchFieldTable.php b/models/ESearchFieldTable.php new file mode 100755 index 0000000..aeccea9 --- /dev/null +++ b/models/ESearchFieldTable.php @@ -0,0 +1,235 @@ +findBySql( + 'element_id=?', array($text->element_id), true + ); + } + + + /** + * Find the field associated with a given element. + * + * @param Element $element The element. + * @return ESearchField + */ + public function findByElement($element) + { + return $this->findBySql( + 'element_id=?', array($element->id), true + ); + } + + + /** + * Find the field associated with a given element, identified by element + * set name and element name. + * + * @param string $set The element set name. + * @param string $element The element name. + * @return ESearchField + */ + public function findByElementName($set, $element) + { + + // Get the element table. + $elementTable = $this->getTable('Element'); + + // Get the parent element. + $element = $elementTable->findByElementSetNameAndElementName( + $set, $element + ); + + // Find the element's field. + return is_null($element) ? null : $this->findByElement($element); + + } + + + /** + * Find the facet with a given slug. + * + * @param string $slug The slug. + * @return ESearchField + */ + public function findBySlug($slug) + { + return $this->findBySql('slug=?', array($slug), true); + } + + + /** + * Flag a metadata element to be indexed in Solr. + * + * @param string $set The element set name. + * @param string $element The element name. + * @param boolean $value True if indexed. + */ + public function setElementIndexed($set, $element, $value = true) { + $this->setElementFlag($set, $element, 'is_indexed', $value); + } + + + /** + * Flag a metadata element to be used as a facet. + * + * @param string $set The element set name. + * @param string $element The element name. + * @param boolean $value True if faceted. + */ + public function setElementFaceted($set, $element, $value = true) { + $this->setElementFlag($set, $element, 'is_facet', $value); + } + + + /** + * Flip a boolean flag on an element-backed field. + * + * @param string $set The element set name. + * @param string $element The element name. + * @param string $flag The name of the flag. + * @param boolean $value True if on. + */ + public function setElementFlag($set, $element, $flag, $value = true) { + $field = $this->findByElementName($set, $element); + $field->$flag = $value; + $field->save(); + } + + + /** + * Get a list of the slugs of all active facets. + * + * @return array The list of active facet slugs. + */ + public function getActiveFacetKeys() + { + + $active = array(); + + // Get names for active facets. + foreach ($this->findBySql('is_facet=?', array(1)) as $field) { + $active[] = $field->facetKey(); + } + + return $active; + + } + + + /** + * Get all fields grouped by element set id. + * + * @return array $facets The ElementSet-grouped facets. + */ + public function groupByElementSet() + { + + $groups = array(); + + foreach ($this->findAll() as $facet) { + + // Get element set name. + $set = $facet->getElementSetName(); + + // Add the facet to its element set group (or create it). + if (array_key_exists($set, $groups)) $groups[$set][] = $facet; + else $groups[$set] = array($facet); + + } + + return $groups; + + } + + + /** + * Installs the current set of elements as facets. + */ + public function installFacetMappings() + { + $elements = $this->_db->getTable('Element'); + + // Generic facets: + $this->installGenericFacet('tag', __('Tag')); + $this->installGenericFacet('collection', __('Collection')); + $this->installGenericFacet('itemtype', __('Item Type')); + $this->installGenericFacet('resulttype', __('Result Type')); + $this->installGenericFacet('featured', __('Featured')); + + // Element-backed facets: + foreach ($elements->findAll() as $element) { + $facet = new ESearchField($element); + $facet->save(); + } + + // By default, index DC Title/Description. + $this->setElementIndexed('Dublin Core', 'Title'); + $this->setElementIndexed('Dublin Core', 'Description'); + } + + /** + * Install a default facet mapping. + * + * @param string $slug The facet `slug`. + * @param string $label The facet `label`. + */ + public function installGenericFacet($slug, $label) + { + $facet = new ESearchField(); + $facet->slug = $slug; + $facet->label = $label; + $facet->is_indexed = 1; + $facet->is_facet = 1; + $facet->save(); + } + + /** + * Updates the facets with elements that have been added since + * `installFacets` was called. + */ + public function updateFacetMappings() + { + $facetSet = array(); + foreach ($this->findAll() as $facet) { + $facetSet[$facet->element_id] = $facet; + } + + $elementTable = $this->_db->getTable('Element'); + $elementSet = array(); + foreach ($elementTable->findAll() as $element) { + if (! array_key_exists($element->id, $facetSet)) { + $facet = new ESearchField($element); + $facet->save(); + } else { + $elementSet[$element->id] = TRUE; + } + } + + foreach ($facetSet as $facetId => $facet) { + if (! array_key_exists($facetId, $elementSet)) { + $facet->delete(); + } + } + } + +} diff --git a/plugin.ini b/plugin.ini new file mode 100755 index 0000000..640b0db --- /dev/null +++ b/plugin.ini @@ -0,0 +1,12 @@ +[info] +name="ESearch" +author="Jason Zou" +description="Integrate ElasticSearch into Omeka for primary searching." +link="https://github.com/jasonzou/ESearch" +support_link="https://github.com/jasonzou/ESearch/issues" +omeka_minimum_version="2.5" +omeka_tested_up_to="2.5" +omeka_target_version="2.5" +version="0.0.1" +tags="elasticsearch, search" +license="Apache 2.0" diff --git a/plugin.php b/plugin.php new file mode 100755 index 0000000..cad7af5 --- /dev/null +++ b/plugin.php @@ -0,0 +1,45 @@ +setUp(); diff --git a/routes.ini b/routes.ini new file mode 100755 index 0000000..7e724e8 --- /dev/null +++ b/routes.ini @@ -0,0 +1,8 @@ +ESearchAdmin.route = "esearch/:action" +ESearchAdmin.defaults.module = "esearch" +ESearchAdmin.defaults.controller = "admin" +ESearchAdmin.defaults.action = "server" + +ESearchResults.route = "esearch" +ESearchResults.defaults.module = "esearch" +ESearchResults.defaults.controller = "results" diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..143efe8 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + + * 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, PSR-4 and classmap class loader. + * + * $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 + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +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; + private $missingClasses = array(); + private $apcuPrefix; + + 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-4 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; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * 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) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + foreach ($this->prefixDirsPsr4[$search] as $dir) { + $length = $this->prefixLengthsPsr4[$first][$search]; + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($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 (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +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/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + $vendorDir . '/react/promise/src/functions_include.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/react/promise/src'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'GuzzleHttp\\Stream\\' => array($vendorDir . '/guzzlehttp/streams/src'), + 'GuzzleHttp\\Ring\\' => array($vendorDir . '/guzzlehttp/ringphp/src'), + 'Elasticsearch\\' => array($vendorDir . '/elasticsearch/elasticsearch/src/Elasticsearch'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..a4d55e8 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInitf84c86e45efac258847d2d4f7970a69e::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $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); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInitf84c86e45efac258847d2d4f7970a69e::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequiref84c86e45efac258847d2d4f7970a69e($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequiref84c86e45efac258847d2d4f7970a69e($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..5e3bfac --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,64 @@ + __DIR__ . '/..' . '/react/promise/src/functions_include.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'R' => + array ( + 'React\\Promise\\' => 14, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + ), + 'G' => + array ( + 'GuzzleHttp\\Stream\\' => 18, + 'GuzzleHttp\\Ring\\' => 16, + ), + 'E' => + array ( + 'Elasticsearch\\' => 14, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'React\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/react/promise/src', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'GuzzleHttp\\Stream\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/streams/src', + ), + 'GuzzleHttp\\Ring\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/ringphp/src', + ), + 'Elasticsearch\\' => + array ( + 0 => __DIR__ . '/..' . '/elasticsearch/elasticsearch/src/Elasticsearch', + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitf84c86e45efac258847d2d4f7970a69e::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitf84c86e45efac258847d2d4f7970a69e::$prefixDirsPsr4; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..70516d6 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,261 @@ +[ + { + "name": "react/promise", + "version": "v2.5.1", + "version_normalized": "2.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "62785ae604c8d69725d693eb370e1d67e94c4053" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/62785ae604c8d69725d693eb370e1d67e94c4053", + "reference": "62785ae604c8d69725d693eb370e1d67e94c4053", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "time": "2017-03-25T12:08:31+00:00", + "type": "library", + "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@gmail.com" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ] + }, + { + "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-12T19:18:40+00:00", + "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-20T03:37:09+00:00", + "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": "psr/log", + "version": "1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-10-10T12:19:37+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ] + }, + { + "name": "elasticsearch/elasticsearch", + "version": "v5.2.0", + "version_normalized": "5.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/elastic/elasticsearch-php.git", + "reference": "dc57bbcd4be2c9061ebe5be59058ea8bdebc904e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/dc57bbcd4be2c9061ebe5be59058ea8bdebc904e", + "reference": "dc57bbcd4be2c9061ebe5be59058ea8bdebc904e", + "shasum": "" + }, + "require": { + "guzzlehttp/ringphp": "~1.0", + "php": "^5.6|^7.0", + "psr/log": "~1.0" + }, + "require-dev": { + "cpliakas/git-wrapper": "~1.0", + "doctrine/inflector": "^1.1", + "mockery/mockery": "0.9.4", + "phpunit/phpunit": "^4.7|^5.4", + "sami/sami": "~3.2", + "symfony/finder": "^2.8", + "symfony/yaml": "^2.8" + }, + "suggest": { + "ext-curl": "*", + "monolog/monolog": "Allows for client-level logging and tracing" + }, + "time": "2017-04-12T20:40:50+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Elasticsearch\\": "src/Elasticsearch/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Zachary Tong" + } + ], + "description": "PHP Client for Elasticsearch", + "keywords": [ + "client", + "elasticsearch", + "search" + ] + } +] diff --git a/vendor/elasticsearch/elasticsearch/.github/CONTRIBUTING.md b/vendor/elasticsearch/elasticsearch/.github/CONTRIBUTING.md new file mode 100644 index 0000000..412d66f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/.github/CONTRIBUTING.md @@ -0,0 +1,48 @@ +If you have a bugfix or new feature that you would like to contribute to elasticsearch-php, please find or open an issue about it first. Talk about what you would like to do. It may be that somebody is already working on it, or that there are particular issues that you should know about before implementing the change. + +We enjoy working with contributors to get their code accepted. There are many approaches to fixing a problem and it is important to find the best approach before writing too much code. + +The process for contributing to any of the Elasticsearch repositories is similar. + +1. Sign the contributor license agreement + + Please make sure you have signed the [Contributor License Agreement](http://www.elasticsearch.org/contributor-agreement/). We are not asking you to assign copyright to us, but to give us the right to distribute your code without restriction. We ask this of all contributors in order to assure our users of the origin and continuing existence of the code. You only need to sign the CLA once. + +2. Set up your fork for development + + $> git clone https://github.com/elasticsearch/elasticsearch-php.git + $> cd elasticsearch-php + $> git submodule update --init --recursive + $> curl -s http://getcomposer.org/installer | php + $> php composer.phar install --dev + +3. Ensure a version of Elasticsearch is running on your machine. Recommended "test" configuration is: + + $> bin/elasticsearch -Des.gateway.type=none -Des.http.port=9200 \ + -Des.index.store.type=memory -Des.discovery.zen.ping.multicast.enabled=false \ + -Des.node.bench=true -Des.script.disable_dynamic=false + +4. Run the unit and yaml integration tests to ensure your changes do not break existing code. The exported `TEST_BUILD_REF` should match the branch of Elasticsearch that is running on your machine (since tests are specific to the server version): + + $> export TEST_BUILD_REF='origin/1.x' + $> export ES_TEST_HOST='http://localhost:9200' + + Then proceed to initialize the REST yaml tests and run the package. **WARNING: the unit tests will clear your cluster + and data..._do not_ run the tests on a production cluster!** + + $> php util/RestSpecRunner.php + $> php vendor/bin/phpunit + +5. Ensure your changes follow the [PSR-2 Coding Style Guide](http://www.php-fig.org/psr/psr-2/). You can run tools such as [PHP-CS-Fixer](http://cs.sensiolabs.org/) or [PHP_CodeSniffer](http://pear.php.net/package/PHP_CodeSniffer) to enforce PSR-2 automatically. + +6. Rebase your changes + + Update your local repository with the most recent code from the main elasticsearch-php repository, and rebase your branch on top of the latest master branch. We prefer your changes to be squashed into a single commit. + +7. Submit a pull request + + Push your local changes to your forked copy of the repository and submit a pull request. In the pull request, describe what your changes do and mention the number of the issue where discussion has taken place, eg โ€œCloses #123โ€ณ. Please consider adding or modifying tests related to your changes. + + +Then sit back and wait. There will probably be discussion about the pull request and, if any changes are needed, we would love to work with you to get your pull request merged into elasticsearch-php. + diff --git a/vendor/elasticsearch/elasticsearch/.github/ISSUE_TEMPLATE.md b/vendor/elasticsearch/elasticsearch/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..40380c1 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,31 @@ + +### Summary of problem or feature request + + + + +### Code snippet of problem + + + +### System details + + + +- Operating System +- PHP Version +- ES-PHP client version +- Elasticsearch version \ No newline at end of file diff --git a/vendor/elasticsearch/elasticsearch/.github/PULL_REQUEST_TEMPLATE.md b/vendor/elasticsearch/elasticsearch/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..0aa96c9 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/vendor/elasticsearch/elasticsearch/.gitignore b/vendor/elasticsearch/elasticsearch/.gitignore new file mode 100755 index 0000000..d24d753 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/.gitignore @@ -0,0 +1,26 @@ +#composer related +composer.lock +vendor/ +composer.phar +.php_cs.cache + +#editor related +.idea + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +Icon? +ehthumbs.db +Thumbs.db + +#generator related +generator/* + +# Elasticsearch related +util/elasticsearch/ +util/cache/ +util/output diff --git a/vendor/elasticsearch/elasticsearch/.gitmodules b/vendor/elasticsearch/elasticsearch/.gitmodules new file mode 100644 index 0000000..e03c446 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/.gitmodules @@ -0,0 +1,3 @@ +[submodule "util/elasticsearch"] + path = util/elasticsearch + url = https://github.com/elasticsearch/elasticsearch.git diff --git a/vendor/elasticsearch/elasticsearch/.php_cs b/vendor/elasticsearch/elasticsearch/.php_cs new file mode 100644 index 0000000..e01869c --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/.php_cs @@ -0,0 +1,22 @@ +exclude('benchmarks') + ->exclude('docs') + ->exclude('util') + ->exclude('.github') + ->exclude('util') + ->exclude('travis') + ->exclude('util/cache') + ->exclude('util/elasticsearch') + ->exclude('vendor') + ->in(__DIR__); + + return Symfony\CS\Config\Config::create() + ->setUsingCache(true) + ->level(Symfony\CS\FixerInterface::PSR2_LEVEL) + ->finder($finder); +} + +return php_cs(); diff --git a/vendor/elasticsearch/elasticsearch/.travis.yml b/vendor/elasticsearch/elasticsearch/.travis.yml new file mode 100644 index 0000000..bd3854f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/.travis.yml @@ -0,0 +1,59 @@ +language: php + +jdk: + - oraclejdk8 + +addons: + apt: + packages: + - oracle-java8-installer + +branches: + except: + - 0.4 + +sudo: true + +php: + - 5.6 + - 7.0 + - 7.1 + - hhvm + +matrix: + fast_finish: true + include: + - php: 7.1 + env: ES_VERSION="5.0" TEST_BUILD_REF="origin/5.0" + - php: 7.1 + env: ES_VERSION="5.1" TEST_BUILD_REF="origin/5.1" + - php: 7.1 + env: ES_VERSION="5.2" TEST_BUILD_REF="origin/5.2" + allow_failures: + - php: hhvm + +env: + global: + - ES_VERSION="5.x" + - TEST_BUILD_REF="origin/5.x" + - ES_TEST_HOST=http://localhost:9200 + - JAVA_HOME="/usr/lib/jvm/java-8-oracle/jre" + +before_install: + - sudo update-java-alternatives -s java-8-oracle + - ./travis/download_and_run_es.sh + +install: + - composer install --prefer-source + +before_script: + - if [ $TRAVIS_PHP_VERSION = '7.0' ]; then PHPUNIT_FLAGS="--coverage-clover ./build/logs/clover.xml"; fi + - php util/RestSpecRunner.php + - php util/EnsureClusterAlive.php + +script: + - vendor/bin/phpunit $PHPUNIT_FLAGS + - vendor/bin/phpunit -c phpunit-integration.xml --group sync $PHPUNIT_FLAGS + +after_script: + - if [ $TRAVIS_PHP_VERSION = '7.0' ]; then php vendor/bin/coveralls; fi diff --git a/vendor/elasticsearch/elasticsearch/BREAKING_CHANGES.md b/vendor/elasticsearch/elasticsearch/BREAKING_CHANGES.md new file mode 100644 index 0000000..cba9966 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/BREAKING_CHANGES.md @@ -0,0 +1,23 @@ +# 5.0 + +## Breaking changes + +- Indices/Analyze Endpoint: `filters` and `char_filters` URI parameters have renamed to `filter` and `char_filter` respectively +- SearchExists endpoint has been removed ([use `size=0` and `terminate_after=1` instead](https://www.elastic.co/guide/en/elasticsearch/reference/current/breaking_50_search_changes.html#_search_exists_api_removed)) +- Warmers have been removed because they are [no longer useful](https://www.elastic.co/guide/en/elasticsearch/reference/current/breaking_50_index_apis.html#_warmers) +- Indices/Optimize Endpoint has been removed ([use `_forcemerge` instead](https://www.elastic.co/guide/en/elasticsearch/reference/current/breaking_50_rest_api_changes.html#_literal__optimize_literal_endpoint_removed)) +- MoreLikeThis (MLT) endpoint has been removed +- DeleteByQuery endpoint has been removed. +- Tasks/List and Tasks/Get are now separate endpoints (see: [[e0cc5f9]](http://github.com/elasticsearch/elasticsearch-php/commit/752d5a2)) +- Client requires PHP 5.6.6 or higher + +## Deprecations + +- Percolator endpoints are deprecated and will be removed in Elasticsearch 6.0 + +## Internal BWC Breaks + +- Namespace injection has changed slightly. If you use custom namespaces, you'll need to update your code (see: Add better ability to inject namespaces [[b1a27b7]](http://github.com/elasticsearch/elasticsearch-php/commit/b1a27b7)) +- Endpoints no longer use the Transport directly. If you use custom endpoints, you'll need to do some minor +refactoring (see: Refactor to remove Transport dependence in endpoints [[ecd454c]](http://github.com/elasticsearch/elasticsearch-php/commit/ecd454c)) +- To facilitate testing and other features, the `ConnectionInterface` has expanded to obtain some more methods ([[getPath()]](http://github.com/elasticsearch/elasticsearch-php/commit/8bcf1a8), [[getUserPass()]](http://github.com/elasticsearch/elasticsearch-php/commit/586fbdb), [[getHost()]](http://github.com/elasticsearch/elasticsearch-php/commit/445fdea)) diff --git a/vendor/elasticsearch/elasticsearch/CHANGELOG.md b/vendor/elasticsearch/elasticsearch/CHANGELOG.md new file mode 100644 index 0000000..89d4b30 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/CHANGELOG.md @@ -0,0 +1,247 @@ +## Release 5.2.0 + +- Add 'batched_reduce_size' / 'typed_keys' params to Search endpoint [[691ce24]](http://github.com/elasticsearch/elasticsearch-php/commit/691ce24) +- Fix Scroll and ClearScroll syntax [[59b3c08]](http://github.com/elasticsearch/elasticsearch-php/commit/59b3c08) +- Add 'request', 'request_cache' to ClearCache endpoint [[07ff0be]](http://github.com/elasticsearch/elasticsearch-php/commit/07ff0be) +- Handle null `type` better [[9900cfd]](http://github.com/elasticsearch/elasticsearch-php/commit/9900cfd) +- Add 'stored_fields' param to Exists, Explain and Search endpoints [[01f9a06]](http://github.com/elasticsearch/elasticsearch-php/commit/01f9a06) +- Add endpoint index and type getters (#557) [[0d9cdfa]](http://github.com/elasticsearch/elasticsearch-php/commit/0d9cdfa) +- added getTransport, getEndpoint, getRegisteredNamespacesBuilder to ClientBuilder.php (#551) [[608bfe8]](http://github.com/elasticsearch/elasticsearch-php/commit/608bfe8) +- Add 'version' param to Exists whitelist [[f2ae26b]](http://github.com/elasticsearch/elasticsearch-php/commit/f2ae26b) +- Add 'typed_keys' param to MSearch whitelist [[3884ca0]](http://github.com/elasticsearch/elasticsearch-php/commit/3884ca0) +- Make id param optionnal for termvectors requests. (#542) [[e6f79de]](http://github.com/elasticsearch/elasticsearch-php/commit/e6f79de) + +### Testing + +- [TEST] Warning header format has changed slightly [[6c8699c]](http://github.com/elasticsearch/elasticsearch-php/commit/6c8699c) +- [TEST] Added missing semicolon (#544) [[ccfb5a6]](http://github.com/elasticsearch/elasticsearch-php/commit/ccfb5a6) +- [TEST] Allow JSON workaround, remove travis hackery [[3467c19]](http://github.com/elasticsearch/elasticsearch-php/commit/3467c19) + +### Docs + +- Update index-operations.asciidoc (#537) [[348fb4d]](http://github.com/elasticsearch/elasticsearch-php/commit/348fb4d) +- [DOCS] use std class in example instead of empty array (#549) [[9c45775]](http://github.com/elasticsearch/elasticsearch-php/commit/9c45775) +- [DOCS] Documentation fixes for 5.0 (typos, etc.) (#543) [[c4cf003]](http://github.com/elasticsearch/elasticsearch-php/commit/c4cf003) +- [DOCS] bool query's "query" should use a "should" (#545) [[8ec26ba]](http://github.com/elasticsearch/elasticsearch-php/commit/8ec26ba) +- [DOCS] Fix scrolling example [[006b3c2]](http://github.com/elasticsearch/elasticsearch-php/commit/006b3c2) + +## Release 5.1.3 + +- allowBadJSONSerialization() builder method should be fluent [[f1812d4]](http://github.com/elasticsearch/elasticsearch-php/commit/f1812d4) + +## Release 5.1.2 + +- A specific version of json-ext is no longer needed in 5.0 branch [[e1b5c2a]](http://github.com/elasticsearch/elasticsearch-php/commit/e1b5c2a) + +## Release 5.1.1 + +- Add 'format' param to all Cat endpoints [[fc25a2c]](http://github.com/elasticsearch/elasticsearch-php/commit/fc25a2c) +- Add opt-in for ignoring JSON_PRESERVE_ZERO_FRACTION when serializing [[e34fdfd]](http://github.com/elasticsearch/elasticsearch-php/commit/e34fdfd) +- Prefer POST for Suggest endpoint [[aae7019]](http://github.com/elasticsearch/elasticsearch-php/commit/aae7019) +- Whitelist 'include_segment_file_sizes' in Cluster/Nodes/Stats endpoint [[a2744b4]](http://github.com/elasticsearch/elasticsearch-php/commit/a2744b4) +- Whitelist 'include_segment_file_sizes' in Indices/Stats endpoint [[2c6cd30]](http://github.com/elasticsearch/elasticsearch-php/commit/2c6cd30) +- Automatically set Content-type and Accept headers [[3d52e11]](http://github.com/elasticsearch/elasticsearch-php/commit/3d52e11) +- Put back UpdateByQuery (#531) [[6f4394f]](http://github.com/elasticsearch/elasticsearch-php/commit/6f4394f) +- Add 'human' param to global whitelist [[d16e541]](http://github.com/elasticsearch/elasticsearch-php/commit/d16e541) + +### Testing + +- [TEST] Tidy up version check [[ec6b832]](http://github.com/elasticsearch/elasticsearch-php/commit/ec6b832) + +### Docs + +- [DOCS] Update search-operations.asciidoc (#520) [[4aba49b]](http://github.com/elasticsearch/elasticsearch-php/commit/4aba49b) +- [DOCS] fix broken links to breaking changes docs [[7d1b0ec]](http://github.com/elasticsearch/elasticsearch-php/commit/7d1b0ec) + +## Release 5.1.0 + +- Catch additional exceptions in the ping function for those who use multiple nodes in their connection pool. [[ffe0510]](http://github.com/elasticsearch/elasticsearch-php/commit/ffe0510) +- Use `array_diff` to Check Endpoint Parameters (#514) [[46f7f36]](http://github.com/elasticsearch/elasticsearch-php/commit/46f7f36) +- Re-Add the DeleteByQuery Functionality (#513) [[b262dca]](http://github.com/elasticsearch/elasticsearch-php/commit/b262dca) +- Add 'full_id' to Cat/Nodes endpoint [[f32cc54]](http://github.com/elasticsearch/elasticsearch-php/commit/f32cc54) +- add ClientBuilder->setConnectionParams() (#507) [[3923432]](http://github.com/elasticsearch/elasticsearch-php/commit/3923432) +- Add new (undocumented) PHP-7 JSON error codes, better unknown handling [[0a7fd55]](http://github.com/elasticsearch/elasticsearch-php/commit/0a7fd55) +- Add ext-json version constraint, update some docs [[ca2791a]](http://github.com/elasticsearch/elasticsearch-php/commit/ca2791a) +- Add catch-all in exception handling [[eb4117c]](http://github.com/elasticsearch/elasticsearch-php/commit/eb4117c) +- Revert "Simplify error parsing now that we don't support <2.0 errors" [[fd38538]](http://github.com/elasticsearch/elasticsearch-php/commit/fd38538) + +### Testing + +- [TEST] Make sure property_exists calls only ocurr on objects [[30baa0d]](http://github.com/elasticsearch/elasticsearch-php/commit/30baa0d) +- [TEST] Tweak travis to install better ext-json [[3409a81]](http://github.com/elasticsearch/elasticsearch-php/commit/3409a81) +- [TEST] Mute rollover test temporarily [[2316d33]](http://github.com/elasticsearch/elasticsearch-php/commit/2316d33) +- [TEST] Add support for warning header checks [[ac1b053]](http://github.com/elasticsearch/elasticsearch-php/commit/ac1b053) + +### Documentation + +- [DOCS] Removed unwanted ) and added proper formatting (#497) [[8187fdd]](http://github.com/elasticsearch/elasticsearch-php/commit/8187fdd) +- [DOCS] Update index-operations.asciidoc (#496) [[a4dd09f]](http://github.com/elasticsearch/elasticsearch-php/commit/a4dd09f) +- [DOCS] Update version in monolog configuration. (#489) [[90fbd53]](http://github.com/elasticsearch/elasticsearch-php/commit/90fbd53) +- [DOCS] Update php-version-requirement.asciidoc (#491) [[4951439]](http://github.com/elasticsearch/elasticsearch-php/commit/4951439) +- [DOCS] "password" param should be "password" [[8ee2bc9]](http://github.com/elasticsearch/elasticsearch-php/commit/8ee2bc9) +- More asciidoc tweaks [[451f985]](http://github.com/elasticsearch/elasticsearch-php/commit/451f985) +- Asciidoc != markdown [[8a816c1]](http://github.com/elasticsearch/elasticsearch-php/commit/8a816c1) +- [Docs] More 5.0 readme tweaks [[a60dd09]](http://github.com/elasticsearch/elasticsearch-php/commit/a60dd09) +- [DOCS] 5.0 doc updates, readme, breaking changes [[6fb6421]](http://github.com/elasticsearch/elasticsearch-php/commit/6fb6421) + + +## Release 5.0.0 + +Woo! + +### New Endpoints +- Add Cat/Tasks endpoint [[42856dc]](http://github.com/elasticsearch/elasticsearch-php/commit/42856dc) +- Add Reindex endpoint [[d2484c7]](http://github.com/elasticsearch/elasticsearch-php/commit/d2484c7) +- Add Indices/Shrink endpoint [[b6b97a4]](http://github.com/elasticsearch/elasticsearch-php/commit/b6b97a4) +- Add Indices/Rollover endpoint [[1ba8299]](http://github.com/elasticsearch/elasticsearch-php/commit/1ba8299) + +### Removals/BWC Breaks/Deprecations + +- Indices/Optimize endpoint has been removed in 5.0 [[4f0b9da]](http://github.com/elasticsearch/elasticsearch-php/commit/4f0b9da) +- Warmers have been removed in 5.0 [[ef24d5d]](http://github.com/elasticsearch/elasticsearch-php/commit/ef24d5d) +- Deprecate various Percolate endpoints [[959eee5]](http://github.com/elasticsearch/elasticsearch-php/commit/959eee5) +- Remove old `percolate` parameter on docblocks [[f64345d]](http://github.com/elasticsearch/elasticsearch-php/commit/f64345d) +- SearchExists Endpoint removed in 5.0 [[6dfc6a0]](http://github.com/elasticsearch/elasticsearch-php/commit/6dfc6a0) +- [Internal BWC Break] Add better ability to inject namespaces [[b1a27b7]](http://github.com/elasticsearch/elasticsearch-php/commit/b1a27b7) +- [Internal BWC Break] Refactor to remove Transport dependence in endpoints [[ecd454c]](http://github.com/elasticsearch/elasticsearch-php/commit/ecd454c) +- [BWC Break] Remove MLT endpoint [[38c05da]](http://github.com/elasticsearch/elasticsearch-php/commit/38c05da) +- [BWC Break] Remove DeleteByQuery endpoint [[9f3776a]](http://github.com/elasticsearch/elasticsearch-php/commit/9f3776a) +- [BWC Break] Rename internal TermVector -> TermVectors, remove old public TermVector [[cbe8619]](http://github.com/elasticsearch/elasticsearch-php/commit/cbe8619) +- [BWC] Add getPath() method to ConnectionInterface [[8bcf1a8]](http://github.com/elasticsearch/elasticsearch-php/commit/8bcf1a8) +- [BWC] Add getUserPass() method to ConnectionInterface [[586fbdb]](http://github.com/elasticsearch/elasticsearch-php/commit/586fbdb) +- [BWC] Add getHost() method to ConnectionInterface [[445fdea]](http://github.com/elasticsearch/elasticsearch-php/commit/445fdea) +- Tasks/List and Tasks/Get are now separate endpoints [[e0cc5f9]](http://github.com/elasticsearch/elasticsearch-php/commit/752d5a2) + +### Updated/Added whitelist params + +- Add Ingest namespace and endpoints [[7b87954]](http://github.com/elasticsearch/elasticsearch-php/commit/7b87954) +- Add `pipeline` parameter to Bulk endpoint whitelist [[3fa1c51]](http://github.com/elasticsearch/elasticsearch-php/commit/3fa1c51) +- Add `pipeline` to Index endpoint [[db5d794]](http://github.com/elasticsearch/elasticsearch-php/commit/db5d794) +- Add `include_defaults` param to Indices/GetSettings whitelist [[496071c]](http://github.com/elasticsearch/elasticsearch-php/commit/496071c) +- Add `preserve_existing` param to Indices/PutSettings whitelist [[69389fc]](http://github.com/elasticsearch/elasticsearch-php/commit/69389fc) +- Add Cluster/AllocationExplain endpoint [[f9c297c]](http://github.com/elasticsearch/elasticsearch-php/commit/f9c297c) +- Add Ingest namespace and endpoints [[66c851f]](http://github.com/elasticsearch/elasticsearch-php/commit/66c851f) +- Add missing params to Analyze endpoint: `char_filter`, `format`, `attributes`, `explain` [[8a0a932]](http://github.com/elasticsearch/elasticsearch-php/commit/8a0a932) +- `filters` is now `filter` in Analyze endpoint [[94dbb15]](http://github.com/elasticsearch/elasticsearch-php/commit/94dbb15) +- Add `size` param to Cat/Threadpool whitelist [[bece0e5]](http://github.com/elasticsearch/elasticsearch-php/commit/bece0e5) +- Add `task_id` to Tasks/Get whitelist [[6a315e0]](http://github.com/elasticsearch/elasticsearch-php/commit/6a315e0) +- Add `docvalue_fields` to Search whitelist, remove `fields` [[63ff8c5]](http://github.com/elasticsearch/elasticsearch-php/commit/63ff8c5) +- Add `format` to Cat/Aliases whitelist [[68630a0]](http://github.com/elasticsearch/elasticsearch-php/commit/68630a0) +- Add 'thread_pool_patterns' parameter to Cat\Threadpool endpoint [[c0820dc]](http://github.com/elasticsearch/elasticsearch-php/commit/c0820dc) +- Add 's' sort param to all Cat endpoints [[87f23a1]](http://github.com/elasticsearch/elasticsearch-php/commit/87f23a1) +- Add '_source' to Update whitelist [[d33be49]](http://github.com/elasticsearch/elasticsearch-php/commit/d33be49) +- Add 'ignore_unavailable' to Snapshot/Status whitelist [[f90c2dd]](http://github.com/elasticsearch/elasticsearch-php/commit/f90c2dd) +- Add 'ignore_unavailable' to Snapshot/Get whitelist [[93c4f22]](http://github.com/elasticsearch/elasticsearch-php/commit/93c4f22) +- Add 'stored_fields' to Mget whitelist [[054ebed]](http://github.com/elasticsearch/elasticsearch-php/commit/054ebed) +- Add 'wait_for_no_relocating_shards' to Cluster/Health whitelist [[8448f99]](http://github.com/elasticsearch/elasticsearch-php/commit/8448f99) +- Add 'health' to Cat/Indices whitelist [[06a3bf5]](http://github.com/elasticsearch/elasticsearch-php/commit/06a3bf5) +- Add '_source_include', '_source_exclude', 'pipeline' params to Bulk whitelist [[3ca12f4]](http://github.com/elasticsearch/elasticsearch-php/commit/3ca12f4) +- Add 'stored_fields' to Get Endpoint [[c57a5a4]](http://github.com/elasticsearch/elasticsearch-php/commit/c57a5a4) +- Add '_source' to Bulk endpoint whitelist [[35b7087]](http://github.com/elasticsearch/elasticsearch-php/commit/35b7087) + +### Documentation + +- [DOCS] Usage example for creating ClientBuilder fixed (#406) [[6a868ea]](http://github.com/elasticsearch/elasticsearch-php/commit/6a868ea) +- [Docs] Fix typo. (#409) [[b013ab0]](http://github.com/elasticsearch/elasticsearch-php/commit/b013ab0) +- Docs: Fixed broken link. [[17a4ed7]](http://github.com/elasticsearch/elasticsearch-php/commit/17a4ed7) +- [DOCS] Rebuild auto-generated docs [[2904d7a]](http://github.com/elasticsearch/elasticsearch-php/commit/2904d7a) +- Add script to generate docs [[4ce648c]](http://github.com/elasticsearch/elasticsearch-php/commit/4ce648c) +- Update Readme with 5.0 branching information [[ddb8ecd]](http://github.com/elasticsearch/elasticsearch-php/commit/ddb8ecd) +- [DOCS] Update URL/Email in class-level doc blocks [[8238cb3]](http://github.com/elasticsearch/elasticsearch-php/commit/8238cb3) +- [DOCS] Update copyright year in licenses [[fcc4ad6]](http://github.com/elasticsearch/elasticsearch-php/commit/fcc4ad6) +- [DOCS] Add Breaking Changes list for 5.0 [[65953ac]](http://github.com/elasticsearch/elasticsearch-php/commit/65953ac) +- [DOCS] add getSource method to the readme (#465) [[90cbdfb]](http://github.com/elasticsearch/elasticsearch-php/commit/90cbdfb) +- [DOCS] Replace deprecated filtered query with boolean [[3b81615]](http://github.com/elasticsearch/elasticsearch-php/commit/3b81615) +- [DOCS] Add Plastic Laravel integration to community page [[e4530a7]](http://github.com/elasticsearch/elasticsearch-php/commit/e4530a7) +- [DOCS] Fix return type in docblock for all Exists* endpoints [[498c003]](http://github.com/elasticsearch/elasticsearch-php/commit/498c003) +- Add autogenerated reference documentation [[bd64d52]](http://github.com/elasticsearch/elasticsearch-php/commit/bd64d52) +- [DOCS] Regenerate reference docs [[030d96e]](http://github.com/elasticsearch/elasticsearch-php/commit/030d96e) + +### Cleanup + +- Remove benchmark autoload [[74b5ad9]](http://github.com/elasticsearch/elasticsearch-php/commit/74b5ad9) +- Remove old rest-spec parser [[07754c4]](http://github.com/elasticsearch/elasticsearch-php/commit/07754c4) +- Remove unused benchmarks [[20a75b1]](http://github.com/elasticsearch/elasticsearch-php/commit/20a75b1) +- Automated PSR-2 style cleanup [[fbe6f92]](http://github.com/elasticsearch/elasticsearch-php/commit/fbe6f92) +- Tweak script for new cli format [[4adbe94]](http://github.com/elasticsearch/elasticsearch-php/commit/4adbe94) +- Fix Indices/Flush after autogeneration [[d56e2c4]](http://github.com/elasticsearch/elasticsearch-php/commit/d56e2c4) +- Tweak SpecParser template [[8341c4c]](http://github.com/elasticsearch/elasticsearch-php/commit/8341c4c) +- Added output folder from SpecParser to gitignore [[cfd49ee]](http://github.com/elasticsearch/elasticsearch-php/commit/cfd49ee) +- Updated ParseSpec to be able to run from console and use new api path [[e0cc5f9]](http://github.com/elasticsearch/elasticsearch-php/commit/d44a323) + +### Bugfixes and Misc. + +- Split Create out to its own internal endpoint for simplicity [[9eb573a]](http://github.com/elasticsearch/elasticsearch-php/commit/9eb573a) +- Cat/Snapshots 'repository' param is not in-fact required, despite spec [[6c77f62]](http://github.com/elasticsearch/elasticsearch-php/commit/6c77f62) +- Fix error handler when no structured error is present [[f380a69]](http://github.com/elasticsearch/elasticsearch-php/commit/f380a69) +- add JSON_PRESERVE_ZERO_FRACTION for Json_encode (#481) [[2ab3971]](http://github.com/elasticsearch/elasticsearch-php/commit/2ab3971) +- Simplify error parsing now that we don't support <2.0 errors [[a6d896b]](http://github.com/elasticsearch/elasticsearch-php/commit/a6d896b) +- Added support for PHP 7.1 (#474) [[864d4d3]](http://github.com/elasticsearch/elasticsearch-php/commit/864d4d3) +- Type exists URI has changed to index/_mapping/type [[dd63eaa]](http://github.com/elasticsearch/elasticsearch-php/commit/dd63eaa) +- Index creation only accepts PUT verbs now [[9c620c2]](http://github.com/elasticsearch/elasticsearch-php/commit/9c620c2) +- Update SearchResponseIterator to remove old-style scan/scroll flow [[72f3b15]](http://github.com/elasticsearch/elasticsearch-php/commit/72f3b15) +- Add "extended" host configuration syntax [[a0ddad1]](http://github.com/elasticsearch/elasticsearch-php/commit/a0ddad1) +- Allow ConnectionFactory to be override (#456) [[cc2a5fe]](http://github.com/elasticsearch/elasticsearch-php/commit/cc2a5fe) +- Special-case unwrapping for async methods that use Exist* endpoints [[347e5c5]](http://github.com/elasticsearch/elasticsearch-php/commit/347e5c5) +- composer: bump min version to PHP 5.6 (#451) [[6648646]](http://github.com/elasticsearch/elasticsearch-php/commit/6648646) +- Allow to get multiple pipelines without id (#453) [[c7f737b]](http://github.com/elasticsearch/elasticsearch-php/commit/c7f737b) +- Split, refactor and fix some tests (#447) [[68e819b]](http://github.com/elasticsearch/elasticsearch-php/commit/68e819b) +- Tests cleaned up a little [[afc9af0]](http://github.com/elasticsearch/elasticsearch-php/commit/afc9af0) +- Fix doc output on github [[62d6132]](http://github.com/elasticsearch/elasticsearch-php/commit/62d6132) +- (pr/445) Move resultOrFuture from endpoint to transport [[80bfeea]](http://github.com/elasticsearch/elasticsearch-php/commit/80bfeea) +- Manually convert true/false to "true"/"false" before http_build_query() [[bef93cb]](http://github.com/elasticsearch/elasticsearch-php/commit/bef93cb) +- getApiPath function returns path without trailing slash [[8bcfaf0]](http://github.com/elasticsearch/elasticsearch-php/commit/8bcfaf0) +- Use valid SPDX license identifier [[963e635]](http://github.com/elasticsearch/elasticsearch-php/commit/963e635) +- Fix bug when Create is called with an stdClass body [[adcaa2c]](http://github.com/elasticsearch/elasticsearch-php/commit/adcaa2c) +- Fix comment tag [[3b8e918]](http://github.com/elasticsearch/elasticsearch-php/commit/3b8e918) +- Add .github templates [[104a7ea]](http://github.com/elasticsearch/elasticsearch-php/commit/104a7ea) + +### Testing + +Lots of work re-working the REST Yaml test framework, getting travis to play nicely with Java8, and misc +tweaks over time. The test framework is in much better shape, largely thanks to help from community member @joelwurtz! + +- [TEST] Add 5.x to test matrix [[77b548d]](http://github.com/elasticsearch/elasticsearch-php/commit/77b548d) +- [TEST] Fixup server startup [[18ea943]](http://github.com/elasticsearch/elasticsearch-php/commit/18ea943) +- [TEST] Add ignore to custom param since ES now validates extraneous uri params [[61f62d8]](http://github.com/elasticsearch/elasticsearch-php/commit/61f62d8) +- [TEST] Regex to detect "stashed" values is not useful, throws false-positives [[7ff9b20]](http://github.com/elasticsearch/elasticsearch-php/commit/7ff9b20) +- [TEST] (Fix) Better context to true/false failures [[a9ee47d]](http://github.com/elasticsearch/elasticsearch-php/commit/a9ee47d) +- [TEST] Better context to true/false failures [[9df055b]](http://github.com/elasticsearch/elasticsearch-php/commit/9df055b) +- [TEST] Add 'indices.shrink/10_basic.yaml' to temp blacklist [[aa93f39]](http://github.com/elasticsearch/elasticsearch-php/commit/aa93f39) +- [TEST] Only run sync tests on Travis [[2f7b863]](http://github.com/elasticsearch/elasticsearch-php/commit/2f7b863) +- [TEST] Update travis config to use ES 5.0 branch [[cbad348]](http://github.com/elasticsearch/elasticsearch-php/commit/cbad348) +- [TEST] Add back accidentally deleted annotations [[c7f8c06]](http://github.com/elasticsearch/elasticsearch-php/commit/c7f8c06) +- [TEST] Better snapshot/repo clearing [[cc3a40d]](http://github.com/elasticsearch/elasticsearch-php/commit/cc3a40d) +- [TEST] output tweaks for better debugging, add temporary blacklist for fatal parsing files [[cb1956b]](http://github.com/elasticsearch/elasticsearch-php/commit/cb1956b) +- [TEST] Tweak verbosity of tests [[e0cc5f9]](http://github.com/elasticsearch/elasticsearch-php/commit/e0cc5f9) +- [TEST] No need to test below PHP 5.6 on master [[7aad25a]](http://github.com/elasticsearch/elasticsearch-php/commit/7aad25a) +- [TEST] Allow hhvm to fail [[fe6993d]](http://github.com/elasticsearch/elasticsearch-php/commit/fe6993d) +- [TEST] Remove ES host/port so it starts in "dev" mode [[c91242d]](http://github.com/elasticsearch/elasticsearch-php/commit/c91242d) +- [TEST] Bump travis file descriptors [[b24fc85]](http://github.com/elasticsearch/elasticsearch-php/commit/b24fc85) +- [TEST] Bump travis file descriptors [[5cd7c37]](http://github.com/elasticsearch/elasticsearch-php/commit/5cd7c37) +- [TEST] Bump travis file descriptors [[65f2eb9]](http://github.com/elasticsearch/elasticsearch-php/commit/65f2eb9) +- [TEST] (Nuclear option) more Java8 JRE tweaks [[00ce1df]](http://github.com/elasticsearch/elasticsearch-php/commit/00ce1df) +- [TEST] (Hundred and one time's a charm) more Java8 JRE tweaks [[c38e5fe]](http://github.com/elasticsearch/elasticsearch-php/commit/c38e5fe) +- [TEST] (Hundredth time's a charm) more Java8 JRE tweaks [[2cef10d]](http://github.com/elasticsearch/elasticsearch-php/commit/2cef10d) +- [TEST] (And yet) more Java8 JRE tweaks [[ecb44ab]](http://github.com/elasticsearch/elasticsearch-php/commit/ecb44ab) +- [TEST] (Yet) more Java8 JRE tweaks [[afc6b8a]](http://github.com/elasticsearch/elasticsearch-php/commit/afc6b8a) +- [TEST] More Java8 JRE tweaks [[0567579]](http://github.com/elasticsearch/elasticsearch-php/commit/0567579) +- [TEST] More Java8 JRE tweaks [[9869977]](http://github.com/elasticsearch/elasticsearch-php/commit/9869977) +- [TEST] Print java version for debug [[9dec069]](http://github.com/elasticsearch/elasticsearch-php/commit/9dec069) +- [TEST] Manually configure Java8 JRE [[1fd3612]](http://github.com/elasticsearch/elasticsearch-php/commit/1fd3612) +- Revert "[TEST] Use Java 8 for tests" [[6fc4c5c]](http://github.com/elasticsearch/elasticsearch-php/commit/6fc4c5c) +- [TEST] Use Java 8 for tests [[2d59159]](http://github.com/elasticsearch/elasticsearch-php/commit/2d59159) +- [TEST] Replace stash before finding nested variables in match [[2dae755]](http://github.com/elasticsearch/elasticsearch-php/commit/2dae755) +- [TEST] Convert `tasks.list` to `tasks.get` because `list` is a reserved word [[e0956a5]](http://github.com/elasticsearch/elasticsearch-php/commit/e0956a5) +- [TEST] Small tweak to timestamp regex [[d5e50c1]](http://github.com/elasticsearch/elasticsearch-php/commit/d5e50c1) +- [TEST] return second level of exception message if possible, for further testing [[a76cbf2]](http://github.com/elasticsearch/elasticsearch-php/commit/a76cbf2) +- [TEST] Master tests only track ES-master (5.0 alpha) now [[76e621c]](http://github.com/elasticsearch/elasticsearch-php/commit/76e621c) +- [TEST] Fix jq syntax for numeric branches (e.g. `2.2`) [[eb48dab]](http://github.com/elasticsearch/elasticsearch-php/commit/eb48dab) +- [TEST] Automate snapshot retrieval [[c08b08b]](http://github.com/elasticsearch/elasticsearch-php/commit/c08b08b) +- [TEST] Update snapshot download script for 2.2 [[350cd6e]](http://github.com/elasticsearch/elasticsearch-php/commit/350cd6e) +- [TEST] Update travis matrix [[04bcf81]](http://github.com/elasticsearch/elasticsearch-php/commit/04bcf81) +- [TEST] Invoke phpunit dependency instead of travis phpunit.phar [[f9e0d99]](http://github.com/elasticsearch/elasticsearch-php/commit/f9e0d99) +- Revert "[TEST] fix object notation" [[b966328]](http://github.com/elasticsearch/elasticsearch-php/commit/b966328) +- Make test fail on Yaml parse error [[f1b3adb]](http://github.com/elasticsearch/elasticsearch-php/commit/f1b3adb) +- Only show log when test fails [[f2acb43]](http://github.com/elasticsearch/elasticsearch-php/commit/f2acb43) + diff --git a/vendor/elasticsearch/elasticsearch/LICENSE b/vendor/elasticsearch/elasticsearch/LICENSE new file mode 100644 index 0000000..0f773cc --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/LICENSE @@ -0,0 +1,635 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + [This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your + freedom to share and change it. By contrast, the GNU General Public + Licenses are intended to guarantee your freedom to share and change + free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some + specially designated software packages--typically libraries--of the + Free Software Foundation and other authors who decide to use it. You + can use it too, but we suggest you first think carefully about whether + this license or the ordinary General Public License is the better + strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, + not price. Our General Public Licenses are designed to make sure that + you have the freedom to distribute copies of free software (and charge + for this service if you wish); that you receive source code or can get + it if you want it; that you can change the software and use pieces of + it in new free programs; and that you are informed that you can do + these things. + + To protect your rights, we need to make restrictions that forbid + distributors to deny you these rights or to ask you to surrender these + rights. These restrictions translate to certain responsibilities for + you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis + or for a fee, you must give the recipients all the rights that we gave + you. You must make sure that they, too, receive or can get the source + code. If you link other code with the library, you must provide + complete object files to the recipients, so that they can relink them + with the library after making changes to the library and recompiling + it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the + library, and (2) we offer you this license, which gives you legal + permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that + there is no warranty for the free library. Also, if the library is + modified by someone else and passed on, the recipients should know + that what they have is not the original version, so that the original + author's reputation will not be affected by problems that might be + introduced by others. + + Finally, software patents pose a constant threat to the existence of + any free program. We wish to make sure that a company cannot + effectively restrict the users of a free program by obtaining a + restrictive license from a patent holder. Therefore, we insist that + any patent license obtained for a version of the library must be + consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the + ordinary GNU General Public License. This license, the GNU Lesser + General Public License, applies to certain designated libraries, and + is quite different from the ordinary General Public License. We use + this license for certain libraries in order to permit linking those + libraries into non-free programs. + + When a program is linked with a library, whether statically or using + a shared library, the combination of the two is legally speaking a + combined work, a derivative of the original library. The ordinary + General Public License therefore permits such linking only if the + entire combination fits its criteria of freedom. The Lesser General + Public License permits more lax criteria for linking other code with + the library. + + We call this license the "Lesser" General Public License because it + does Less to protect the user's freedom than the ordinary General + Public License. It also provides other free software developers Less + of an advantage over competing non-free programs. These disadvantages + are the reason we use the ordinary General Public License for many + libraries. However, the Lesser license provides advantages in certain + special circumstances. + + For example, on rare occasions, there may be a special need to + encourage the widest possible use of a certain library, so that it becomes + a de-facto standard. To achieve this, non-free programs must be + allowed to use the library. A more frequent case is that a free + library does the same job as widely used non-free libraries. In this + case, there is little to gain by limiting the free library to free + software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free + programs enables a greater number of people to use a large body of + free software. For example, permission to use the GNU C Library in + non-free programs enables many more people to use the whole GNU + operating system, as well as its variant, the GNU/Linux operating + system. + + Although the Lesser General Public License is Less protective of the + users' freedom, it does ensure that the user of a program that is + linked with the Library has the freedom and the wherewithal to run + that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and + modification follow. Pay close attention to the difference between a + "work based on the library" and a "work that uses the library". The + former contains code derived from the library, whereas the latter must + be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other + program which contains a notice placed by the copyright holder or + other authorized party saying it may be distributed under the terms of + this Lesser General Public License (also called "this License"). + Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data + prepared so as to be conveniently linked with application programs + (which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work + which has been distributed under these terms. A "work based on the + Library" means either the Library or any derivative work under + copyright law: that is to say, a work containing the Library or a + portion of it, either verbatim or with modifications and/or translated + straightforwardly into another language. (Hereinafter, translation is + included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for + making modifications to it. For a library, complete source code means + all the source code for all modules it contains, plus any associated + interface definition files, plus the scripts used to control compilation + and installation of the library. + + Activities other than copying, distribution and modification are not + covered by this License; they are outside its scope. The act of + running a program using the Library is not restricted, and output from + such a program is covered only if its contents constitute a work based + on the Library (independent of the use of the Library in a tool for + writing it). Whether that is true depends on what the Library does + and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's + complete source code as you receive it, in any medium, provided that + you conspicuously and appropriately publish on each copy an + appropriate copyright notice and disclaimer of warranty; keep intact + all the notices that refer to this License and to the absence of any + warranty; and distribute a copy of this License along with the + Library. + + You may charge a fee for the physical act of transferring a copy, + and you may at your option offer warranty protection in exchange for a + fee. + + 2. You may modify your copy or copies of the Library or any portion + of it, thus forming a work based on the Library, and copy and + distribute such modifications or work under the terms of Section 1 + above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + + These requirements apply to the modified work as a whole. If + identifiable sections of that work are not derived from the Library, + and can be reasonably considered independent and separate works in + themselves, then this License, and its terms, do not apply to those + sections when you distribute them as separate works. But when you + distribute the same sections as part of a whole which is a work based + on the Library, the distribution of the whole must be on the terms of + this License, whose permissions for other licensees extend to the + entire whole, and thus to each and every part regardless of who wrote + it. + + Thus, it is not the intent of this section to claim rights or contest + your rights to work written entirely by you; rather, the intent is to + exercise the right to control the distribution of derivative or + collective works based on the Library. + + In addition, mere aggregation of another work not based on the Library + with the Library (or with a work based on the Library) on a volume of + a storage or distribution medium does not bring the other work under + the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public + License instead of this License to a given copy of the Library. To do + this, you must alter all the notices that refer to this License, so + that they refer to the ordinary GNU General Public License, version 2, + instead of to this License. (If a newer version than version 2 of the + ordinary GNU General Public License has appeared, then you can specify + that version instead if you wish.) Do not make any other change in + these notices. + + Once this change is made in a given copy, it is irreversible for + that copy, so the ordinary GNU General Public License applies to all + subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of + the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or + derivative of it, under Section 2) in object code or executable form + under the terms of Sections 1 and 2 above provided that you accompany + it with the complete corresponding machine-readable source code, which + must be distributed under the terms of Sections 1 and 2 above on a + medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy + from a designated place, then offering equivalent access to copy the + source code from the same place satisfies the requirement to + distribute the source code, even though third parties are not + compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the + Library, but is designed to work with the Library by being compiled or + linked with it, is called a "work that uses the Library". Such a + work, in isolation, is not a derivative work of the Library, and + therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library + creates an executable that is a derivative of the Library (because it + contains portions of the Library), rather than a "work that uses the + library". The executable is therefore covered by this License. + Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file + that is part of the Library, the object code for the work may be a + derivative work of the Library even though the source code is not. + Whether this is true is especially significant if the work can be + linked without the Library, or if the work is itself a library. The + threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data + structure layouts and accessors, and small macros and small inline + functions (ten lines or less in length), then the use of the object + file is unrestricted, regardless of whether it is legally a derivative + work. (Executables containing this object code plus portions of the + Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may + distribute the object code for the work under the terms of Section 6. + Any executables containing that work also fall under Section 6, + whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or + link a "work that uses the Library" with the Library to produce a + work containing portions of the Library, and distribute that work + under terms of your choice, provided that the terms permit + modification of the work for the customer's own use and reverse + engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the + Library is used in it and that the Library and its use are covered by + this License. You must supply a copy of this License. If the work + during execution displays copyright notices, you must include the + copyright notice for the Library among them, as well as a reference + directing the user to the copy of this License. Also, you must do one + of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the + Library" must include any data and utility programs needed for + reproducing the executable from it. However, as a special exception, + the materials to be distributed need not include anything that is + normally distributed (in either source or binary form) with the major + components (compiler, kernel, and so on) of the operating system on + which the executable runs, unless that component itself accompanies + the executable. + + It may happen that this requirement contradicts the license + restrictions of other proprietary libraries that do not normally + accompany the operating system. Such a contradiction means you cannot + use both them and the Library together in an executable that you + distribute. + + 7. You may place library facilities that are a work based on the + Library side-by-side in a single library together with other library + facilities not covered by this License, and distribute such a combined + library, provided that the separate distribution of the work based on + the Library and of the other library facilities is otherwise + permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute + the Library except as expressly provided under this License. Any + attempt otherwise to copy, modify, sublicense, link with, or + distribute the Library is void, and will automatically terminate your + rights under this License. However, parties who have received copies, + or rights, from you under this License will not have their licenses + terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not + signed it. However, nothing else grants you permission to modify or + distribute the Library or its derivative works. These actions are + prohibited by law if you do not accept this License. Therefore, by + modifying or distributing the Library (or any work based on the + Library), you indicate your acceptance of this License to do so, and + all its terms and conditions for copying, distributing or modifying + the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the + Library), the recipient automatically receives a license from the + original licensor to copy, distribute, link with or modify the Library + subject to these terms and conditions. You may not impose any further + restrictions on the recipients' exercise of the rights granted herein. + You are not responsible for enforcing compliance by third parties with + this License. + + 11. If, as a consequence of a court judgment or allegation of patent + infringement or for any other reason (not limited to patent issues), + conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot + distribute so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you + may not distribute the Library at all. For example, if a patent + license would not permit royalty-free redistribution of the Library by + all those who receive copies directly or indirectly through you, then + the only way you could satisfy both it and this License would be to + refrain entirely from distribution of the Library. + + If any portion of this section is held invalid or unenforceable under any + particular circumstance, the balance of the section is intended to apply, + and the section as a whole is intended to apply in other circumstances. + + It is not the purpose of this section to induce you to infringe any + patents or other property right claims or to contest validity of any + such claims; this section has the sole purpose of protecting the + integrity of the free software distribution system which is + implemented by public license practices. Many people have made + generous contributions to the wide range of software distributed + through that system in reliance on consistent application of that + system; it is up to the author/donor to decide if he or she is willing + to distribute software through any other system and a licensee cannot + impose that choice. + + This section is intended to make thoroughly clear what is believed to + be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in + certain countries either by patents or by copyrighted interfaces, the + original copyright holder who places the Library under this License may add + an explicit geographical distribution limitation excluding those countries, + so that distribution is permitted only in or among countries not thus + excluded. In such case, this License incorporates the limitation as if + written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new + versions of the Lesser General Public License from time to time. + Such new versions will be similar in spirit to the present version, + but may differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the Library + specifies a version number of this License which applies to it and + "any later version", you have the option of following the terms and + conditions either of that version or of any later version published by + the Free Software Foundation. If the Library does not specify a + license version number, you may choose any version ever published by + the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free + programs whose distribution conditions are incompatible with these, + write to the author to ask for permission. For software which is + copyrighted by the Free Software Foundation, write to the Free + Software Foundation; we sometimes make exceptions for this. Our + decision will be guided by the two goals of preserving the free status + of all derivatives of our free software and of promoting the sharing + and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO + WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. + EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR + OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE + LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME + THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN + WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY + AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU + FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR + CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE + LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING + RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A + FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF + SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/vendor/elasticsearch/elasticsearch/NOTICE b/vendor/elasticsearch/elasticsearch/NOTICE new file mode 100644 index 0000000..3897fb5 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/NOTICE @@ -0,0 +1,32 @@ +Apache v2.0 Notice: + Copyright 2013-2014 Elasticsearch + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +LGPL v2.1 Notice: + Copyright (C) 2013-2014 Elasticsearch + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA \ No newline at end of file diff --git a/vendor/elasticsearch/elasticsearch/README.md b/vendor/elasticsearch/elasticsearch/README.md new file mode 100644 index 0000000..700bd9d --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/README.md @@ -0,0 +1,377 @@ +elasticsearch-php +================= + +[![Build Status](https://img.shields.io/travis/elastic/elasticsearch-php.svg?style=flat-square)](https://travis-ci.org/elastic/elasticsearch-php) + + +Official low-level client for Elasticsearch. Its goal is to provide common ground for all Elasticsearch-related code in PHP; because of this it tries to be opinion-free and very extendable. + +To maintain consistency across all the low-level clients (Ruby, Python, etc), clients accept simple associative arrays as parameters. All parameters, from the URI to the document body, are defined in the associative array. + + +Features +-------- + + - One-to-one mapping with REST API and other language clients + - Configurable, automatic discovery of cluster nodes + - Persistent, Keep-Alive connections (within the lifetime of the script) + - Load balancing (with pluggable selection strategy) across all available nodes. Defaults to round-robin + - Pluggable connection pools to offer different connection strategies + - Generalized, pluggable architecture - most components can be replaced with your own custom class if specialized behavior is required + - Option to use asyncronous future, which enables parallel execution of curl requests to multiple nodes + +Version Matrix +-------------- + +| Elasticsearch Version | Elasticsearch-PHP Branch | +| --------------------- | ------------------------ | +| >= 5.0 | 5.0 | +| >= 2.0, < 5.0 | 1.0 or 2.0 | +| >= 1.0, < 2.0 | 1.0 or 2.0 | +| <= 0.90.x | 0.4 | + + - If you are using Elasticsearch 5.0+ , use Elasticsearch-PHP 5.0 branch. + - If you are using Elasticsearch 1.x or 2.x, prefer using the Elasticsearch-PHP 2.0 branch. The 1.0 branch is compatible however. + - If you are using a version older than 1.0, you must install the `0.4` Elasticsearch-PHP branch. Since ES 0.90.x and below is now EOL, the corresponding `0.4` branch will not receive any more development or bugfixes. Please upgrade. + - You should never use Elasticsearch-PHP Master branch, as it tracks Elasticearch master and may contain incomplete features or breaks in backwards compat. Only use ES-PHP master if you are developing against ES master for some reason. + +Documentation +-------------- +[Full documentation can be found here.](http://www.elasticsearch.org/guide/en/elasticsearch/client/php-api/5.0/index.html) Docs are stored within the repo under /docs/, so if you see a typo or problem, please submit a PR to fix it! + +Installation via Composer +------------------------- +The recommended method to install _Elasticsearch-PHP_ is through [Composer](http://getcomposer.org). + +1. Add ``elasticsearch/elasticsearch`` as a dependency in your project's ``composer.json`` file (change version to suit your version of Elasticsearch): + + ```json + { + "require": { + "elasticsearch/elasticsearch": "~5.0" + } + } + ``` + +2. Download and install Composer: + + ```bash + curl -s http://getcomposer.org/installer | php + ``` + +3. Install your dependencies: + + ```bash + php composer.phar install --no-dev + ``` + +4. Require Composer's autoloader + + Composer also prepares an autoload file that's capable of autoloading all of the classes in any of the libraries that it downloads. To use it, just add the following line to your code's bootstrap process: + + ```php + build(); + ``` +You can find out more on how to install Composer, configure autoloading, and other best-practices for defining dependencies at [getcomposer.org](http://getcomposer.org). + +You'll notice that the installation command specified `--no-dev`. This prevents Composer from installing the various testing and development dependencies. For average users, there is no need to install the test suite (which also includes the complete source code of Elasticsearch). If you wish to contribute to development, just omit the `--no-dev` flag to be able to run tests. + +PHP Version Requirement +---- +Version 5.0 of this library requires at least PHP version 5.6.6 to function. In addition, it requires the native JSON +extension to be version 1.3.7 or higher. + +| PHP Version | Elasticsearch-PHP Branch | +| ----------- | ------------------------ | +| >= 5.6.6 | 5.0 | +| >= 5.4.0 | 2.0 | +| >= 5.3.9 | 0.4, 1.0 | + + +Quickstart +---- + + +### Index a document + +In elasticsearch-php, almost everything is configured by associative arrays. The REST endpoint, document and optional parameters - everything is an associative array. + +To index a document, we need to specify four pieces of information: index, type, id and a document body. This is done by +constructing an associative array of key:value pairs. The request body is itself an associative array with key:value pairs +corresponding to the data in your document: + +```php +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'id' => 'my_id', + 'body' => ['testField' => 'abc'] +]; + +$response = $client->index($params); +print_r($response); +``` + +The response that you get back indicates the document was created in the index that you specified. The response is an +associative array containing a decoded version of the JSON that Elasticsearch returns: + +```php +Array +( + [_index] => my_index + [_type] => my_type + [_id] => my_id + [_version] => 1 + [created] => 1 +) + +``` + +### Get a document + +Let's get the document that we just indexed. This will simply return the document: + +```php +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'id' => 'my_id' +]; + +$response = $client->get($params); +print_r($response); +``` + +The response contains some metadata (index, type, etc) as well as a `_source` field...this is the original document +that you sent to Elasticsearch. + +```php +Array +( + [_index] => my_index + [_type] => my_type + [_id] => my_id + [_version] => 1 + [found] => 1 + [_source] => Array + ( + [testField] => abc + ) + +) +``` + +If you want to retrieve the `_source` field directly, there is the `getSource` method: + +```php +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'id' => 'my_id' +]; + +$source = $client->getSource($params); +doSomething($source); +``` + +### Search for a document + +Searching is a hallmark of Elasticsearch, so let's perform a search. We are going to use the Match query as a demonstration: + +```php +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'body' => [ + 'query' => [ + 'match' => [ + 'testField' => 'abc' + ] + ] + ] +]; + +$response = $client->search($params); +print_r($response); +``` + +The response is a little different from the previous responses. We see some metadata (`took`, `timed_out`, etc) and +an array named `hits`. This represents your search results. Inside of `hits` is another array named `hits`, which contains +individual search results: + +```php +Array +( + [took] => 1 + [timed_out] => + [_shards] => Array + ( + [total] => 5 + [successful] => 5 + [failed] => 0 + ) + + [hits] => Array + ( + [total] => 1 + [max_score] => 0.30685282 + [hits] => Array + ( + [0] => Array + ( + [_index] => my_index + [_type] => my_type + [_id] => my_id + [_score] => 0.30685282 + [_source] => Array + ( + [testField] => abc + ) + ) + ) + ) +) +``` + +### Delete a document + +Alright, let's go ahead and delete the document that we added previously: + +```php +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'id' => 'my_id' +]; + +$response = $client->delete($params); +print_r($response); +``` + +You'll notice this is identical syntax to the `get` syntax. The only difference is the operation: `delete` instead of +`get`. The response will confirm the document was deleted: + +```php +Array +( + [found] => 1 + [_index] => my_index + [_type] => my_type + [_id] => my_id + [_version] => 2 +) +``` + + +### Delete an index + +Due to the dynamic nature of Elasticsearch, the first document we added automatically built an index with some default settings. Let's delete that index because we want to specify our own settings later: + +```php +$deleteParams = [ + 'index' => 'my_index' +]; +$response = $client->indices()->delete($deleteParams); +print_r($response); +``` + +The response: + + +```php +Array +( + [acknowledged] => 1 +) +``` + +### Create an index + +Now that we are starting fresh (no data or index), let's add a new index with some custom settings: + +```php +$params = [ + 'index' => 'my_index', + 'body' => [ + 'settings' => [ + 'number_of_shards' => 2, + 'number_of_replicas' => 0 + ] + ] +]; + +$response = $client->indices()->create($params); +print_r($response); +``` + +Elasticsearch will now create that index with your chosen settings, and return an acknowledgement: + +```php +Array +( + [acknowledged] => 1 +) +``` + + + +Wrap up +======= + +That was just a crash-course overview of the client and it's syntax. If you are familiar with elasticsearch, you'll notice that the methods are named just like REST endpoints. + +You'll also notice that the client is configured in a manner that facilitates easy discovery via the IDE. All core actions are available under the `$client` object (indexing, searching, getting, etc). Index and cluster management are located under the `$client->indices()` and `$client->cluster()` objects, respectively. + +Check out the rest of the [Documentation](http://www.elasticsearch.org/guide/en/elasticsearch/client/php-api/current/index.html) to see how the entire client works. + + +Available Licenses +------- + +Starting with version 1.3.1, Elasticsearch-PHP is available under two licenses: Apache v2.0 and LGPL v2.1. Versions +prior to 1.3.1 are still licensed with only Apache v2.0. + +The user may choose which license they wish to use. Since there is no discriminating executable or distribution bundle +to differentiate licensing, the user should document their license choice externally, in case the library is re-distributed. +If no explicit choice is made, assumption is that redistribution obeys rules of both licenses. + +### Contributions +All contributions to the library are to be so that they can be licensed under both licenses. + +Apache v2.0 License: +>Copyright 2013-2016 Elasticsearch +> +>Licensed under the Apache License, Version 2.0 (the "License"); +>you may not use this file except in compliance with the License. +>You may obtain a copy of the License at +> +> http://www.apache.org/licenses/LICENSE-2.0 +> +>Unless required by applicable law or agreed to in writing, software +>distributed under the License is distributed on an "AS IS" BASIS, +>WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +>See the License for the specific language governing permissions and +>limitations under the License. + +LGPL v2.1 Notice: +>Copyright (C) 2013-2016 Elasticsearch +> +>This library is free software; you can redistribute it and/or +>modify it under the terms of the GNU Lesser General Public +>License as published by the Free Software Foundation; either +>version 2.1 of the License, or (at your option) any later version. +> +>This library 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 +>Lesser General Public License for more details. +> +>You should have received a copy of the GNU Lesser General Public +>License along with this library; if not, write to the Free Software +>Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/vendor/elasticsearch/elasticsearch/composer.json b/vendor/elasticsearch/elasticsearch/composer.json new file mode 100644 index 0000000..ae6f450 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/composer.json @@ -0,0 +1,40 @@ +{ + "name": "elasticsearch/elasticsearch", + "description": "PHP Client for Elasticsearch", + "keywords": ["search","client", "elasticsearch"], + "type": "library", + "license": "Apache-2.0", + "authors": [ + { + "name": "Zachary Tong" + } + ], + "require": { + "php": "^5.6|^7.0", + "psr/log": "~1.0", + "guzzlehttp/ringphp" : "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.7|^5.4", + "mockery/mockery": "0.9.4", + "symfony/yaml": "^2.8", + "symfony/finder": "^2.8", + "cpliakas/git-wrapper": "~1.0", + "sami/sami": "~3.2", + "doctrine/inflector": "^1.1" + }, + "suggest": { + "ext-curl": "*", + "monolog/monolog": "Allows for client-level logging and tracing" + }, + "autoload": { + "psr-4": { + "Elasticsearch\\": "src/Elasticsearch/" + } + }, + "autoload-dev": { + "psr-4": { + "Elasticsearch\\Tests\\": "tests/Elasticsearch/Tests/" + } + } +} diff --git a/vendor/elasticsearch/elasticsearch/docs/breaking-changes.asciidoc b/vendor/elasticsearch/elasticsearch/docs/breaking-changes.asciidoc new file mode 100644 index 0000000..e6028aa --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/breaking-changes.asciidoc @@ -0,0 +1,21 @@ +== Breaking changes from 2.x + +- Indices/Analyze Endpoint: `filters` and `char_filters` URI parameters have been renamed to `filter` and `char_filter` respectively +- SearchExists endpoint has been removed (use `size=0` and `terminate_after=1` instead) +- Warmers have been removed because they are no longer useful +- Indices/Optimize Endpoint has been removed (use `_forcemerge` instead) +- MoreLikeThis (MLT) endpoint has been removed +- DeleteByQuery endpoint has been removed. +- Tasks/List and Tasks/Get are now separate endpoints (see: link:http://github.com/elasticsearch/elasticsearch-php/commit/752d5a2[e0cc5f9]) +- Client requires PHP 5.6.6 or higher + +=== Deprecations + +- Percolator endpoints are deprecated and will be removed in Elasticsearch 6.0 + +=== Internal BWC Breaks + +- Namespace injection has changed slightly. If you use custom namespaces, you'll need to update your code (see: Add better ability to inject namespaces link:http://github.com/elasticsearch/elasticsearch-php/commit/b1a27b7[b1a27b7]) +- Endpoints no longer use the Transport directly. If you use custom endpoints, you'll need to do some minor +refactoring (see: Refactor to remove Transport dependence in endpoints link:http://github.com/elasticsearch/elasticsearch-php/commit/ecd454c[ecd454c]) +- To facilitate testing and other features, the `ConnectionInterface` has expanded to obtain some more methods (link:http://github.com/elasticsearch/elasticsearch-php/commit/8bcf1a8[getPath()], link:http://github.com/elasticsearch/elasticsearch-php/commit/586fbdb[getUserPass()], link:http://github.com/elasticsearch/elasticsearch-php/commit/445fdea[getHost()]) diff --git a/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Client.asciidoc b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Client.asciidoc new file mode 100644 index 0000000..6704ef2 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Client.asciidoc @@ -0,0 +1,1174 @@ + + +[[Elasticsearch_Client]] +=== Elasticsearch\Client + + + +Class Client + + +*Methods* + +The class defines the following methods: + +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> + + + + +[[Elasticsearch_Clientinfo_info]] +.`info()` +**** +[source,php] +---- +/* +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->info($params); +---- +**** + + + +[[Elasticsearch_Clientping_ping]] +.`ping()` +**** +[source,php] +---- +/* +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->ping($params); +---- +**** + + + +[[Elasticsearch_Clientget_get]] +.`get()` +**** +[source,php] +---- +/* +$params['id'] = (string) The document ID (Required) + ['index'] = (string) The name of the index (Required) + ['type'] = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required) + ['ignore_missing'] = ?? + ['fields'] = (list) A comma-separated list of fields to return in the response + ['parent'] = (string) The ID of the parent document + ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + ['realtime'] = (boolean) Specify whether to perform the operation in realtime or search mode + ['refresh'] = (boolean) Refresh the shard containing the document before performing the operation + ['routing'] = (string) Specific routing value + ['_source'] = (list) True or false to return the _source field or not, or a list of fields to return + ['_source_exclude'] = (list) A list of fields to exclude from the returned _source field + ['_source_include'] = (list) A list of fields to extract and return from the _source field + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->get($params); +---- +**** + + + +[[Elasticsearch_ClientgetSource_getSource]] +.`getSource()` +**** +[source,php] +---- +/* +$params['id'] = (string) The document ID (Required) + ['index'] = (string) The name of the index (Required) + ['type'] = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required) + ['ignore_missing'] = ?? + ['parent'] = (string) The ID of the parent document + ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + ['realtime'] = (boolean) Specify whether to perform the operation in realtime or search mode + ['refresh'] = (boolean) Refresh the shard containing the document before performing the operation + ['routing'] = (string) Specific routing value + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->getSource($params); +---- +**** + + + +[[Elasticsearch_Clientdelete_delete]] +.`delete()` +**** +[source,php] +---- +/* +$params['id'] = (string) The document ID (Required) + ['index'] = (string) The name of the index (Required) + ['type'] = (string) The type of the document (Required) + ['consistency'] = (enum) Specific write consistency setting for the operation + ['parent'] = (string) ID of parent document + ['refresh'] = (boolean) Refresh the index after performing the operation + ['replication'] = (enum) Specific replication type + ['routing'] = (string) Specific routing value + ['timeout'] = (time) Explicit operation timeout + ['version_type'] = (enum) Specific version type + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->delete($params); +---- +**** + + + +[[Elasticsearch_Clientcount_count]] +.`count()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of indices to restrict the results + ['type'] = (list) A comma-separated list of types to restrict the results + ['min_score'] = (number) Include only documents with a specific `_score` value in the result + ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + ['routing'] = (string) Specific routing value + ['source'] = (string) The URL-encoded query definition (instead of using the request body) + ['body'] = (array) A query to restrict the results (optional) + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->count($params); +---- +**** + + + +[[Elasticsearch_ClientcountPercolate_countPercolate]] +.`countPercolate()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of indices to restrict the results + ['type'] = (list) A comma-separated list of types to restrict the results + ['id'] = (string) ID of document + ['ignore_unavailable'] = (boolean) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + ['routing'] = (string) Specific routing value + ['allow_no_indices'] = (boolean) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['body'] = (array) A query to restrict the results (optional) + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['percolate_index'] = (string) The index to count percolate the document into. Defaults to index. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->countPercolate($params); +---- +**** + + + +[[Elasticsearch_Clientpercolate_percolate]] +.`percolate()` +**** +[source,php] +---- +/* +$params['index'] = (string) The name of the index with a registered percolator query (Required) + ['type'] = (string) The document type (Required) + ['prefer_local'] = (boolean) With `true`, specify that a local shard should be used if available, with `false`, use a random shard (default: true) + ['body'] = (array) The document (`doc`) to percolate against registered queries; optionally also a `query` to limit the percolation to specific registered queries + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->percolate($params); +---- +**** + + + +[[Elasticsearch_Clientmpercolate_mpercolate]] +.`mpercolate()` +**** +[source,php] +---- +/* +$params['index'] = (string) Default index for items which don't provide one + ['type'] = (string) Default document type for items which don't provide one + ['ignore_unavailable'] = (boolean) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (boolean) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->mpercolate($params); +---- +**** + + + +[[Elasticsearch_Clienttermvectors_termvectors]] +.`termvectors()` +**** +[source,php] +---- +/* +$params['index'] = (string) Default index for items which don't provide one + ['type'] = (string) Default document type for items which don't provide one + ['term_statistics'] = (boolean) Specifies if total term frequency and document frequency should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + ['field_statistics'] = (boolean) Specifies if document count, sum of document frequencies and sum of total term frequencies should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + ['fields'] = (list) A comma-separated list of fields to return. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + ['offsets'] = (boolean) Specifies if term offsets should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + ['positions'] = (boolean) Specifies if term positions should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + ['payloads'] = (boolean) Specifies if term payloads should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\". + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->termvectors($params); +---- +**** + + + +[[Elasticsearch_Clientmtermvectors_mtermvectors]] +.`mtermvectors()` +**** +[source,php] +---- +/* +$params['index'] = (string) Default index for items which don't provide one + ['type'] = (string) Default document type for items which don't provide one + ['ids'] = (list) A comma-separated list of documents ids. You must define ids as parameter or set \"ids\" or \"docs\" in the request body + ['term_statistics'] = (boolean) Specifies if total term frequency and document frequency should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + ['field_statistics'] = (boolean) Specifies if document count, sum of document frequencies and sum of total term frequencies should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + ['fields'] = (list) A comma-separated list of fields to return. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + ['offsets'] = (boolean) Specifies if term offsets should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + ['positions'] = (boolean) Specifies if term positions should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + ['payloads'] = (boolean) Specifies if term payloads should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\". + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->mtermvectors($params); +---- +**** + + + +[[Elasticsearch_Clientexists_exists]] +.`exists()` +**** +[source,php] +---- +/* +$params['id'] = (string) The document ID (Required) + ['index'] = (string) The name of the index (Required) + ['type'] = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required) + ['parent'] = (string) The ID of the parent document + ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + ['realtime'] = (boolean) Specify whether to perform the operation in realtime or search mode + ['refresh'] = (boolean) Refresh the shard containing the document before performing the operation + ['routing'] = (string) Specific routing value + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->exists($params); +---- +**** + + + +[[Elasticsearch_Clientmget_mget]] +.`mget()` +**** +[source,php] +---- +/* +$params['index'] = (string) The name of the index + ['type'] = (string) The type of the document + ['fields'] = (list) A comma-separated list of fields to return in the response + ['parent'] = (string) The ID of the parent document + ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + ['realtime'] = (boolean) Specify whether to perform the operation in realtime or search mode + ['refresh'] = (boolean) Refresh the shard containing the document before performing the operation + ['routing'] = (string) Specific routing value + ['body'] = (array) Document identifiers; can be either `docs` (containing full document information) or `ids` (when index and type is provided in the URL. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->mget($params); +---- +**** + + + +[[Elasticsearch_Clientmsearch_msearch]] +.`msearch()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names to use as default + ['type'] = (list) A comma-separated list of document types to use as default + ['search_type'] = (enum) Search operation type + ['body'] = (array|string) The request definitions (metadata-search request definition pairs), separated by newlines + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->msearch($params); +---- +**** + + + +[[Elasticsearch_Clientcreate_create]] +.`create()` +**** +[source,php] +---- +/* +$params['index'] = (string) The name of the index (Required) + ['type'] = (string) The type of the document (Required) + ['id'] = (string) Specific document ID (when the POST method is used) + ['consistency'] = (enum) Explicit write consistency setting for the operation + ['parent'] = (string) ID of the parent document + ['refresh'] = (boolean) Refresh the index after performing the operation + ['replication'] = (enum) Specific replication type + ['routing'] = (string) Specific routing value + ['timeout'] = (time) Explicit operation timeout + ['timestamp'] = (time) Explicit timestamp for the document + ['ttl'] = (duration) Expiration time for the document + ['version'] = (number) Explicit version number for concurrency control + ['version_type'] = (enum) Specific version type + ['body'] = (array) The document + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->create($params); +---- +**** + + + +[[Elasticsearch_Clientbulk_bulk]] +.`bulk()` +**** +[source,php] +---- +/* +$params['index'] = (string) Default index for items which don't provide one + ['type'] = (string) Default document type for items which don't provide one + ['consistency'] = (enum) Explicit write consistency setting for the operation + ['refresh'] = (boolean) Refresh the index after performing the operation + ['replication'] = (enum) Explicitly set the replication type + ['fields'] = (list) Default comma-separated list of fields to return in the response for updates + ['body'] = (array) The document + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->bulk($params); +---- +**** + + + +[[Elasticsearch_Clientindex_index]] +.`index()` +**** +[source,php] +---- +/* +$params['index'] = (string) The name of the index (Required) + ['type'] = (string) The type of the document (Required) + ['id'] = (string) Specific document ID (when the POST method is used) + ['consistency'] = (enum) Explicit write consistency setting for the operation + ['op_type'] = (enum) Explicit operation type + ['parent'] = (string) ID of the parent document + ['refresh'] = (boolean) Refresh the index after performing the operation + ['replication'] = (enum) Specific replication type + ['routing'] = (string) Specific routing value + ['timeout'] = (time) Explicit operation timeout + ['timestamp'] = (time) Explicit timestamp for the document + ['ttl'] = (duration) Expiration time for the document + ['version'] = (number) Explicit version number for concurrency control + ['version_type'] = (enum) Specific version type + ['body'] = (array) The document + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->index($params); +---- +**** + + + +[[Elasticsearch_Clientreindex_reindex]] +.`reindex()` +**** +[source,php] +---- +/* +$params['refresh'] = (boolean) Should the effected indexes be refreshed? + ['timeout'] = (time) Time each individual bulk request should wait for shards that are unavailable + ['consistency'] = (enum) Explicit write consistency setting for the operation + ['wait_for_completion'] = (boolean) Should the request should block until the reindex is complete + ['requests_per_second'] = (float) The throttle for this request in sub-requests per second. 0 means set no throttle + ['body'] = (array) The search definition using the Query DSL and the prototype for the index request (Required) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->reindex($params); +---- +**** + + + +[[Elasticsearch_Clientsuggest_suggest]] +.`suggest()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names to restrict the operation; use `_all` or empty string to perform the operation on all indices + ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones + ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + ['routing'] = (string) Specific routing value + ['source'] = (string) The URL-encoded request definition (instead of using request body) + ['body'] = (array) The request definition + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->suggest($params); +---- +**** + + + +[[Elasticsearch_Clientexplain_explain]] +.`explain()` +**** +[source,php] +---- +/* +$params['id'] = (string) The document ID (Required) + ['index'] = (string) The name of the index (Required) + ['type'] = (string) The type of the document (Required) + ['analyze_wildcard'] = (boolean) Specify whether wildcards and prefix queries in the query string query should be analyzed (default: false) + ['analyzer'] = (string) The analyzer for the query string query + ['default_operator'] = (enum) The default operator for query string query (AND or OR) + ['df'] = (string) The default field for query string query (default: _all) + ['fields'] = (list) A comma-separated list of fields to return in the response + ['lenient'] = (boolean) Specify whether format-based query failures (such as providing text to a numeric field) should be ignored + ['lowercase_expanded_terms'] = (boolean) Specify whether query terms should be lowercased + ['parent'] = (string) The ID of the parent document + ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + ['q'] = (string) Query in the Lucene query string syntax + ['routing'] = (string) Specific routing value + ['source'] = (string) The URL-encoded query definition (instead of using the request body) + ['_source'] = (list) True or false to return the _source field or not, or a list of fields to return + ['_source_exclude'] = (list) A list of fields to exclude from the returned _source field + ['_source_include'] = (list) A list of fields to extract and return from the _source field + ['body'] = (string) The URL-encoded query definition (instead of using the request body) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->explain($params); +---- +**** + + + +[[Elasticsearch_Clientsearch_search]] +.`search()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices + ['type'] = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types + ['analyzer'] = (string) The analyzer to use for the query string + ['analyze_wildcard'] = (boolean) Specify whether wildcard and prefix queries should be analyzed (default: false) + ['default_operator'] = (enum) The default operator for query string query (AND or OR) + ['df'] = (string) The field to use as default where no field prefix is given in the query string + ['explain'] = (boolean) Specify whether to return detailed information about score computation as part of a hit + ['fields'] = (list) A comma-separated list of fields to return as part of a hit + ['from'] = (number) Starting offset (default: 0) + ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones + ['indices_boost'] = (list) Comma-separated list of index boosts + ['lenient'] = (boolean) Specify whether format-based query failures (such as providing text to a numeric field) should be ignored + ['lowercase_expanded_terms'] = (boolean) Specify whether query terms should be lowercased + ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + ['q'] = (string) Query in the Lucene query string syntax + ['query_cache'] = (boolean) Enable query cache for this request + ['request_cache'] = (boolean) Enable request cache for this request + ['routing'] = (list) A comma-separated list of specific routing values + ['scroll'] = (duration) Specify how long a consistent view of the index should be maintained for scrolled search + ['search_type'] = (enum) Search operation type + ['size'] = (number) Number of hits to return (default: 10) + ['sort'] = (list) A comma-separated list of : pairs + ['source'] = (string) The URL-encoded request definition using the Query DSL (instead of using request body) + ['_source'] = (list) True or false to return the _source field or not, or a list of fields to return + ['_source_exclude'] = (list) A list of fields to exclude from the returned _source field + ['_source_include'] = (list) A list of fields to extract and return from the _source field + ['stats'] = (list) Specific 'tag' of the request for logging and statistical purposes + ['suggest_field'] = (string) Specify which field to use for suggestions + ['suggest_mode'] = (enum) Specify suggest mode + ['suggest_size'] = (number) How many suggestions to return in response + ['suggest_text'] = (text) The source text for which the suggestions should be returned + ['timeout'] = (time) Explicit operation timeout + ['version'] = (boolean) Specify whether to return document version as part of a hit + ['body'] = (array|string) The search definition using the Query DSL + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->search($params); +---- +**** + + + +[[Elasticsearch_ClientsearchShards_searchShards]] +.`searchShards()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices + ['type'] = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types + ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + ['routing'] = (string) Specific routing value + ['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->searchShards($params); +---- +**** + + + +[[Elasticsearch_ClientsearchTemplate_searchTemplate]] +.`searchTemplate()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices + ['type'] = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->searchTemplate($params); +---- +**** + + + +[[Elasticsearch_Clientscroll_scroll]] +.`scroll()` +**** +[source,php] +---- +/* +$params['scroll_id'] = (string) The scroll ID for scrolled search + ['scroll'] = (duration) Specify how long a consistent view of the index should be maintained for scrolled search + ['body'] = (string) The scroll ID for scrolled search + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->scroll($params); +---- +**** + + + +[[Elasticsearch_ClientclearScroll_clearScroll]] +.`clearScroll()` +**** +[source,php] +---- +/* +$params['scroll_id'] = (string) The scroll ID for scrolled search + ['scroll'] = (duration) Specify how long a consistent view of the index should be maintained for scrolled search + ['body'] = (string) The scroll ID for scrolled search + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->clearScroll($params); +---- +**** + + + +[[Elasticsearch_Clientupdate_update]] +.`update()` +**** +[source,php] +---- +/* +$params['id'] = (string) Document ID (Required) + ['index'] = (string) The name of the index (Required) + ['type'] = (string) The type of the document (Required) + ['consistency'] = (enum) Explicit write consistency setting for the operation + ['fields'] = (list) A comma-separated list of fields to return in the response + ['lang'] = (string) The script language (default: mvel) + ['parent'] = (string) ID of the parent document + ['refresh'] = (boolean) Refresh the index after performing the operation + ['replication'] = (enum) Specific replication type + ['retry_on_conflict'] = (number) Specify how many times should the operation be retried when a conflict occurs (default: 0) + ['routing'] = (string) Specific routing value + ['script'] = () The URL-encoded script definition (instead of using request body) + ['timeout'] = (time) Explicit operation timeout + ['timestamp'] = (time) Explicit timestamp for the document + ['ttl'] = (duration) Expiration time for the document + ['version_type'] = (number) Explicit version number for concurrency control + ['body'] = (array) The request definition using either `script` or partial `doc` + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->update($params); +---- +**** + + + +[[Elasticsearch_ClientgetScript_getScript]] +.`getScript()` +**** +[source,php] +---- +/* +$params['id'] = (string) The script ID (Required) + ['lang'] = (string) The script language (Required) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->getScript($params); +---- +**** + + + +[[Elasticsearch_ClientdeleteScript_deleteScript]] +.`deleteScript()` +**** +[source,php] +---- +/* +$params['id'] = (string) The script ID (Required) + ['lang'] = (string) The script language (Required) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->deleteScript($params); +---- +**** + + + +[[Elasticsearch_ClientputScript_putScript]] +.`putScript()` +**** +[source,php] +---- +/* +$params['id'] = (string) The script ID (Required) + ['lang'] = (string) The script language (Required) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->putScript($params); +---- +**** + + + +[[Elasticsearch_ClientgetTemplate_getTemplate]] +.`getTemplate()` +**** +[source,php] +---- +/* +$params['id'] = (string) The search template ID (Required) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->getTemplate($params); +---- +**** + + + +[[Elasticsearch_ClientdeleteTemplate_deleteTemplate]] +.`deleteTemplate()` +**** +[source,php] +---- +/* +$params['id'] = (string) The search template ID (Required) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->deleteTemplate($params); +---- +**** + + + +[[Elasticsearch_ClientputTemplate_putTemplate]] +.`putTemplate()` +**** +[source,php] +---- +/* +$params['id'] = (string) The search template ID (Required) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->putTemplate($params); +---- +**** + + + +[[Elasticsearch_ClientfieldStats_fieldStats]] +.`fieldStats()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of indices to restrict the results + ['fields'] = (list) A comma-separated list of fields for to get field statistics for (min value, max value, and more) + ['level'] = (enum) Defines if field stats should be returned on a per index level or on a cluster wide level + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->fieldStats($params); +---- +**** + + + +[[Elasticsearch_ClientrenderSearchTemplate_renderSearchTemplate]] +.`renderSearchTemplate()` +**** +[source,php] +---- +/* +$params['id'] = (string) ID of the template to render + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->renderSearchTemplate($params); +---- +**** + + + +[[Elasticsearch_Clientindices_indices]] +.`indices()` +**** +[source,php] +---- +/* +Operate on the Indices Namespace of commands + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices(); +---- +**** + + + +[[Elasticsearch_Clientcluster_cluster]] +.`cluster()` +**** +[source,php] +---- +/* +Operate on the Cluster namespace of commands + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cluster(); +---- +**** + + + +[[Elasticsearch_Clientnodes_nodes]] +.`nodes()` +**** +[source,php] +---- +/* +Operate on the Nodes namespace of commands + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->nodes(); +---- +**** + + + +[[Elasticsearch_Clientsnapshot_snapshot]] +.`snapshot()` +**** +[source,php] +---- +/* +Operate on the Snapshot namespace of commands + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->snapshot(); +---- +**** + + + +[[Elasticsearch_Clientcat_cat]] +.`cat()` +**** +[source,php] +---- +/* +Operate on the Cat namespace of commands + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat(); +---- +**** + + + +[[Elasticsearch_Clientingest_ingest]] +.`ingest()` +**** +[source,php] +---- +/* +Operate on the Ingest namespace of commands + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->ingest(); +---- +**** + + + +[[Elasticsearch_Clienttasks_tasks]] +.`tasks()` +**** +[source,php] +---- +/* +Operate on the Tasks namespace of commands + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->tasks(); +---- +**** + + + +[[Elasticsearch_Client-call-_call]] +.`__call()` +**** +[source,php] +---- +/* +Catchall for registered namespaces + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->__call($name,$arguments); +---- +**** + + + +[[Elasticsearch_ClientextractArgument_extractArgument]] +.`extractArgument()` +**** +[source,php] +---- +/* +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->extractArgument($params,$arg); +---- +**** + + diff --git a/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/ClientBuilder.asciidoc b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/ClientBuilder.asciidoc new file mode 100644 index 0000000..63eacc9 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/ClientBuilder.asciidoc @@ -0,0 +1,342 @@ + + +[[Elasticsearch_ClientBuilder]] +=== Elasticsearch\ClientBuilder + + + +Class ClientBuilder + + +*Methods* + +The class defines the following methods: + +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> + + + +[[Elasticsearch_ClientBuildercreate_create]] +.`create()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuilderfromConfig_fromConfig]] +.`fromConfig()` +**** +[source,php] +---- +/* + Build a new client from the provided config. Hash keys +should correspond to the method name e.g. ['connectionPool'] +corresponds to setConnectionPool(). + ['body'] = (array) Request body +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuilderdefaultHandler_defaultHandler]] +.`defaultHandler()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildermultiHandler_multiHandler]] +.`multiHandler()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersingleHandler_singleHandler]] +.`singleHandler()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuilderdefaultLogger_defaultLogger]] +.`defaultLogger()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetConnectionFactory_setConnectionFactory]] +.`setConnectionFactory()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetConnectionPool_setConnectionPool]] +.`setConnectionPool()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetEndpoint_setEndpoint]] +.`setEndpoint()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuilderregisterNamespace_registerNamespace]] +.`registerNamespace()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetTransport_setTransport]] +.`setTransport()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetHandler_setHandler]] +.`setHandler()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetLogger_setLogger]] +.`setLogger()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetTracer_setTracer]] +.`setTracer()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetSerializer_setSerializer]] +.`setSerializer()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetHosts_setHosts]] +.`setHosts()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetRetries_setRetries]] +.`setRetries()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetSelector_setSelector]] +.`setSelector()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetSniffOnStart_setSniffOnStart]] +.`setSniffOnStart()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetSSLCert_setSSLCert]] +.`setSSLCert()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetSSLKey_setSSLKey]] +.`setSSLKey()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuildersetSSLVerification_setSSLVerification]] +.`setSSLVerification()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + + +[[Elasticsearch_ClientBuilderbuild_build]] +.`build()` +**** +[source,php] +---- +/* +*/ + +---- +**** + + diff --git a/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/CatNamespace.asciidoc b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/CatNamespace.asciidoc new file mode 100644 index 0000000..afd8914 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/CatNamespace.asciidoc @@ -0,0 +1,515 @@ + + +[[Elasticsearch_Namespaces_CatNamespace]] +=== Elasticsearch\Namespaces\CatNamespace + + + +Class CatNamespace + + +*Methods* + +The class defines the following methods: + +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> + + + +[[Elasticsearch_Namespaces_CatNamespacealiases_aliases]] +.`aliases()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->aliases($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespaceallocation_allocation]] +.`allocation()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['bytes'] = (enum) The unit in which to display byte values + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->allocation($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespacecount_count]] +.`count()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->count($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespacehealth_health]] +.`health()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['ts'] = (bool) Set to false to disable timestamping + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->health($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespacehelp_help]] +.`help()` +**** +[source,php] +---- +/* +$params['help'] = (bool) Return help information + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->help($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespaceindices_indices]] +.`indices()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['bytes'] = (enum) The unit in which to display byte values + ['pri'] = (bool) Set to true to return stats only for primary shards + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->indices($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespacemaster_master]] +.`master()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->master($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespacenodes_nodes]] +.`nodes()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->nodes($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespacenodeAttrs_nodeAttrs]] +.`nodeAttrs()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->nodeAttrs($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespacependingTasks_pendingTasks]] +.`pendingTasks()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->pendingTasks($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespacerecovery_recovery]] +.`recovery()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['bytes'] = (enum) The unit in which to display byte values + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->recovery($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespacerepositories_repositories]] +.`repositories()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->repositories($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespaceshards_shards]] +.`shards()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['bytes'] = (enum) The unit in which to display byte values + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->shards($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespacesnapshots_snapshots]] +.`snapshots()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['bytes'] = (enum) The unit in which to display byte values + ['repository'] = (string) Name of repository from which to fetch the snapshot information + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->snapshots($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespacethreadPool_threadPool]] +.`threadPool()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['full_id'] = (bool) Enables displaying the complete node ids + ['size'] = (enum) The multiplier in which to display values ([ "", "k", "m", "g", "t", "p" ]) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->threadPool($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespacefielddata_fielddata]] +.`fielddata()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['bytes'] = (enum) The unit in which to display byte values + ['fields'] = (list) A comma-separated list of fields to return the fielddata size + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->fielddata($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespaceplugins_plugins]] +.`plugins()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->plugins($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespacesegments_segments]] +.`segments()` +**** +[source,php] +---- +/* +$params['h'] = (list) Comma-separated list of column names to display + ['help'] = (bool) Return help information + ['v'] = (bool) Verbose mode. Display column headers + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->segments($params); +---- +**** + + + +[[Elasticsearch_Namespaces_CatNamespacetasks_tasks]] +.`tasks()` +**** +[source,php] +---- +/* +$params['format'] = (string) a short version of the Accept header, e.g. json, yaml + ['node_id'] = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes + ['format'] = (string) a short version of the Accept header, e.g. json, yaml + ['actions'] = (list) A comma-separated list of actions that should be returned. Leave empty to return all. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cat()->tasks($params); +---- +**** + + diff --git a/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/ClusterNamespace.asciidoc b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/ClusterNamespace.asciidoc new file mode 100644 index 0000000..562182c --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/ClusterNamespace.asciidoc @@ -0,0 +1,210 @@ + + +[[Elasticsearch_Namespaces_ClusterNamespace]] +=== Elasticsearch\Namespaces\ClusterNamespace + + + +Class ClusterNamespace + + +*Methods* + +The class defines the following methods: + +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> + + + +[[Elasticsearch_Namespaces_ClusterNamespacehealth_health]] +.`health()` +**** +[source,php] +---- +/* +$params['index'] = (string) Limit the information returned to a specific index + ['level'] = (enum) Specify the level of detail for returned information + ['local'] = (boolean) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['timeout'] = (time) Explicit operation timeout + ['wait_for_active_shards'] = (number) Wait until the specified number of shards is active + ['wait_for_nodes'] = (number) Wait until the specified number of nodes is available + ['wait_for_relocating_shards'] = (number) Wait until the specified number of relocating shards is finished + ['wait_for_status'] = (enum) Wait until cluster is in a specific state + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cluster()->health($params); +---- +**** + + + +[[Elasticsearch_Namespaces_ClusterNamespacereroute_reroute]] +.`reroute()` +**** +[source,php] +---- +/* +$params['dry_run'] = (boolean) Simulate the operation only and return the resulting state + ['filter_metadata'] = (boolean) Don't return cluster state metadata (default: false) + ['body'] = (boolean) Don't return cluster state metadata (default: false) + ['explain'] = (boolean) Return an explanation of why the commands can or cannot be executed + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cluster()->reroute($params); +---- +**** + + + +[[Elasticsearch_Namespaces_ClusterNamespacestate_state]] +.`state()` +**** +[source,php] +---- +/* +$params['filter_blocks'] = (boolean) Do not return information about blocks + ['filter_index_templates'] = (boolean) Do not return information about index templates + ['filter_indices'] = (list) Limit returned metadata information to specific indices + ['filter_metadata'] = (boolean) Do not return information about indices metadata + ['filter_nodes'] = (boolean) Do not return information about nodes + ['filter_routing_table'] = (boolean) Do not return information about shard allocation (`routing_table` and `routing_nodes`) + ['local'] = (boolean) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Specify timeout for connection to master + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cluster()->state($params); +---- +**** + + + +[[Elasticsearch_Namespaces_ClusterNamespacestats_stats]] +.`stats()` +**** +[source,php] +---- +/* +$params['flat_settings'] = (boolean) Return settings in flat format (default: false) + ['human'] = (boolean) Whether to return time and byte values in human-readable format. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cluster()->stats($params); +---- +**** + + + +[[Elasticsearch_Namespaces_ClusterNamespaceputSettings_putSettings]] +.`putSettings()` +**** +[source,php] +---- +/* +$params['body'] = () + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cluster()->putSettings($params); +---- +**** + + + +[[Elasticsearch_Namespaces_ClusterNamespacegetSettings_getSettings]] +.`getSettings()` +**** +[source,php] +---- +/* +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cluster()->getSettings($params); +---- +**** + + + +[[Elasticsearch_Namespaces_ClusterNamespacependingTasks_pendingTasks]] +.`pendingTasks()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['master_timeout'] = (time) Specify timeout for connection to master + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cluster()->pendingTasks($params); +---- +**** + + + +[[Elasticsearch_Namespaces_ClusterNamespaceallocationExplain_allocationExplain]] +.`allocationExplain()` +**** +[source,php] +---- +/* +$params['include_yes_decisions'] = (bool) Return 'YES' decisions in explanation (default: false) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->cluster()->allocationExplain($params); +---- +**** + + diff --git a/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/IndicesNamespace.asciidoc b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/IndicesNamespace.asciidoc new file mode 100644 index 0000000..e46618b --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/IndicesNamespace.asciidoc @@ -0,0 +1,1071 @@ + + +[[Elasticsearch_Namespaces_IndicesNamespace]] +=== Elasticsearch\Namespaces\IndicesNamespace + + + +Class IndicesNamespace + + +*Methods* + +The class defines the following methods: + +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> + + + +[[Elasticsearch_Namespaces_IndicesNamespaceexists_exists]] +.`exists()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of indices to check (Required) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->exists($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceget_get]] +.`get()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of indices to check (Required) + ['feature'] = (list) A comma-separated list of features to return + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->get($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacesegments_segments]] +.`segments()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + ['operation_threading'] = () TODO: ? + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->segments($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacedeleteTemplate_deleteTemplate]] +.`deleteTemplate()` +**** +[source,php] +---- +/* +$params['name'] = (string) The name of the template (Required) + ['timeout'] = (time) Explicit operation timeout + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->deleteTemplate($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacedelete_delete]] +.`delete()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of indices to delete; use `_all` or empty string to delete all indices + ['timeout'] = (time) Explicit operation timeout + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->delete($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacestats_stats]] +.`stats()` +**** +[source,php] +---- +/* +$params['fields'] = (boolean) A comma-separated list of fields for `fielddata` metric (supports wildcards) + ['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + ['indexing_types'] = (list) A comma-separated list of document types to include in the `indexing` statistics + ['metric_family'] = (enum) Limit the information returned to a specific metric + ['search_groups'] = (list) A comma-separated list of search groups to include in the `search` statistics + ['all'] = (boolean) Return all available information + ['clear'] = (boolean) Reset the default level of detail + ['docs'] = (boolean) Return information about indexed and deleted documents + ['fielddata'] = (boolean) Return information about field data + ['filter_cache'] = (boolean) Return information about filter cache + ['flush'] = (boolean) Return information about flush operations + ['get'] = (boolean) Return information about get operations + ['groups'] = (boolean) A comma-separated list of search groups for `search` statistics + ['id_cache'] = (boolean) Return information about ID cache + ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones + ['indexing'] = (boolean) Return information about indexing operations + ['merge'] = (boolean) Return information about merge operations + ['refresh'] = (boolean) Return information about refresh operations + ['search'] = (boolean) Return information about search operations; use the `groups` parameter to include information for specific search groups + ['store'] = (boolean) Return information about the size of the index + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->stats($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceputSettings_putSettings]] +.`putSettings()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + ['body'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->putSettings($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacesnapshotIndex_snapshotIndex]] +.`snapshotIndex()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->snapshotIndex($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceshrink_shrink]] +.`shrink()` +**** +[source,php] +---- +/* +$params['index'] = (string) The name of the source index to shrink + ['target'] = (string) The name of the target index to shrink into + ['timeout'] = (time) Explicit operation timeout + ['master_timeout'] = (time) Specify timeout for connection to master + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->shrink($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacegetMapping_getMapping]] +.`getMapping()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + ['type'] = (list) A comma-separated list of document types + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->getMapping($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacegetFieldMapping_getFieldMapping]] +.`getFieldMapping()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + ['type'] = (list) A comma-separated list of document types + ['field'] = (list) A comma-separated list of document fields + ['include_defaults'] = (bool) specifies default mapping values should be returned + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->getFieldMapping($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceflush_flush]] +.`flush()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + ['force'] = (boolean) TODO: ? + ['full'] = (boolean) TODO: ? + ['refresh'] = (boolean) Refresh the index after performing the operation + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->flush($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceflushSynced_flushSynced]] +.`flushSynced()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + ['force'] = (boolean) TODO: ? + ['full'] = (boolean) TODO: ? + ['refresh'] = (boolean) Refresh the index after performing the operation + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->flushSynced($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacerefresh_refresh]] +.`refresh()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + ['operation_threading'] = () TODO: ? + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->refresh($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacerecovery_recovery]] +.`recovery()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + ['detailed'] = (bool) Whether to display detailed information about shard recovery + ['active_only'] = (bool) Display only those recoveries that are currently on-going + ['human'] = (bool) Whether to return time and byte values in human-readable format. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->recovery($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceexistsType_existsType]] +.`existsType()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` to check the types across all indices (Required) + ['type'] = (list) A comma-separated list of document types to check (Required) + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->existsType($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceputAlias_putAlias]] +.`putAlias()` +**** +[source,php] +---- +/* +$params['index'] = (string) The name of the index with an alias + ['name'] = (string) The name of the alias to be created or updated + ['timeout'] = (time) Explicit timestamp for the document + ['body'] = (time) Explicit timestamp for the document + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->putAlias($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceputTemplate_putTemplate]] +.`putTemplate()` +**** +[source,php] +---- +/* +$params['name'] = (string) The name of the template (Required) + ['order'] = (number) The order for this template when merging multiple matching ones (higher numbers are merged later, overriding the lower numbers) + ['timeout'] = (time) Explicit operation timeout + ['body'] = (time) Explicit operation timeout + ['create'] = (bool) Whether the index template should only be added if new or can also replace an existing one + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->putTemplate($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacevalidateQuery_validateQuery]] +.`validateQuery()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names to restrict the operation; use `_all` or empty string to perform the operation on all indices + ['type'] = (list) A comma-separated list of document types to restrict the operation; leave empty to perform the operation on all types + ['explain'] = (boolean) Return detailed information about the error + ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones + ['operation_threading'] = () TODO: ? + ['source'] = (string) The URL-encoded query definition (instead of using the request body) + ['body'] = (string) The URL-encoded query definition (instead of using the request body) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->validateQuery($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacegetAlias_getAlias]] +.`getAlias()` +**** +[source,php] +---- +/* +$params['name'] = (list) A comma-separated list of alias names to return (Required) + ['index'] = (list) A comma-separated list of index names to filter aliases + ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones + ['name'] = (list) A comma-separated list of alias names to return + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->getAlias($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceputMapping_putMapping]] +.`putMapping()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` to perform the operation on all indices (Required) + ['type'] = (string) The name of the document type + ['ignore_conflicts'] = (boolean) Specify whether to ignore conflicts while updating the mapping (default: false) + ['timeout'] = (time) Explicit operation timeout + ['body'] = (time) Explicit operation timeout + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->putMapping($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacedeleteMapping_deleteMapping]] +.`deleteMapping()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` for all indices (Required) + ['type'] = (string) The name of the document type to delete (Required) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->deleteMapping($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacegetTemplate_getTemplate]] +.`getTemplate()` +**** +[source,php] +---- +/* +$params['name'] = (string) The name of the template (Required) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->getTemplate($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceexistsTemplate_existsTemplate]] +.`existsTemplate()` +**** +[source,php] +---- +/* +$params['name'] = (string) The name of the template (Required) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->existsTemplate($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacecreate_create]] +.`create()` +**** +[source,php] +---- +/* +$params['index'] = (string) The name of the index (Required) + ['timeout'] = (time) Explicit operation timeout + ['body'] = (time) Explicit operation timeout + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->create($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceforceMerge_forceMerge]] +.`forceMerge()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + ['flush'] = (boolean) Specify whether the index should be flushed after performing the operation (default: true) + ['max_num_segments'] = (number) The number of segments the index should be merged into (default: dynamic) + ['only_expunge_deletes'] = (boolean) Specify whether the operation should only expunge deleted documents + ['operation_threading'] = () TODO: ? + ['refresh'] = (boolean) Specify whether the index should be refreshed after performing the operation (default: true) + ['wait_for_merge'] = (boolean) Specify whether the request should block until the merge process is finished (default: true) + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->forceMerge($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacedeleteAlias_deleteAlias]] +.`deleteAlias()` +**** +[source,php] +---- +/* +$params['index'] = (string) The name of the index with an alias (Required) + ['name'] = (string) The name of the alias to be deleted (Required) + ['timeout'] = (time) Explicit timestamp for the document + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->deleteAlias($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceopen_open]] +.`open()` +**** +[source,php] +---- +/* +$params['index'] = (string) The name of the index (Required) + ['timeout'] = (time) Explicit operation timeout + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->open($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceanalyze_analyze]] +.`analyze()` +**** +[source,php] +---- +/* +$params['index'] = (string) The name of the index to scope the operation + ['analyzer'] = (string) The name of the analyzer to use + ['field'] = (string) Use the analyzer configured for this field (instead of passing the analyzer name) + ['filter'] = (list) A comma-separated list of filters to use for the analysis + ['prefer_local'] = (boolean) With `true`, specify that a local shard should be used if available, with `false`, use a random shard (default: true) + ['text'] = (string) The text on which the analysis should be performed (when request body is not used) + ['tokenizer'] = (string) The name of the tokenizer to use for the analysis + ['format'] = (enum) Format of the output + ['body'] = (enum) Format of the output + ['char_filter'] = (list) A comma-separated list of character filters to use for the analysis + ['explain'] = (bool) With `true`, outputs more advanced details. (default: false) + ['attributes'] = (list) A comma-separated list of token attributes to output, this parameter works only with `explain=true` + ['format'] = (enum) Format of the output (["detailed", "text"]) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->analyze($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceclearCache_clearCache]] +.`clearCache()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index name to limit the operation + ['field_data'] = (boolean) Clear field data + ['fielddata'] = (boolean) Clear field data + ['fields'] = (list) A comma-separated list of fields to clear when using the `field_data` parameter (default: all) + ['filter'] = (boolean) Clear filter caches + ['filter_cache'] = (boolean) Clear filter caches + ['filter_keys'] = (boolean) A comma-separated list of keys to clear when using the `filter_cache` parameter (default: all) + ['id'] = (boolean) Clear ID caches for parent/child + ['id_cache'] = (boolean) Clear ID caches for parent/child + ['recycler'] = (boolean) Clear the recycler cache + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->clearCache($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceupdateAliases_updateAliases]] +.`updateAliases()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names to filter aliases + ['timeout'] = (time) Explicit timestamp for the document + ['body'] = (time) Explicit timestamp for the document + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->updateAliases($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacegetAliases_getAliases]] +.`getAliases()` +**** +[source,php] +---- +/* +$params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + ['timeout'] = (time) Explicit timestamp for the document + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->getAliases($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceexistsAlias_existsAlias]] +.`existsAlias()` +**** +[source,php] +---- +/* +$params['name'] = (list) A comma-separated list of alias names to return (Required) + ['index'] = (list) A comma-separated list of index names to filter aliases + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->existsAlias($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacestatus_status]] +.`status()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones + ['operation_threading'] = () TODO: ? + ['recovery'] = (boolean) Return information about shard recovery + ['snapshot'] = (boolean) TODO: ? + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->status($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacegetSettings_getSettings]] +.`getSettings()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->getSettings($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceclose_close]] +.`close()` +**** +[source,php] +---- +/* +$params['index'] = (string) The name of the index (Required) + ['timeout'] = (time) Explicit operation timeout + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->close($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceseal_seal]] +.`seal()` +**** +[source,php] +---- +/* +$params['index'] = (string) The name of the index + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->seal($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceupgrade_upgrade]] +.`upgrade()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + ['wait_for_completion']= (boolean) Specify whether the request should block until the all segments are upgraded (default: false) + ['only_ancient_segments'] = (boolean) If true, only ancient (an older Lucene major release) segments will be upgraded + ['refresh'] = (boolean) Refresh the index after performing the operation + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->upgrade($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacegetUpgrade_getUpgrade]] +.`getUpgrade()` +**** +[source,php] +---- +/* +$params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + ['wait_for_completion']= (boolean) Specify whether the request should block until the all segments are upgraded (default: false) + ['only_ancient_segments'] = (boolean) If true, only ancient (an older Lucene major release) segments will be upgraded + ['refresh'] = (boolean) Refresh the index after performing the operation + ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->getUpgrade($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespaceshardStores_shardStores]] +.`shardStores()` +**** +[source,php] +---- +/* +$params['index'] = (string) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + ['status'] = (list) A comma-separated list of statuses used to filter on shards to get store information for + ['ignore_unavailable'] = (boolean) Whether specified concrete indices should be ignored when unavailable (missing or closed) + ['allow_no_indices'] = (boolean) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + ['expand_wildcards'] = (boolean) Whether to expand wildcard expression to concrete indices that are open, closed or both. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->shardStores($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IndicesNamespacerollover_rollover]] +.`rollover()` +**** +[source,php] +---- +/* +$params['newIndex'] = (string) The name of the rollover index + ['alias'] = (string) The name of the alias to rollover + ['timeout'] = (time) Explicit operation timeout + ['master_timeout'] = (time) Specify timeout for connection to master + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->indices()->rollover($params); +---- +**** + + diff --git a/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/IngestNamespace.asciidoc b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/IngestNamespace.asciidoc new file mode 100644 index 0000000..7132c88 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/IngestNamespace.asciidoc @@ -0,0 +1,106 @@ + + +[[Elasticsearch_Namespaces_IngestNamespace]] +=== Elasticsearch\Namespaces\IngestNamespace + + + +Class IngestNamespace + + +*Methods* + +The class defines the following methods: + +* <> +* <> +* <> +* <> + + + +[[Elasticsearch_Namespaces_IngestNamespacedeletePipeline_deletePipeline]] +.`deletePipeline()` +**** +[source,php] +---- +/* +$params['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['timeout'] = (time) Explicit operation timeout + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->ingest()->deletePipeline($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IngestNamespacegetPipeline_getPipeline]] +.`getPipeline()` +**** +[source,php] +---- +/* +$params['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->ingest()->getPipeline($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IngestNamespaceputPipeline_putPipeline]] +.`putPipeline()` +**** +[source,php] +---- +/* +$params['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['timeout'] = (time) Explicit operation timeout + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->ingest()->putPipeline($params); +---- +**** + + + +[[Elasticsearch_Namespaces_IngestNamespacesimulate_simulate]] +.`simulate()` +**** +[source,php] +---- +/* +$params['verbose'] = (bool) Verbose mode. Display data output for each processor in executed pipeline + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->ingest()->simulate($params); +---- +**** + + diff --git a/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/NodesNamespace.asciidoc b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/NodesNamespace.asciidoc new file mode 100644 index 0000000..fb71cc5 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/NodesNamespace.asciidoc @@ -0,0 +1,125 @@ + + +[[Elasticsearch_Namespaces_NodesNamespace]] +=== Elasticsearch\Namespaces\NodesNamespace + + + +Class NodesNamespace + + +*Methods* + +The class defines the following methods: + +* <> +* <> +* <> +* <> + + + +[[Elasticsearch_Namespaces_NodesNamespacestats_stats]] +.`stats()` +**** +[source,php] +---- +/* +$params['fields'] = (list) A comma-separated list of fields for `fielddata` metric (supports wildcards) + ['metric_family'] = (enum) Limit the information returned to a certain metric family + ['metric'] = (enum) Limit the information returned for `indices` family to a specific metric + ['node_id'] = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes + ['all'] = (boolean) Return all available information + ['clear'] = (boolean) Reset the default level of detail + ['fs'] = (boolean) Return information about the filesystem + ['http'] = (boolean) Return information about HTTP + ['indices'] = (boolean) Return information about indices + ['jvm'] = (boolean) Return information about the JVM + ['network'] = (boolean) Return information about network + ['os'] = (boolean) Return information about the operating system + ['process'] = (boolean) Return information about the Elasticsearch process + ['thread_pool'] = (boolean) Return information about the thread pool + ['transport'] = (boolean) Return information about transport + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->nodes()->stats($params); +---- +**** + + + +[[Elasticsearch_Namespaces_NodesNamespaceinfo_info]] +.`info()` +**** +[source,php] +---- +/* +$params['node_id'] = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes + ['metric'] = (list) A comma-separated list of metrics you wish returned. Leave empty to return all. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->nodes()->info($params); +---- +**** + + + +[[Elasticsearch_Namespaces_NodesNamespacehotThreads_hotThreads]] +.`hotThreads()` +**** +[source,php] +---- +/* +$params['node_id'] = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes + ['interval'] = (time) The interval for the second sampling of threads + ['snapshots'] = (number) Number of samples of thread stacktrace (default: 10) + ['threads'] = (number) Specify the number of threads to provide information for (default: 3) + ['type'] = (enum) The type to sample (default: cpu) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->nodes()->hotThreads($params); +---- +**** + + + +[[Elasticsearch_Namespaces_NodesNamespaceshutdown_shutdown]] +.`shutdown()` +**** +[source,php] +---- +/* +$params['node_id'] = (list) A comma-separated list of node IDs or names to perform the operation on; use `_local` to perform the operation on the node you're connected to, leave empty to perform the operation on all nodes + ['delay'] = (time) Set the delay for the operation (default: 1s) + ['exit'] = (boolean) Exit the JVM as well (default: true) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->nodes()->shutdown($params); +---- +**** + + diff --git a/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/SnapshotNamespace.asciidoc b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/SnapshotNamespace.asciidoc new file mode 100644 index 0000000..594133b --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/SnapshotNamespace.asciidoc @@ -0,0 +1,220 @@ + + +[[Elasticsearch_Namespaces_SnapshotNamespace]] +=== Elasticsearch\Namespaces\SnapshotNamespace + + + +Class SnapshotNamespace + + +*Methods* + +The class defines the following methods: + +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> + + + +[[Elasticsearch_Namespaces_SnapshotNamespacecreate_create]] +.`create()` +**** +[source,php] +---- +/* +$params['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['wait_for_completion'] = (bool) Should this request wait until the operation has completed before returning + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->snapshot()->create($params); +---- +**** + + + +[[Elasticsearch_Namespaces_SnapshotNamespacecreateRepository_createRepository]] +.`createRepository()` +**** +[source,php] +---- +/* +$params['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['timeout'] = (time) Explicit operation timeout + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->snapshot()->createRepository($params); +---- +**** + + + +[[Elasticsearch_Namespaces_SnapshotNamespacedelete_delete]] +.`delete()` +**** +[source,php] +---- +/* +$params['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->snapshot()->delete($params); +---- +**** + + + +[[Elasticsearch_Namespaces_SnapshotNamespacedeleteRepository_deleteRepository]] +.`deleteRepository()` +**** +[source,php] +---- +/* +$params['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['timeout'] = (time) Explicit operation timeout + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->snapshot()->deleteRepository($params); +---- +**** + + + +[[Elasticsearch_Namespaces_SnapshotNamespaceget_get]] +.`get()` +**** +[source,php] +---- +/* +$params['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->snapshot()->get($params); +---- +**** + + + +[[Elasticsearch_Namespaces_SnapshotNamespacegetRepository_getRepository]] +.`getRepository()` +**** +[source,php] +---- +/* +$params['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['timeout'] = (time) Explicit operation timeout + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->snapshot()->getRepository($params); +---- +**** + + + +[[Elasticsearch_Namespaces_SnapshotNamespacerestore_restore]] +.`restore()` +**** +[source,php] +---- +/* +$params['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['wait_for_completion'] = (bool) Should this request wait until the operation has completed before returning + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->snapshot()->restore($params); +---- +**** + + + +[[Elasticsearch_Namespaces_SnapshotNamespacestatus_status]] +.`status()` +**** +[source,php] +---- +/* +$params['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->snapshot()->status($params); +---- +**** + + + +[[Elasticsearch_Namespaces_SnapshotNamespaceverifyRepository_verifyRepository]] +.`verifyRepository()` +**** +[source,php] +---- +/* +$params['master_timeout'] = (time) Explicit operation timeout for connection to master node + ['timeout'] = (time) Explicit operation timeout + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->snapshot()->verifyRepository($params); +---- +**** + + diff --git a/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/TasksNamespace.asciidoc b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/TasksNamespace.asciidoc new file mode 100644 index 0000000..9d51713 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/Elasticsearch/Namespaces/TasksNamespace.asciidoc @@ -0,0 +1,84 @@ + + +[[Elasticsearch_Namespaces_TasksNamespace]] +=== Elasticsearch\Namespaces\TasksNamespace + + + +Class TasksNamespace + + +*Methods* + +The class defines the following methods: + +* <> +* <> +* <> + + + +[[Elasticsearch_Namespaces_TasksNamespaceget_get]] +.`get()` +**** +[source,php] +---- +/* +$params['wait_for_completion'] = (bool) Wait for the matching tasks to complete (default: false) + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->tasks()->get($params); +---- +**** + + + +[[Elasticsearch_Namespaces_TasksNamespacetasksList_tasksList]] +.`tasksList()` +**** +[source,php] +---- +/* +$params['node_id'] = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes + ['actions'] = (list) A comma-separated list of actions that should be cancelled. Leave empty to cancel all. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->tasks()->tasksList($params); +---- +**** + + + +[[Elasticsearch_Namespaces_TasksNamespacecancel_cancel]] +.`cancel()` +**** +[source,php] +---- +/* +$params['node_id'] = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes + ['actions'] = (list) A comma-separated list of actions that should be cancelled. Leave empty to cancel all. + ['body'] = (array) Request body +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = $client->tasks()->cancel($params); +---- +**** + + diff --git a/vendor/elasticsearch/elasticsearch/docs/build/PROJECT_VERSION b/vendor/elasticsearch/elasticsearch/docs/build/PROJECT_VERSION new file mode 100644 index 0000000..8b25206 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/PROJECT_VERSION @@ -0,0 +1 @@ +master \ No newline at end of file diff --git a/vendor/elasticsearch/elasticsearch/docs/build/SAMI_VERSION b/vendor/elasticsearch/elasticsearch/docs/build/SAMI_VERSION new file mode 100644 index 0000000..75a358e --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/SAMI_VERSION @@ -0,0 +1 @@ +3.3.0-DEV \ No newline at end of file diff --git a/vendor/elasticsearch/elasticsearch/docs/build/classes.asciidoc b/vendor/elasticsearch/elasticsearch/docs/build/classes.asciidoc new file mode 100644 index 0000000..af30299 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/classes.asciidoc @@ -0,0 +1,26 @@ + +[[ElasticsearchPHP_Endpoints]] +== Reference - Endpoints + +This is a complete list of namespaces and their associated endpoints. + +NOTE: This is auto-generated documentation + +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> +include::Elasticsearch/Client.asciidoc[] +include::Elasticsearch/ClientBuilder.asciidoc[] +include::Elasticsearch/Namespaces/CatNamespace.asciidoc[] +include::Elasticsearch/Namespaces/ClusterNamespace.asciidoc[] +include::Elasticsearch/Namespaces/IndicesNamespace.asciidoc[] +include::Elasticsearch/Namespaces/IngestNamespace.asciidoc[] +include::Elasticsearch/Namespaces/NodesNamespace.asciidoc[] +include::Elasticsearch/Namespaces/SnapshotNamespace.asciidoc[] +include::Elasticsearch/Namespaces/TasksNamespace.asciidoc[] diff --git a/vendor/elasticsearch/elasticsearch/docs/build/interfaces.asciidoc b/vendor/elasticsearch/elasticsearch/docs/build/interfaces.asciidoc new file mode 100644 index 0000000..5b17b38 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/interfaces.asciidoc @@ -0,0 +1,7 @@ + +[[ElasticsearchPHP_Interfaces]] +== Reference - Interfaces + +This is a complete list of available interfaces: + +* There are no interfaces available. diff --git a/vendor/elasticsearch/elasticsearch/docs/build/namespaces.asciidoc b/vendor/elasticsearch/elasticsearch/docs/build/namespaces.asciidoc new file mode 100644 index 0000000..a59ad37 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/namespaces.asciidoc @@ -0,0 +1,10 @@ + +[[ElasticsearchPHP_Namespaces]] +== Reference - Namespaces + +This is a complete list of available namespaces: + +* <> +* <> +include::Elasticsearch.asciidoc[] +include::Elasticsearch/Namespaces.asciidoc[] diff --git a/vendor/elasticsearch/elasticsearch/docs/build/renderer.index b/vendor/elasticsearch/elasticsearch/docs/build/renderer.index new file mode 100644 index 0000000..9064cb3 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/build/renderer.index @@ -0,0 +1 @@ +C:19:"Sami\Renderer\Index":944:{a:3:{i:0;a:9:{s:20:"Elasticsearch\Client";s:40:"c92166baaf85cb08a91ce4e3cf845b1626ba12f0";s:27:"Elasticsearch\ClientBuilder";s:40:"347f22134a07f53b3355a211264d8e53aa545243";s:37:"Elasticsearch\Namespaces\CatNamespace";s:40:"51d06cd6b8334bcf3a2580f67bbc5c88f8fde761";s:41:"Elasticsearch\Namespaces\ClusterNamespace";s:40:"0934f56b5dfa7978ab1907b6c8a04b0a293ae274";s:41:"Elasticsearch\Namespaces\IndicesNamespace";s:40:"3d23245494af9443c215b31faa4e78dd6ab29750";s:40:"Elasticsearch\Namespaces\IngestNamespace";s:40:"b52adeb7071f16cba79cdcc3dac3fa6e53ed62bd";s:39:"Elasticsearch\Namespaces\NodesNamespace";s:40:"da4e71f9d953d00600920c26fe585b6884e45f94";s:42:"Elasticsearch\Namespaces\SnapshotNamespace";s:40:"e28a1807789b0fcca3fd6b9712ed713650cf7ac2";s:39:"Elasticsearch\Namespaces\TasksNamespace";s:40:"2de86d7ab409a629320725f6444c76d2a9313c72";}i:1;a:1:{i:0;s:6:"master";}i:2;a:2:{i:0;s:13:"Elasticsearch";i:1;s:24:"Elasticsearch\Namespaces";}}} \ No newline at end of file diff --git a/vendor/elasticsearch/elasticsearch/docs/community.asciidoc b/vendor/elasticsearch/elasticsearch/docs/community.asciidoc new file mode 100644 index 0000000..c05b441 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/community.asciidoc @@ -0,0 +1,89 @@ + +== Community DSLs + +=== ElasticsearchDSL + +https://github.com/ongr-io/ElasticsearchDSL[Link: ElasticsearchDSL] +[quote, ElasticsearchDSL] +__________________________ +Introducing Elasticsearch DSL library to provide objective query builder for Elasticsearch bundle and elasticsearch-php client. You can easily build any Elasticsearch query and transform it to an array. +__________________________ + +=== elasticsearcher + +https://github.com/madewithlove/elasticsearcher[Link: elasticsearcher] + +[quote, elasticsearcher] +__________________________ +This agnostic package is a lightweight wrapper on top of the Elasticsearch PHP client. Its main goal is to allow for easier structuring of queries and indices in your application. It does not want to hide or replace functionality of the Elasticsearch PHP client. +__________________________ + +== Community Integrations + +=== Symfony + +==== ONGR Elasticsearch Bundle + +https://github.com/ongr-io/ElasticsearchBundle[Link: ONGR Elasticsearch Bundle] + +[quote, ONGR Elasticsearch Bundle] +__________________________ +Elasticsearch Bundle was created in order to serve the need for professional elasticsearch +integration with enterprise level Symfony 2 systems. This bundle is: + +- Supported by ONGR.io development team. +- Uses the official elasticsearch-php client. +- Ensures full integration with Symfony 2 framework. + +Technical goodies: + +- Provides nestable and DSL query builder to be executed by type repository services. +- Uses Doctrine-like document / entities document-object mapping using annotations. +- Query results iterators are provided for your convenience. +- Registers console commands for index and types management and data import / export. +- Designed in an extensible way for all your custom needs. +__________________________ + + +=== Drupal + +==== Elasticsearch Connector + +https://www.drupal.org/project/elasticsearch_connector[Link: Elasticsearch Connector] + +[quote, Elasticsearch Connector] +__________________________ +Elasticsearch Connector is a set of modules designed to build a full Elasticsearch eco system in Drupal. +__________________________ + +=== Laravel + +==== shift31/Laravel-Elasticsearch + +https://github.com/shift31/laravel-elasticsearch[Link: shift31/Laravel-Elasticsearch] + +[quote, Laravel-Elasticsearch] +__________________________ +This is a Laravel (4+) Service Provider for the official Elasticsearch low-level client. +__________________________ + + +==== cviebrock/Laravel-Elasticsearch + +https://github.com/cviebrock/laravel-elasticsearch[Link: cviebrock/Laravel-Elasticsearch] + +[quote, Laravel-Elasticsearch] +__________________________ +An easy way to use the official Elastic Search client in your Laravel applications. +__________________________ + + +==== Plastic + +https://github.com/sleimanx2/plastic[Link: Plastic] + +[quote, Plastic] +__________________________ +Plastic is an Elasticsearch ODM and mapper for Laravel. It renders the developer experience more enjoyable while using Elasticsearch, by providing a fluent syntax for mapping, querying, and storing eloquent models. +__________________________ + diff --git a/vendor/elasticsearch/elasticsearch/docs/configuration.asciidoc b/vendor/elasticsearch/elasticsearch/docs/configuration.asciidoc new file mode 100644 index 0000000..ed6a46f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/configuration.asciidoc @@ -0,0 +1,442 @@ + +== Configuration + +Almost every aspect of the client is configurable. Most users will only need to configure a few parameters to suit +their needs, but it is possible to completely replace much of the internals if required. + +Custom configuration is accomplished before the client is instantiated, through the ClientBuilder helper object. +We'll walk through all the configuration options and show sample code to replace the various components. + +=== Inline Host Configuration + +The most common configuration is telling the client about your cluster: how many nodes, their addresses and ports. If +no hosts are specified, the client will attempt to connect to `localhost:9200`. + +This behavior can be changed by using the `setHosts()` method on `ClientBuilder`. The method accepts an array of values, +each entry corresponding to one node in your cluster. The format of the host can vary, depending on your needs (ip vs +hostname, port, ssl, etc) + +[source,php] +---- +$hosts = [ + '192.168.1.1:9200', // IP + Port + '192.168.1.2', // Just IP + 'mydomain.server.com:9201', // Domain + Port + 'mydomain2.server.com', // Just Domain + 'https://localhost', // SSL to localhost + 'https://192.168.1.3:9200' // SSL to IP + Port +]; +$client = ClientBuilder::create() // Instantiate a new ClientBuilder + ->setHosts($hosts) // Set the hosts + ->build(); // Build the client object +---- + +Notice that the `ClientBuilder` object allows chaining method calls for brevity. It is also possible to call the methods +individually: + +[source,php] +---- +$hosts = [ + '192.168.1.1:9200', // IP + Port + '192.168.1.2', // Just IP + 'mydomain.server.com:9201', // Domain + Port + 'mydomain2.server.com', // Just Domain + 'https://localhost', // SSL to localhost + 'https://192.168.1.3:9200' // SSL to IP + Port +]; +$clientBuilder = ClientBuilder::create(); // Instantiate a new ClientBuilder +$clientBuilder->setHosts($hosts); // Set the hosts +$client = $clientBuilder->build(); // Build the client object +---- + +=== Extended Host Configuration + +The client also supports an _extended_ host configuration syntax. The inline configuration method relies on PHP's +`filter_var()` and `parse_url()` methods to validate and extract the components of a URL. Unfortunately, these built-in +methods run into problems with certain edge-cases. For example, `filter_var()` will not accept URL's that have underscores +(which are questionably legal, depending on how you interpret the RFCs). Similarly, `parse_url()` will choke if a +Basic Auth's password contains special characters such as a pound sign (`#`) or question-marks (`?`). + +For this reason, the client supports an extended host syntax which provides greater control over host initialization. +None of the components are validated, so edge-cases like underscores in domain names will not cause problems. + +The extended syntax is an array of parameters for each host: + +[source,php] +---- +$hosts = [ + // This is effectively equal to: "https://username:password!#$?*abc@foo.com:9200/" + [ + 'host' => 'foo.com', + 'port' => '9200', + 'scheme' => 'https', + 'user' => 'username', + 'pass' => 'password!#$?*abc' + ], + + // This is equal to "http://localhost:9200/" + [ + 'host' => 'localhost', // Only host is required + ] +]; +$client = ClientBuilder::create() // Instantiate a new ClientBuilder + ->setHosts($hosts) // Set the hosts + ->build(); // Build the client object +---- + +Only the `host` parameter is required for each configured host. If not provided, the default port is `9200`. The default +scheme is `http`. + +=== Authorization and Encryption + +For details about HTTP Authorization and SSL encryption, please see link:_security.html[Authorization and SSL]. + +=== Set retries + +By default, the client will retry `n` times, where `n = number of nodes` in your cluster. A retry is only performed +if the operation results in a "hard" exception: connection refusal, connection timeout, DNS lookup timeout, etc. 4xx and +5xx errors are not considered retry'able events, since the node returns an operational response. + +If you would like to disable retries, or change the number, you can do so with the `setRetries()` method: + +[source,php] +---------------------------- + +$client = ClientBuilder::create() + ->setRetries(2) + ->build(); +---------------------------- + +When the client runs out of retries, it will throw the last exception that it received. For example, if you have ten +alive nodes, and `setRetries(5)`, the client will attempt to execute the command up to five times. If all five nodes +result in a connection timeout (for example), the client will throw an `OperationTimeoutException`. Depending on the +Connection Pool being used, these nodes may also be marked dead. + +To help in identification, exceptions that are thrown due to max retries will wrap a `MaxRetriesException`. For example, +you can catch a specific curl exception then check if it wraps a MaxRetriesException using `getPrevious()`: + +[source,php] +---- +$client = Elasticsearch\ClientBuilder::create() + ->setHosts(["localhost:1"]) + ->setRetries(0) + ->build(); + +try { + $client->search($searchParams); +} catch (Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost $e) { + $previous = $e->getPrevious(); + if ($previous instanceof 'Elasticsearch\Common\Exceptions\MaxRetriesException') { + echo "Max retries!"; + } +} +---- + +Alternatively, all "hard" curl exceptions (`CouldNotConnectToHost`, `CouldNotResolveHostException`, `OperationTimeoutException`) +extend the more general `TransportException`. So you could instead catch the general `TransportException` and then +check it's previous value: + +[source,php] +---- +$client = Elasticsearch\ClientBuilder::create() + ->setHosts(["localhost:1"]) + ->setRetries(0) + ->build(); + +try { + $client->search($searchParams); +} catch (Elasticsearch\Common\Exceptions\TransportException $e) { + $previous = $e->getPrevious(); + if ($previous instanceof 'Elasticsearch\Common\Exceptions\MaxRetriesException') { + echo "Max retries!"; + } +} +---- + + +[[enabling_logger]] +=== Enabling the Logger +Elasticsearch-PHP supports logging, but it is not enabled by default for performance reasons. If you wish to enable logging, +you need to select a logging implementation, install it, then enable the logger in the Client. The recommended logger +is https://github.com/Seldaek/monolog[Monolog], but any logger that implements the `PSR/Log` interface will work. + +You might have noticed that Monolog was suggested during installation. To begin using Monolog, add it to your `composer.json`: + +[source,json] +---------------------------- +{ + "require": { + ... + "elasticsearch/elasticsearch" : "~5.0", + "monolog/monolog": "~1.0" + } +} +---------------------------- + +And then update your composer installation: + +[source,shell] +---------------------------- +php composer.phar update +---------------------------- + +Once Monolog (or another logger) is installed, you need to create a log object and inject it into the client. The +`ClientBuilder` object has a helper static function that will generate a common Monolog-based logger for you. All you need +to do is provide the path to your desired logging location: + +[source,php] +---- +$logger = ClientBuilder::defaultLogger('path/to/your.log'); + +$client = ClientBuilder::create() // Instantiate a new ClientBuilder + ->setLogger($logger) // Set the logger with a default logger + ->build(); // Build the client object +---- + +You can also specify the severity of log messages that you wish to log: + +[source,php] +---- +// set severity with second parameter +$logger = ClientBuilder::defaultLogger('/path/to/logs/', Logger::INFO); + +$client = ClientBuilder::create() // Instantiate a new ClientBuilder + ->setLogger($logger) // Set the logger with a default logger + ->build(); // Build the client object +---- + +The `defaultLogger()` method is just a helper, you are not required to use it. You can create your own logger and inject +that instead: + + +[source,php] +---- +use Monolog\Logger; +use Monolog\Handler\StreamHandler; + +$logger = new Logger('name'); +$logger->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); + +$client = ClientBuilder::create() // Instantiate a new ClientBuilder + ->setLogger($logger) // Set your custom logger + ->build(); // Build the client object +---- + + +=== Configure the HTTP Handler + +Elasticsearch-PHP uses an interchangeable HTTP transport layer called https://github.com/guzzle/RingPHP/[RingPHP]. This +allows the client to construct a generic HTTP request, then pass it to the transport layer to execute. The actual execution +details are hidden from the client and it is modular, so that you can choose from several HTTP handlers depending on your needs. + +The default handler that the client uses is a combination handler. When executing in synchronous mode, the handler +uses `CurlHandler`, which executes single curl calls. These are very fast for single requests. When asynchronous (future) +mode is enabled, the handler switches to `CurlMultiHandler`, which uses the curl_multi interface. This involves a bit +more overhead, but allows batches of HTTP requests to be processed in parallel. + +You can configure the HTTP handler with one of several helper functions, or provide your own custom handler: + +[source,php] +---- +$defaultHandler = ClientBuilder::defaultHandler(); +$singleHandler = ClientBuilder::singleHandler(); +$multiHandler = ClientBuilder::multiHandler(); +$customHandler = new MyCustomHandler(); + +$client = ClientBuilder::create() + ->setHandler($defaultHandler) + ->build(); +---- + +For details on creating your own custom Ring handler, please see the http://guzzle.readthedocs.org/en/latest/handlers.html[RingPHP Documentation] + +The default handler is recommended in almost all cases. This allows fast synchronous execution, while retaining flexibility +to invoke parallel batches with async future mode. You may consider using just the `singleHandler` if you know you will +never need async capabilities, since it will save a small amount of overhead by reducing indirection. + + +=== Setting the Connection Pool + +The client maintains a pool of connections, with each connection representing a node in your cluster. There are several +connection pool implementations available, and each has slightly different behavior (pinging vs no pinging, etc). +Connection pools are configured via the `setConnectionPool()` method: + +[source,php] +---- +$connectionPool = '\Elasticsearch\ConnectionPool\StaticNoPingConnectionPool'; +$client = ClientBuilder::create() + ->setConnectionPool($connectionPool) + ->build(); +---- + +For more details, please see the dedicated page on link:_connection_pool.html[configuring connection pools]. + +=== Setting the Connection Selector + +The connection pool manages the connections to your cluster, but the Selector is the logic that decides which connection +should be used for the next API request. There are several selectors that you can choose from. Selectors can be changed +via the `setSelector()` method: + +[source,php] +---- +$selector = '\Elasticsearch\ConnectionPool\Selectors\StickyRoundRobinSelector'; +$client = ClientBuilder::create() + ->setSelector($selector) + ->build(); +---- + +For more details, please see the dedicated page on link:_selectors.html[configuring selectors]. + + +=== Setting the Serializer + +Requests are given to the client in the form of associative arrays, but Elasticsearch expects JSON. The Serializer's +job is to serialize PHP objects into JSON. It also de-serializes JSON back into PHP arrays. This seems trivial, but +there are a few edgecases which make it useful for the serializer to remain modular. + +The majority of people will never need to change the default serializer (`SmartSerializer`), but if you need to, +it can be done via the `setSerializer()` method: + +[source,php] +---- +$serializer = '\Elasticsearch\Serializers\SmartSerializer'; +$client = ClientBuilder::create() + ->setSerializer($serializer) + ->build(); +---- + +For more details, please see the dedicated page on link:_serializers.html[configuring serializers]. + + +=== Setting a custom ConnectionFactory + +The ConnectionFactory instantiates new Connection objects when requested by the ConnectionPool. A single Connection +represents a single node. Since the client hands actual networking work over to RingPHP, the Connection's main job is +book-keeping: Is this node alive? Did it fail a ping request? What is the host and port? + +There is little reason to provide your own ConnectionFactory, but if you need to do so, you need to supply an intact +ConnectionFactory object to the `setConnectionFactory()` method. The object should implement the `ConnectionFactoryInterface` +interface. + +[source,php] +---- + +class MyConnectionFactory implements ConnectionFactoryInterface +{ + + public function __construct($handler, array $connectionParams, + SerializerInterface $serializer, + LoggerInterface $logger, + LoggerInterface $tracer) + { + // Code here + } + + + /** + * @param $hostDetails + * + * @return ConnectionInterface + */ + public function create($hostDetails) + { + // Code here...must return a Connection object + } +} + + +$connectionFactory = new MyConnectionFactory( + $handler, + $connectionParams, + $serializer, + $logger, + $tracer +); + +$client = ClientBuilder::create() + ->setConnectionFactory($connectionFactory); + ->build(); +---- + +As you can see, if you decide to inject your own ConnectionFactory, you take over the responsibiltiy of wiring it correctly. +The ConnectionFactory requires a working HTTP handler, serializer, logger and tracer. + + +=== Set the Endpoint closure + +The client uses an Endpoint closure to dispatch API requests to the correct Endpoint object. A namespace object will +construct a new Endpoint via this closure, which means this is a handy location if you wish to extend the available set +of API endpoints available + +For example, we could add a new endpoint like so: + +[source,php] +---- + +$transport = $this->transport; +$serializer = $this->serializer; + +$newEndpoint = function ($class) use ($transport, $serializer) { + if ($class == 'SuperSearch') { + return new MyProject\SuperSearch($transport); + } else { + // Default handler + $fullPath = '\\Elasticsearch\\Endpoints\\' . $class; + if ($class === 'Bulk' || $class === 'Msearch' || $class === 'MPercolate') { + return new $fullPath($transport, $serializer); + } else { + return new $fullPath($transport); + } + } +}; + +$client = ClientBuilder::create() + ->setEndpoint($newEndpoint) + ->build(); +---- + +Obviously, by doing this you take responsibility that all existing endpoints still function correctly. And you also +assume the responsibility of correctly wiring the Transport and Serializer into each endpoint. + + +=== Building the client from a configuration hash + +To help ease automated building of the client, all configurations can be provided in a setting +hash instead of calling the individual methods directly. This functionality is exposed through +the `ClientBuilder::FromConfig()` static method, which accepts an array of configurations +and returns a fully built client. + +Array keys correspond to the method name, e.g. `retries` key corresponds to `setRetries()` method. + + +[source,php] +---- +$params = [ + 'hosts' => [ + 'localhost:9200' + ], + 'retries' => 2, + 'handler' => ClientBuilder::singleHandler() +]; +$client = ClientBuilder::fromConfig($params); +---- + + +Unknown parameters will throw an exception, to help the user find potential problems. +If this behavior is not desired (e.g. you are using the hash for other purposes, and may have +keys unrelated to the Elasticsearch client), you can set $quiet = true in fromConfig() to +silence the exceptions. + +[source,php] +---- +$params = [ + 'hosts' => [ + 'localhost:9200' + ], + 'retries' => 2, + 'imNotReal' => 5 +]; + +// Set $quiet to true to ignore the unknown `imNotReal` key +$client = ClientBuilder::fromConfig($params, true); +---- diff --git a/vendor/elasticsearch/elasticsearch/docs/connection-pool.asciidoc b/vendor/elasticsearch/elasticsearch/docs/connection-pool.asciidoc new file mode 100644 index 0000000..8999ff6 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/connection-pool.asciidoc @@ -0,0 +1,204 @@ + +== Connection Pool + +The connection pool is an object inside the client that is responsible for maintaining the current list of nodes. +Theoretically, nodes are either dead or alive. + +However, in the real world, things are never so clear. Nodes are sometimes in a gray-zone of _"probably dead but not +confirmed"_, _"timed-out but unclear why"_ or _"recently dead but now alive"_. The connection pool's job is to +manage this set of unruly connections and try to provide the best behavior to the client. + +If a connection pool is unable to find an alive node to query against, it will return a `NoNodesAvailableException`. +This is distinct from an exception due to maximum retries. For example, your cluster may have 10 nodes. You execute +a request and 9 out of the 10 nodes fail due to connection timeouts. The tenth node succeeds and the query executes. +The first nine nodes will be marked dead (depending on the connection pool being used) and their "dead" timers will begin +ticking. + +When the next request is sent to the client, nodes 1-9 are still considered "dead", so they will be skipped. The request +is sent to the only known alive node (#10), and if this node fails, a `NoNodesAvailableException` is returned. You'll note +this is much less than the `retries` value, because `retries` only applies to retries against alive nodes. In this case, +only one node is known to be alive, so `NoNodesAvailableException` is returned. + + +There are several connection pool implementations that you can choose from: + +=== staticNoPingConnectionPool (default) + +This connection pool maintains a static list of hosts, which are assumed to be alive when the client initializes. If +a node fails a request, it is marked as `dead` for 60 seconds and the next node is tried. After 60 seconds, the node +is revived and put back into rotation. Each additional failed request will cause the dead timeout to increase exponentially. + +A successful request will reset the "failed ping timeout" counter. + +If you wish to explicitly set the `StaticNoPingConnectionPool` implementation, you may do so with the `setConnectionPool()` +method of the ClientBuilder object: + +[source,php] +---- +$client = ClientBuilder::create() + ->setConnectionPool('\Elasticsearch\ConnectionPool\StaticNoPingConnectionPool', []) + ->build(); +---- + +Note that the implementation is specified via a namespace path to the class. + +=== staticConnectionPool + +Identical to the `StaticNoPingConnectionPool`, except it pings nodes before they are used to determine if they are alive. +This may be useful for long-running scripts, but tends to be additional overhead that is unnecessary for average PHP scripts. + +To use the `StaticConnectionPool`: + +[source,php] +---- +$client = ClientBuilder::create() + ->setConnectionPool('\Elasticsearch\ConnectionPool\StaticConnectionPool', []) + ->build(); +---- + +Note that the implementation is specified via a namespace path to the class. + +=== simpleConnectionPool + +The `SimpleConnectionPool` simply returns the next node as specified by the Selector; it does not track +the "liveness" of nodes. This pool will return nodes whether they are alive or dead. It is just a simple pool of static +hosts. + +The `SimpleConnectionPool` is not recommended for routine use, but it may be a useful debugging tool. + +To use the `SimpleConnectionPool`: + +[source,php] +---- +$client = ClientBuilder::create() + ->setConnectionPool('\Elasticsearch\ConnectionPool\SimpleConnectionPool', []) + ->build(); +---- + +Note that the implementation is specified via a namespace path to the class. + +=== sniffingConnectionPool + +Unlike the two previous static connection pools, this one is dynamic. The user provides a seed list of hosts, which the +client uses to "sniff" and discover the rest of the cluster. It achieves this through the Cluster State API. As new +nodes are added or removed from the cluster, the client will update its pool of active connections. + +To use the `SniffingConnectionPool`: + +[source,php] +---- +$client = ClientBuilder::create() + ->setConnectionPool('\Elasticsearch\ConnectionPool\SniffingConnectionPool', []) + ->build(); +---- + +Note that the implementation is specified via a namespace path to the class. + + +=== Custom Connection Pool + +If you wish to implement your own custom Connection Pool, your class must implement `ConnectionPoolInterface`: + +[source,php] +---- +class MyCustomConnectionPool implements ConnectionPoolInterface +{ + + /** + * @param bool $force + * + * @return ConnectionInterface + */ + public function nextConnection($force = false) + { + // code here + } + + /** + * @return void + */ + public function scheduleCheck() + { + // code here + } +} +---- + +You can then instantiate an instance of your ConnectionPool and inject it into the ClientBuilder: + +[source,php] +---- +$myConnectionPool = new MyCustomConnectionPool(); + +$client = ClientBuilder::create() + ->setConnectionPool($myConnectionPool, []) + ->build(); +---- + +If your connection pool only makes minor changes, you may consider extending `AbstractConnectionPool`, which provides +some helper concrete methods. If you choose to go down this route, you need to make sure your ConnectionPool's implementation +has a compatible constructor (since it is not defined in the interface): + +[source,php] +---- +class MyCustomConnectionPool extends AbstractConnectionPool implements ConnectionPoolInterface +{ + + public function __construct($connections, SelectorInterface $selector, ConnectionFactory $factory, $connectionPoolParams) + { + parent::__construct($connections, $selector, $factory, $connectionPoolParams); + } + + /** + * @param bool $force + * + * @return ConnectionInterface + */ + public function nextConnection($force = false) + { + // code here + } + + /** + * @return void + */ + public function scheduleCheck() + { + // code here + } +} +---- + +If your constructor matches AbstractConnectionPool, you may use either object injection or namespace instantiation: + +[source,php] +---- +$myConnectionPool = new MyCustomConnectionPool(); + +$client = ClientBuilder::create() + ->setConnectionPool($myConnectionPool, []) // object injection + ->setConnectionPool('/MyProject/ConnectionPools/MyCustomConnectionPool', []) // or namespace + ->build(); +---- + + +=== Which connection pool to choose? PHP and connection pooling + +At first glance, the `sniffingConnectionPool` implementation seems superior. For many languages, it is. In PHP, the +conversation is a bit more nuanced. + +Because PHP is a share-nothing architecture, there is no way to maintain a connection pool across script instances. +This means that every script is responsible for creating, maintaining, and destroying connections everytime the script +is re-run. + +Sniffing is a relatively lightweight operation (one API call to `/_cluster/state`, followed by pings to each node) but +it may be a non-negligible overhead for certain PHP applications. The average PHP script will likely load the client, +execute a few queries and then close. Imagine this script being called 1000 times per second: the sniffing connection +pool will perform the sniffing and pinging process 1000 times per second. The sniffing process will add a large +amount of overhead. + +In reality, if your script only executes a few queries, the sniffing concept is _too_ robust. It tends to be more +useful in long-lived processes which potentially "out-live" a static list. + +For this reason the default connection pool is currently the `staticNoPingConnectionPool`. You can, of course, change +this default - but we strongly recommend you load test and verify that it does not negatively impact your performance. diff --git a/vendor/elasticsearch/elasticsearch/docs/crud.asciidoc b/vendor/elasticsearch/elasticsearch/docs/crud.asciidoc new file mode 100644 index 0000000..3d3b548 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/crud.asciidoc @@ -0,0 +1,245 @@ + +== Indexing Documents + +When you add documents to Elasticsearch, you index JSON documents. This maps naturally to PHP associative arrays, since +they can easily be encoded in JSON. Therefore, in Elasticsearch-PHP you create and pass associative arrays to the client +for indexing. There are several methods of ingesting data into Elasticsearch, which we will cover here. + +=== Single document indexing + +When indexing a document, you can either provide an ID or let elasticsearch generate one for you. + +{zwsp} + + +.Providing an ID value +[source,php] +---- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'id' => 'my_id', + 'body' => [ 'testField' => 'abc'] +]; + +// Document will be indexed to my_index/my_type/my_id +$response = $client->index($params); +---- +{zwsp} + + +.Omitting an ID value +[source,php] +---- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'body' => [ 'testField' => 'abc'] +]; + +// Document will be indexed to my_index/my_type/ +$response = $client->index($params); +---- +{zwsp} + + +If you need to set other parameters, such as a `routing` value, you specify those in the array alongside the `index`, +`type`, etc. For example, let's set the routing and timestamp of this new document: + +.Additional parameters +[source,php] +---- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'id' => 'my_id', + 'routing' => 'company_xyz', + 'timestamp' => strtotime("-1d"), + 'body' => [ 'testField' => 'abc'] +]; + + +$response = $client->index($params); +---- +{zwsp} + + +=== Bulk Indexing + +Elasticsearch also supports bulk indexing of documents. The bulk API expects JSON action/metadata pairs, separated by +newlines. When constructing your documents in PHP, the process is similar. You first create an action array object +(e.g. `index` object), then you create a document body object. This process repeats for all your documents. + +A simple example might look like this: + +.Bulk indexing with PHP arrays +[source,php] +---- +for($i = 0; $i < 100; $i++) { + $params['body'][] = [ + 'index' => [ + '_index' => 'my_index', + '_type' => 'my_type', + ] + ]; + + $params['body'][] = [ + 'my_field' => 'my_value', + 'second_field' => 'some more values' + ]; +} + +$responses = $client->bulk($params); +---- + +In practice, you'll likely have more documents than you want to send in a single bulk request. In that case, you need +to batch up the requests and periodically send them: + + +.Bulk indexing with batches +[source,php] +---- +$params = ['body' => []]; + +for ($i = 1; $i <= 1234567; $i++) { + $params['body'][] = [ + 'index' => [ + '_index' => 'my_index', + '_type' => 'my_type', + '_id' => $i + ] + ]; + + $params['body'][] = [ + 'my_field' => 'my_value', + 'second_field' => 'some more values' + ]; + + // Every 1000 documents stop and send the bulk request + if ($i % 1000 == 0) { + $responses = $client->bulk($params); + + // erase the old bulk request + $params = ['body' => []]; + + // unset the bulk response when you are done to save memory + unset($responses); + } +} + +// Send the last batch if it exists +if (!empty($params['body'])) { + $responses = $client->bulk($params); +} +---- + +== Getting Documents + +Elasticsearch provides realtime GETs of documents. This means that as soon as the document has been indexed and your +client receives an acknowledgement, you can immediately retrieve the document from any shard. Get operations are +performed by requesting a document by its full `index/type/id` path: + +[source,php] +---- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'id' => 'my_id' +]; + +// Get doc at /my_index/my_type/my_id +$response = $client->get($params); +---- +{zwsp} + + +== Updating Documents + +Updating a document allows you to either completely replace the contents of the existing document, or perform a partial +update to just some fields (either changing an existing field, or adding new fields). + +=== Partial document update + +If you want to partially update a document (e.g. change an existing field, or add a new one) you can do so by specifying +the `doc` in the `body` parameter. This will merge the fields in `doc` with the existing document: + + +[source,php] +---- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'id' => 'my_id', + 'body' => [ + 'doc' => [ + 'new_field' => 'abc' + ] + ] +]; + +// Update doc at /my_index/my_type/my_id +$response = $client->update($params); +---- +{zwsp} + + +=== Scripted document update + +Sometimes you need to perform a scripted update, such as incrementing a counter or appending a new value to an array. +To perform a scripted update, you need to provide a script and (usually) a set of parameters: + +[source,php] +---- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'id' => 'my_id', + 'body' => [ + 'script' => 'ctx._source.counter += count', + 'params' => [ + 'count' => 4 + ] + ] +]; + +$response = $client->update($params); +---- +{zwsp} + + +=== Upserts + +Upserts are "Update or Insert" operations. This means an upsert will attempt to run your update script, but if the document +does not exist (or the field you are trying to update doesn't exist), default values will be inserted instead. + +[source,php] +---- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'id' => 'my_id', + 'body' => [ + 'script' => 'ctx._source.counter += count', + 'params' => [ + 'count' => 4 + ], + 'upsert' => [ + 'counter' => 1 + ] + ] +]; + +$response = $client->update($params); +---- +{zwsp} + + + +== Deleting documents + +Finally, you can delete documents by specifying their full `/index/type/id` path: + +[source,php] +---- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'id' => 'my_id' +]; + +// Delete doc at /my_index/my_type/my_id +$response = $client->delete($params); +---- +{zwsp} + diff --git a/vendor/elasticsearch/elasticsearch/docs/futures.asciidoc b/vendor/elasticsearch/elasticsearch/docs/futures.asciidoc new file mode 100644 index 0000000..2d69662 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/futures.asciidoc @@ -0,0 +1,259 @@ + +== Future Mode + +The client offers a mode called "future" or "async" mode. This allows batch processing of requests (sent in parallel +to the cluster), which can have a dramatic impact on performance and throughput. + +PHP is fundamentally single-threaded, however libcurl provides functionality called the "multi interface". This allows +languages like PHP to gain concurrency by providing a batch of requests to process. The batch is executed in a parallel +fashion by the underlying multithreaded libcurl library, and the batch of responses is then returned to PHP. + +In a single-threaded environment, the time to execute `n` requests is the sum of those `n` requests' latencies. With +the multi interface, the time to execute `n` requests is the latency of the slowest request (assuming enough handles +are available to execute all requests in parallel). + +Furthermore, the multi-interface allows requests to different hosts simultaneously, which means the Elasticsearch-PHP +client can more effectively utilize your full cluster. + +=== Using Future Mode + +Utilizing this feature is relatively straightforward, but it does introduce more responsibility into your code. To enable +future mode, set the `future` flag in the client options to `'lazy'`: + +[source,php] +---- +$client = ClientBuilder::create()->build(); + +$params = [ + 'index' => 'test', + 'type' => 'test', + 'id' => 1, + 'client' => [ + 'future' => 'lazy' + ] +]; + +$future = $client->get($params); +---- + +This will return a _future_, rather than the actual response. A future represents a _future computation_ and acts like +a placeholder. You can pass a future around your code like a regular object. When you need the result values, you +can _resolve_ the future. If the future has already resolved (due to some other activity), the values will be immediately +available. If the future has not resolved yet, the resolution will block until those values have become available (e.g. +after the API call completes). + +In practice, this means you can queue up a batch of requests by using `future: lazy` and they will pend until you resolve +the futures, at which time all requests will be sent in parallel to the cluster and return asynchronously to curl. + +This sounds tricky, but it is actually very simple thanks to RingPHP's `FutureArray` interface, which makes the future +act like a simple associative array. For example: + +[source,php] +---- +$client = ClientBuilder::create()->build(); + +$params = [ + 'index' => 'test', + 'type' => 'test', + 'id' => 1, + 'client' => [ + 'future' => 'lazy' + ] +]; + +$future = $client->get($params); + +$doc = $future['_source']; // This call will block and force the future to resolve +---- + +Interacting with the future as an associative array, just like a normal response, will cause the future to resolve +that particular value (which in turn resolves all pending requests and values). This allows patterns such as: + +[source,php] +---- +$client = ClientBuilder::create()->build(); +$futures = []; + +for ($i = 0; $i < 1000; $i++) { + $params = [ + 'index' => 'test', + 'type' => 'test', + 'id' => $i, + 'client' => [ + 'future' => 'lazy' + ] + ]; + + $futures[] = $client->get($params); //queue up the request +} + + +foreach ($futures as $future) { + // access future's values, causing resolution if necessary + echo $future['_source']; +} +---- + +The queued requests will execute in parallel and populate their futures after execution. Batch size defaults to +100 requests-per-batch. + +If you wish to force future resolution, but don't actually need the values immediately, you can call `wait()` on the future +to force resolution too: + +[source,php] +---- +$client = ClientBuilder::create()->build(); +$futures = []; + +for ($i = 0; $i < 1000; $i++) { + $params = [ + 'index' => 'test', + 'type' => 'test', + 'id' => $i, + 'client' => [ + 'future' => 'lazy' + ] + ]; + + $futures[] = $client->get($params); //queue up the request +} + +//wait() forces future resolution and will execute the underlying curl batch +$futures[999]->wait(); +---- + +=== Changing batch size + +The default batch size is 100, meaning 100 requests will queue up before the client forces futures to begin resolving +(e.g. initiate a `curl_multi` call). The batch size can be changed depending on your preferences. The batch size +is controllable via the `max_handles` setting when configuring the handler: + +[source,php] +---- +$handlerParams = [ + 'max_handles' => 500 +]; + +$defaultHandler = ClientBuilder::defaultHandler($handlerParams); + +$client = ClientBuilder::create() + ->setHandler($defaultHandler) + ->build(); +---- + +This will change the behavior to wait on 500 queued requests before sending the batch. Note, however, that forcing a +future to resolve will cause the underlying curl batch to execute, regardless of if the batch is "full" or not. In this +example, only 499 requests are added to the queue...but the final future resolution will force the batch to flush +anyway: + +[source,php] +---- +$handlerParams = [ + 'max_handles' => 500 +]; + +$defaultHandler = ClientBuilder::defaultHandler($handlerParams); + +$client = ClientBuilder::create() + ->setHandler($defaultHandler) + ->build(); + +$futures = []; + +for ($i = 0; $i < 499; $i++) { + $params = [ + 'index' => 'test', + 'type' => 'test', + 'id' => $i, + 'client' => [ + 'future' => 'lazy' + ] + ]; + + $futures[] = $client->get($params); //queue up the request +} + +// resolve the future, and therefore the underlying batch +$body = $future[499]['body']; +---- + +=== Heterogeneous batches are OK + +It is possible to queue up heterogeneous batches of requests. For example, you can queue up several GETs, indexing requests +and a search: + +[source,php] +---- +$client = ClientBuilder::create()->build(); +$futures = []; + +$params = [ + 'index' => 'test', + 'type' => 'test', + 'id' => 1, + 'client' => [ + 'future' => 'lazy' + ] +]; + +$futures['getRequest'] = $client->get($params); // First request + +$params = [ + 'index' => 'test', + 'type' => 'test', + 'id' => 2, + 'body' => [ + 'field' => 'value' + ], + 'client' => [ + 'future' => 'lazy' + ] +]; + +$futures['indexRequest'] = $client->index($params); // Second request + +$params = [ + 'index' => 'test', + 'type' => 'test', + 'body' => [ + 'query' => [ + 'match' => [ + 'field' => 'value' + ] + ] + ], + 'client' => [ + 'future' => 'lazy' + ] +]; + +$futures['searchRequest'] = $client->search($params); // Third request + +// Resolve futures...blocks until network call completes +$searchResults = $futures['searchRequest']['hits']; + +// Should return immediately, since the previous future resolved the entire batch +$doc = $futures['getRequest']['_source']; +---- + +=== Caveats to Future mode + +There are a few caveats to using future mode. The biggest is also the most obvious: you need to deal with resolving the +future yourself. This is usually trivial, but can sometimes introduce unexpected complications. + +For example, if you resolve manually using `wait()`, you may need to call `wait()` several times if there were retries. +This is because each retry will introduce another layer of wrapped futures, and each needs to be resolved to get the +final result. + +This is not needed if you access values via the ArrayInterface however (e.g. `$response['hits']['hits']`), since +FutureArrayInterface will automatically and fully resolve the future to provide values. + +Another caveat is that certain APIs will lose their "helper" functionality. For example, "exists" APIs (e.g. +`$client->exists()`, `$client->indices()->exists`, `$client->indices->templateExists()`, etc) typically return a true +or false under normal operation. + +When operated in future mode, unwrapping of the future is left to your application, +which means the client can no longer inspect the response and return a simple true/false. Instead, you'll see the raw +response from Elasticsearch and will have to take action appropriately. + +This also applies to `ping()`. diff --git a/vendor/elasticsearch/elasticsearch/docs/index-operations.asciidoc b/vendor/elasticsearch/elasticsearch/docs/index-operations.asciidoc new file mode 100644 index 0000000..879c440 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/index-operations.asciidoc @@ -0,0 +1,274 @@ + +== Index Management Operations + +Index management operations allow you to manage the indices in your Elasticsearch cluster, such as creating, deleting and +updating indices and their mappings/settings. + +=== Create an index + +The index operations are all contained under a distinct namespace, separated from other methods that are on the root +client object. As an example, let's create a new index: + +[source,php] +---- +$client = ClientBuilder::create()->build(); +$params = [ + 'index' => 'my_index' +]; + +// Create the index +$response = $client->indices()->create($params); +---- +{zwsp} + + +You can specify any parameters that would normally be included in a new index creation API. All parameters that +would normally go in the request body are located in the 'body' parameter: + +[source,php] +---- +$client = ClientBuilder::create()->build(); +$params = [ + 'index' => 'my_index', + 'body' => [ + 'settings' => [ + 'number_of_shards' => 3, + 'number_of_replicas' => 2 + ], + 'mappings' => [ + 'my_type' => [ + '_source' => [ + 'enabled' => true + ], + 'properties' => [ + 'first_name' => [ + 'type' => 'string', + 'analyzer' => 'standard' + ], + 'age' => [ + 'type' => 'integer' + ] + ] + ] + ] + ] +]; + + +// Create the index with mappings and settings now +$response = $client->indices()->create($params); +---- +{zwsp} + + +=== Create an index (advanced example) + +This is a more complicated example of creating an index, showing how to define analyzers, tokenizers, filters and +index settings. Although essentially the same as the previous example, the more complicated example can be helpful +for "real world" usage of the client, since this particular syntax is easy to mess up. + +[source,php] +---- +$params = [ + 'index' => 'reuters', + 'body' => [ + 'settings' => [ <1> + 'number_of_shards' => 1, + 'number_of_replicas' => 0, + 'analysis' => [ <2> + 'filter' => [ + 'shingle' => [ + 'type' => 'shingle' + ] + ], + 'char_filter' => [ + 'pre_negs' => [ + 'type' => 'pattern_replace', + 'pattern' => '(\\w+)\\s+((?i:never|no|nothing|nowhere|noone|none|not|havent|hasnt|hadnt|cant|couldnt|shouldnt|wont|wouldnt|dont|doesnt|didnt|isnt|arent|aint))\\b', + 'replacement' => '~$1 $2' + ], + 'post_negs' => [ + 'type' => 'pattern_replace', + 'pattern' => '\\b((?i:never|no|nothing|nowhere|noone|none|not|havent|hasnt|hadnt|cant|couldnt|shouldnt|wont|wouldnt|dont|doesnt|didnt|isnt|arent|aint))\\s+(\\w+)', + 'replacement' => '$1 ~$2' + ] + ], + 'analyzer' => [ + 'reuters' => [ + 'type' => 'custom', + 'tokenizer' => 'standard', + 'filter' => ['lowercase', 'stop', 'kstem'] + ] + ] + ] + ], + 'mappings' => [ <3> + '_default_' => [ <4> + 'properties' => [ + 'title' => [ + 'type' => 'text', + 'analyzer' => 'reuters', + 'term_vector' => 'yes', + 'copy_to' => 'combined' + ], + 'body' => [ + 'type' => 'text', + 'analyzer' => 'reuters', + 'term_vector' => 'yes', + 'copy_to' => 'combined' + ], + 'combined' => [ + 'type' => 'text', + 'analyzer' => 'reuters', + 'term_vector' => 'yes' + ], + 'topics' => [ + 'type' => 'text', + 'index' => 'not_analyzed' + ], + 'places' => [ + 'type' => 'text', + 'index' => 'not_analyzed' + ] + ] + ], + 'my_type' => [ <5> + 'properties' => [ + 'my_field' => [ + 'type' => 'text' + ] + ] + ] + ] + ] +]; +$client->indices()->create($params); +---- +<1> The top level `settings` contains config about the index (# of shards, etc) as well as analyzers +<2> `analysis` is nested inside of `settings`, and contains tokenizers, filters, char filters and analyzers +<3> `mappings` is another element nested inside of `settings`, and contains the mappings for various types +<4> The `_default_` type is a dynamic template that is applied to all fields that don't have an explicit mapping +<5> The `my_type` type is an example of a user-defined type that holds a single field, `my_field` + + +=== Delete an index + +Deleting an index is very simple: + +[source,php] +---- +$params = ['index' => 'my_index']; +$response = $client->indices()->delete($params); +---- +{zwsp} + + +=== Put Settings API +The Put Settings API allows you to modify any index setting that is dynamic: + +[source,php] +---- +$params = [ + 'index' => 'my_index', + 'body' => [ + 'settings' => [ + 'number_of_replicas' => 0, + 'refresh_interval' => -1 + ] + ] +]; + +$response = $client->indices()->putSettings($params); +---- +{zwsp} + + +=== Get Settings API + +Get Settings API will show you the currently configured settings for one or more indexes: + +[source,php] +---- +// Get settings for one index +$params = ['index' => 'my_index']; +$response = $client->indices()->getSettings($params); + +// Get settings for several indices +$params = [ + 'index' => [ 'my_index', 'my_index2' ] +]; +$response = $client->indices()->getSettings($params); +---- +{zwsp} + + +=== Put Mappings API + +The Put Mappings API allows you to modify or add to an existing index's mapping. + +[source,php] +---- +// Set the index and type +$params = [ + 'index' => 'my_index', + 'type' => 'my_type2', + 'body' => [ + 'my_type2' => [ + '_source' => [ + 'enabled' => true + ], + 'properties' => [ + 'first_name' => [ + 'type' => 'string', + 'analyzer' => 'standard' + ], + 'age' => [ + 'type' => 'integer' + ] + ] + ] + ] +]; + +// Update the index mapping +$client->indices()->putMapping($params); +---- +{zwsp} + + +=== Get Mappings API + +The Get Mappings API will return the mapping details about your indexes and types. Depending on the mappings that you wish to retrieve, you can specify a number of combinations of index and type: + +[source,php] +---- +// Get mappings for all indexes and types +$response = $client->indices()->getMapping(); + +// Get mappings for all types in 'my_index' +$params = ['index' => 'my_index']; +$response = $client->indices()->getMapping($params); + +// Get mappings for all types of 'my_type', regardless of index +$params = ['type' => 'my_type' ]; +$response = $client->indices()->getMapping($params); + +// Get mapping 'my_type' in 'my_index' +$params = [ + 'index' => 'my_index' + 'type' => 'my_type' +]; +$response = $client->indices()->getMapping($params); + +// Get mappings for two indexes +$params = [ + 'index' => [ 'my_index', 'my_index2' ] +]; +$response = $client->indices()->getMapping($params); +---- +{zwsp} + + +=== Other APIs in the Indices Namespace +There are a number of other APIs in the indices namespace that allow you to manage your elasticsearch indexes (add/remove templates, flush segments, close indexes, etc). + +If you use an IDE with autocompletion, you should be able to easily explore the indices namespace by typing: + +[source,php] +---- +$client->indices()-> +---- +And perusing the list of available methods. Alternatively, browsing the `\Elasticsearch\Namespaces\Indices.php` file will show you the full list of available method calls (as well as parameter lists in the comments for each method). diff --git a/vendor/elasticsearch/elasticsearch/docs/index.asciidoc b/vendor/elasticsearch/elasticsearch/docs/index.asciidoc new file mode 100644 index 0000000..4403e2e --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/index.asciidoc @@ -0,0 +1,40 @@ + += Elasticsearch-PHP + +include::overview.asciidoc[] + +include::quickstart.asciidoc[] + +include::installation.asciidoc[] + +include::configuration.asciidoc[] + +include::per-request-configuration.asciidoc[] + +include::futures.asciidoc[] + +include::php_json_objects.asciidoc[] + +include::index-operations.asciidoc[] + +include::crud.asciidoc[] + +include::search-operations.asciidoc[] + +include::namespaces.asciidoc[] + +include::security.asciidoc[] + +include::connection-pool.asciidoc[] + +include::selectors.asciidoc[] + +include::serializers.asciidoc[] + +include::php-version-requirement.asciidoc[] + +include::breaking-changes.asciidoc[] + +include::community.asciidoc[] + +include::build/classes.asciidoc[] diff --git a/vendor/elasticsearch/elasticsearch/docs/installation.asciidoc b/vendor/elasticsearch/elasticsearch/docs/installation.asciidoc new file mode 100644 index 0000000..47b8011 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/installation.asciidoc @@ -0,0 +1,79 @@ +== Installation + +Elasticsearch-php only has four requirements that you need to worry about: + +* PHP 5.6.6 or higher +* http://getcomposer.org[Composer] +* http://php.net/manual/en/book.curl.php[ext-curl]: the Libcurl extension for PHP (see note below) +* Native JSON Extensions (`ext-json`) 1.3.7 or higher + +The rest of the dependencies will automatically be downloaded and installed by Composer. Composer is a package and dependency manager for PHP. Installing elasticsearch-php with Composer is very easy. + +[NOTE] +.Libcurl can be replaced +==== +The default HTTP handlers that ship with Elasticsearch-php require the PHP libcurl extension, but it is not technically +required for the client to operate. If you have a host that does not have libcurl installed, you can use an +alternate HTTP handler based on PHP streams. Performance _will_ suffer, as the libcurl extension is much faster. +==== + +=== Version Matrix + +You need to match your version of Elasticsearch to the appropriate version of this library. + +The master branch will always track Elasticsearch master, but it is not recommended to use `dev-master` in your production code. + +[width="40%",options="header",frame="topbot"] +|============================ +|Elasticsearch Version | Elasticsearch-PHP Branch +| >= 5.0 | `5.0` +| >= 1.0, <= 5.0 | `1.0`, `2.0` +| <= 0.90.* | `0.4` +|============================ + +=== Composer Installation + +* Include elasticsearch-php in your `composer.json` file. If you are starting a new project, simply paste the following JSON snippet into a new file called `composer.json`. If you have an existing project, include this requirement under the rest of requirements already present: ++ +[source,json] +-------------------------- +{ + "require": { + "elasticsearch/elasticsearch": "~5.0" + } +} +-------------------------- + +* Install the client with composer. The first command downloads the `composer.phar` PHP package, and the second command invokes the installation. Composer will automatically download any required dependencies, store them in a /vendor/ directory and build an autoloader. ++ +[source,shell] +-------------------------- +curl -s http://getcomposer.org/installer | php +php composer.phar install --no-dev +-------------------------- ++ +More information about http://getcomposer.org/[Composer can be found at their website]. + +* Finally, include the generated autoloader in your main project. If your project is already based on Composer, the autoloader is likely already included somewhere and you don't need to add it again. Finally, instantiate a new client: ++ +[source,php] +-------------------------- +require 'vendor/autoload.php'; + +$client = Elasticsearch\ClientBuilder::create()->build(); +-------------------------- ++ +Client instantiation is performed with a static helper function `create()`. This creates a ClientBuilder object, +which helps you to set custom configurations. When you are done configuring, you call the `build()` method to generate +a `Client` object. We'll discuss configuration more in the Configuration section. + + +=== --no-dev flag +You'll notice that the installation command specified `--no-dev`. This prevents Composer +from installing the various testing and development dependencies. For average users, there +is no need to install the test suite. In particular, the development dependencies include +a full copy of Elasticsearch so that tests can be run against the REST specifications. This +is a rather large download for non-developers, hence the `--no-dev` flag + +If you wish to contribute to development of this library, just omit the `--no-dev` flag to +be able to run tests. diff --git a/vendor/elasticsearch/elasticsearch/docs/namespaces.asciidoc b/vendor/elasticsearch/elasticsearch/docs/namespaces.asciidoc new file mode 100644 index 0000000..13f093b --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/namespaces.asciidoc @@ -0,0 +1,82 @@ + +== Namespaces + +The client has a number of "namespaces", which generally expose administrative +functionality. The namespaces correspond to the various administrative endpoints +in Elasticsearch. This is a complete list of namespaces: + + +[width="40%",options="header",frame="topbot"] +|============================ +| Namespace | Functionality +| `indices()` | Index-centric stats and info +| `nodes()` | Node-centric stats and info +| `cluster()` | Cluster-centric stats and info +| `snapshot()` | Methods to snapshot/restore your cluster and indices +| `cat()` | Access to the Cat API (which is generally used standalone from the command line +|============================ + +Some methods are available in several different namespaces, which give you +the same information but grouped into different contexts. To see how these +namespaces work, let's look at the `_stats` output: + + +[source,php] +---- +$client = ClientBuilder::create()->build(); + +// Index Stats +// Corresponds to curl -XGET localhost:9200/_stats +$response = $client->indices()->stats(); + +// Node Stats +// Corresponds to curl -XGET localhost:9200/_nodes/stats +$response = $client->nodes()->stats(); + +// Cluster Stats +// Corresponds to curl -XGET localhost:9200/_cluster/stats +$response = $client->cluster()->stats(); +---- +{zwsp} + + +As you can see, the same `stats()` call is made through three different +namespaces. Sometimes the methods require parameters. These parameters work +just like any other method in the library. + +For example, we can request index stats about a specific index, or multiple +indices: + +[source,php] +---- +$client = ClientBuilder::create()->build(); + +// Corresponds to curl -XGET localhost:9200/my_index/_stats +$params['index'] = 'my_index'; +$response = $client->indices()->stats($params); + +// Corresponds to curl -XGET localhost:9200/my_index1,my_index2/_stats +$params['index'] = array('my_index1', 'my_index2'); +$response = $client->indices()->stats($params); +---- +{zwsp} + + +As another example, here is how you might add an alias to an existing index: + +[source,php] +---- +$params['body'] = array( + 'actions' => array( + array( + 'add' => array( + 'index' => 'myindex', + 'alias' => 'myalias' + ) + ) + ) +); +$client->indices()->updateAliases($params); +---- + +Notice how both the `stats` calls and the updateAlias took a variety of parameters, +each according to what the particular API requires. The `stats` API only requires +an index name(s), while the `updateAlias` requires a body of actions. diff --git a/vendor/elasticsearch/elasticsearch/docs/overview.asciidoc b/vendor/elasticsearch/elasticsearch/docs/overview.asciidoc new file mode 100644 index 0000000..f2dda5a --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/overview.asciidoc @@ -0,0 +1,8 @@ +== Overview + +This is the official PHP client for Elasticsearch. It is designed to be a very low-level client that does not stray from the REST API. + +All methods closely match the REST API, and furthermore, match the method structure of other language clients (ruby, python, etc). We hope that this consistency makes it easy to get started with a client, and to seamlessly switch from one language to the next with minimal effort. + +The client is designed to be "unopinionated". There are a few universal niceties added to the client (cluster state sniffing, round-robin requests, etc) but largely it is very barebones. This was intentional. We want a common base that more sophisticated libraries can build on top of. + diff --git a/vendor/elasticsearch/elasticsearch/docs/per-request-configuration.asciidoc b/vendor/elasticsearch/elasticsearch/docs/per-request-configuration.asciidoc new file mode 100644 index 0000000..cc74662 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/per-request-configuration.asciidoc @@ -0,0 +1,295 @@ + +== Per-request configuration + +There are several configurations that can be set on a per-request basis, rather than at a connection- or client-level. +These are specified as part of the request associative array. + +=== Ignoring exceptions +The library attempts to throw exceptions for common problems. These exceptions match the HTTP response code provided +by Elasticsearch. For example, attempting to GET a nonexistent document will throw a `MissingDocument404Exception`. + +Exceptions are a useful and consistent way to deal with problems like missing documents, syntax errors, version +conflicts, etc. But sometimes you want to deal with the response body rather than catch exceptions (often useful +in test suites). + +If you need that behavior, you can configure an `ignore` parameter. This should be configured in the `client` parameter +of the reuqest array. For example, this example will ignore the `MissingDocument404Exception` +exception and instead return the JSON provided by Elasticsearch. + + +[source,php] +---- +$client = ClientBuilder::create()->build(); + +$params = [ + 'index' => 'test_missing', + 'type' => 'test', + 'id' => 1, + 'client' => [ 'ignore' => 404 ] <1> +]; +echo $client->get($params); + +> {"_index":"test_missing","_type":"test","_id":"1","found":false} +---- +<1> This will ignore just the 404 missing exception + +You can specify multiple HTTP status codes to ignore, by providing an array of values: + +[source,php] +---- +$client = ClientBuilder::create()->build(); + +$params = [ + 'index' => 'test_missing', + 'type' => 'test', + 'client' => [ 'ignore' => [400, 404] ] <1> +]; +echo $client->get($params); + +> No handler found for uri [/test_missing/test/] and method [GET] + +---- +<1> `ignore` also accepts an array of exceptions to ignore. In this example, +the `BadRequest400Exception` is being ignored + + +It should be noted that the response is simply a string, which may or may not be encoded as JSON. In the first example, +the response body was a complete JSON object which could be decoded. In the second example, it was simply a string. + +Since the client has no way of knowing what the exception response will contain, no attempts to decode it are taken. + +=== Providing custom query parameters + +Sometimes you need to provide custom query params, such as authentication tokens for a third-party plugin or proxy. +All query parameters are white-listed in Elasticsearch-php, which is to protect you from specifying a param which is +not accepted by Elasticsearch. + +If you need custom parameters, you need to bypass this whitelisting mechanism. To do so, add them to the `custom` +parameter as an array of values: + +[source,php] +---- +$client = ClientBuilder::create()->build(); + +$params = [ + 'index' => 'test', + 'type' => 'test', + 'id' => 1, + 'parent' => 'abc', // white-listed Elasticsearch parameter + 'client' => [ + 'custom' => [ + 'customToken' => 'abc', // user-defined, not white listed, not checked + 'otherToken' => 123 + ] + ] +]; +$exists = $client->exists($params); +---- + + +=== Increasing the Verbosity of responses + +By default, the client will only return the response body. If you require more information (e.g. stats about the transfer, +headers, status codes, etc), you can tell the client to return a more verbose response. This is enabled via the +`verbose` parameter in the client options. + +Without verbosity, all you see is the response body: + +[source,php] +---- +$client = ClientBuilder::create()->build(); + +$params = [ + 'index' => 'test', + 'type' => 'test', + 'id' => 1 +]; +$response = $client->get($params); +print_r($response); + + +Array +( + [_index] => test + [_type] => test + [_id] => 1 + [_version] => 1 + [found] => 1 + [_source] => Array + ( + [field] => value + ) + +) +---- + +With verbosity turned on, you will see all of the transfer stats: + +[source,php] +---- +$client = ClientBuilder::create()->build(); + +$params = [ + 'index' => 'test', + 'type' => 'test', + 'id' => 1, + 'client' => [ + 'verbose' => true + ] +]; +$response = $client->get($params); +print_r($response); + + +Array +( + [transfer_stats] => Array + ( + [url] => http://127.0.0.1:9200/test/test/1 + [content_type] => application/json; charset=UTF-8 + [http_code] => 200 + [header_size] => 86 + [request_size] => 51 + [filetime] => -1 + [ssl_verify_result] => 0 + [redirect_count] => 0 + [total_time] => 0.00289 + [namelookup_time] => 9.7E-5 + [connect_time] => 0.000265 + [pretransfer_time] => 0.000322 + [size_upload] => 0 + [size_download] => 96 + [speed_download] => 33217 + [speed_upload] => 0 + [download_content_length] => 96 + [upload_content_length] => -1 + [starttransfer_time] => 0.002796 + [redirect_time] => 0 + [redirect_url] => + [primary_ip] => 127.0.0.1 + [certinfo] => Array + ( + ) + + [primary_port] => 9200 + [local_ip] => 127.0.0.1 + [local_port] => 62971 + ) + + [curl] => Array + ( + [error] => + [errno] => 0 + ) + + [effective_url] => http://127.0.0.1:9200/test/test/1 + [headers] => Array + ( + [Content-Type] => Array + ( + [0] => application/json; charset=UTF-8 + ) + + [Content-Length] => Array + ( + [0] => 96 + ) + + ) + + [status] => 200 + [reason] => OK + [body] => Array + ( + [_index] => test + [_type] => test + [_id] => 1 + [_version] => 1 + [found] => 1 + [_source] => Array + ( + [field] => value + ) + ) +) +---- + +=== Curl Timeouts + +It is possible to configure per-request curl timeouts via the `timeout` and `connect_timeout` parameters. These +control the client-side, curl timeouts. The `connect_timeout` parameter controls how long curl should wait for the +"connect" phase to finish, while the `timeout` parameter controls how long curl should wait for the entire request +to finish. + +If either timeout expires, curl will close the connection and return an error. Both parameters should be specified +in seconds. + +Note: client-side timeouts *do not* mean that Elasticsearch aborts the request. Elasticsearch will continue executing +the request until it completes. In the case of a slow query or bulk request, the operation will continue executing +"in the background", unknown to your client. If your client kills connections rapidly with a timeout, only to immediately +execute another request, it is possible to swamp the server with many connections because there is no "back-pressure" on the +client. In these situations, you will see the appropriate threadpool queue growing in size, and may start receiving +`EsRejectedExecutionException` exceptions from Elasticsearch when the queue finally reaches capacity. + +[source,php] +---- +$client = ClientBuilder::create()->build(); + +$params = [ + 'index' => 'test', + 'type' => 'test', + 'id' => 1, + 'client' => [ + 'timeout' => 10, // ten second timeout + 'connect_timeout' => 10 + ] +]; +$response = $client->get($params); +---- + +=== Enabling Future Mode + +The client supports asynchronous, batch processing of requests. This is enabled (if your HTTP handler supports it) on +a per-request basis via the `future` parameter in the client options: + +[source,php] +---- +$client = ClientBuilder::create()->build(); + +$params = [ + 'index' => 'test', + 'type' => 'test', + 'id' => 1, + 'client' => [ + 'future' => 'lazy' + ] +]; +$future = $client->get($params); +$results = $future->wait(); // resolve the future +---- + +Future mode supports two options: `true` or `lazy`. For more details about how asynchronous execution functions, and +how to work with the results, see the dedicated page on <<_future_mode>>. + +=== SSL Encryption + +Normally, you will specify SSL configurations when you create the client (see <<_security>> for more details), since encryption typically +applies to all requests. However, it is possible to configure on a per-request basis too if you need that functionality. +For example, if you need to use a self-signed cert on a specific request, you can specify it via the `verify` parameter +in the client options: + + +[source,php] +---- +$client = ClientBuilder::create()->build(); + +$params = [ + 'index' => 'test', + 'type' => 'test', + 'id' => 1, + 'client' => [ + 'verify' => 'path/to/cacert.pem' //Use a self-signed certificate + ] +]; +$result = $client->get($params); +---- diff --git a/vendor/elasticsearch/elasticsearch/docs/php-version-requirement.asciidoc b/vendor/elasticsearch/elasticsearch/docs/php-version-requirement.asciidoc new file mode 100644 index 0000000..b45fb5a --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/php-version-requirement.asciidoc @@ -0,0 +1,4 @@ +== PHP Version Requirement + +Version 5.0 of Elasticsearch-PHP requires PHP version 5.6.6 or higher. In addition, it requires the native JSON +extension to be version 1.3.7 or higher. diff --git a/vendor/elasticsearch/elasticsearch/docs/php_json_objects.asciidoc b/vendor/elasticsearch/elasticsearch/docs/php_json_objects.asciidoc new file mode 100644 index 0000000..8704eab --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/php_json_objects.asciidoc @@ -0,0 +1,161 @@ +[[php_json_objects]] +== Dealing with JSON Arrays and Objects in PHP + +A common source of confusion with the client revolves around JSON arrays and objects, and how to specify them in PHP. +In particular, problems are caused by empty objects and arrays of objects. This page will show you some common patterns +used in Elasticsearch JSON API, and how to convert that to a PHP representation. + +=== Empty Objects + +The Elasticsearch API uses empty JSON objects in several locations, and this can cause problems for PHP. Unlike other +languages, PHP does not have a "short" notation for empty objects and so many developers are unaware how to specify +an empty object. + +Consider adding a Highlight to a query: + +[source,json] +---- +{ + "query" : { + "match" : { + "content" : "quick brown fox" + } + }, + "highlight" : { + "fields" : { + "content" : {} <1> + } + } +} +---- +<1> This empty JSON object is what causes problems. + +The problem is that PHP will automatically convert `"content" : {}` into `"content" : []`, which is no longer valid +Elasticsearch DSL. We need to tell PHP that the empty object is explicitly an object, not an array. To define this +query in PHP, you would do: + +[source,json] +---- +$params['body'] = array( + 'query' => array( + 'match' => array( + 'content' => 'quick brown fox' + ) + ), + 'highlight' => array( + 'fields' => array( + 'content' => new \stdClass() <1> + ) + ) +); +$results = $client->search($params); +---- +<1> We use the generic PHP stdClass object to represent an empty object. The JSON will now encode correctly. + +By using an explicit stdClass object, we can force the `json_encode` parser to correctly output an empty object, instead +of an empty array. Sadly, this verbose solution is the only way to acomplish the goal in PHP...there is no "short" +version of an empty object. + +=== Arrays of Objects + +Another common pattern in Elasticsearch DSL is an array of objects. For example, consider adding a sort to your query: + +[source,json] +---- +{ + "query" : { + "match" : { "content" : "quick brown fox" } + }, + "sort" : [ <1> + {"time" : {"order" : "desc"}}, + {"popularity" : {"order" : "desc"}} + ] +} +---- +<1> "sort" contains an array of JSON objects + +This arrangement is *very* common, but the construction in PHP can be tricky since it requires nesting arrays. The +verbosity of PHP tends to obscure what is actually going on. To construct an array of objects, you actually need +an array of arrays: + +[source,json] +---- +$params['body'] = array( + 'query' => array( + 'match' => array( + 'content' => 'quick brown fox' + ) + ), + 'sort' => array( <1> + array('time' => array('order' => 'desc')), <2> + array('popularity' => array('order' => 'desc')) <3> + ) +); +$results = $client->search($params); +---- +<1> This array encodes the `"sort" : []` array +<2> This array encodes the `{"time" : {"order" : "desc"}}` object +<3> This array encodes the `{"popularity" : {"order" : "desc"}}` object + +If you are on PHP 5.4+, I would strongly encourage you to use the short array syntax. It makes these nested arrays +much simpler to read: + +[source,json] +---- +$params['body'] = [ + 'query' => [ + 'match' => [ + 'content' => 'quick brown fox' + ] + ], + 'sort' => [ + ['time' => ['order' => 'desc']], + ['popularity' => ['order' => 'desc']] + ] +]; +$results = $client->search($params); +---- + +=== Arrays of empty objects + +Occasionally, you'll encounter DSL that requires both of the previous patterns. The function score query is a good +example, it sometimes requires an array of objects, and some of those objects might be empty JSON objects. + +Given this query: +[source,json] +---- +{ + "query":{ + "function_score":{ + "functions":[ + { + "random_score":{} + } + ], + "boost_mode":"replace" + } + } +} +---- + +We can build it using the following PHP code: + + +[source,json] +---- +$params['body'] = array( + 'query' => array( + 'function_score' => array( + 'functions' => array( <1> + array( <2> + 'random_score' => new \stdClass() <3> + ) + ) + ) + ) +); +$results = $client->search($params); +---- +<1> This encodes the array of objects: `"functions" : []` +<2> This encodes an object inside the array: `{ "random_score": {} }` +<3> This encodes the empty JSON object: `"random_score": {}` diff --git a/vendor/elasticsearch/elasticsearch/docs/quickstart.asciidoc b/vendor/elasticsearch/elasticsearch/docs/quickstart.asciidoc new file mode 100644 index 0000000..740f698 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/quickstart.asciidoc @@ -0,0 +1,269 @@ + +== Quickstart + +This section will give you a quick overview of the client and how the major functions work. + +=== Installation + +* Include elasticsearch-php in your `composer.json` file: ++ +[source,json] +---------------------------- +{ + "require": { + "elasticsearch/elasticsearch": "~5.0" + } +} +---------------------------- + +* Install the client with composer: ++ +[source,shell] +---------------------------- +curl -s http://getcomposer.org/installer | php +php composer.phar install --no-dev +---------------------------- + +* Include the autoloader in your main project (if you haven't already), and instantiate a new client : ++ +[source,php] +---------------------------- +require 'vendor/autoload.php'; + +use Elasticsearch\ClientBuilder; + +$client = ClientBuilder::create()->build(); +---------------------------- + + +=== Index a document + +In elasticsearch-php, almost everything is configured by associative arrays. The REST endpoint, document and optional parameters - everything is an associative array. + +To index a document, we need to specify four pieces of information: index, type, id and a document body. This is done by +constructing an associative array of key:value pairs. The request body is itself an associative array with key:value pairs +corresponding to the data in your document: + +[source,php] +---------------------------- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'id' => 'my_id', + 'body' => ['testField' => 'abc'] +]; + +$response = $client->index($params); +print_r($response); +---------------------------- + +The response that you get back indicates the document was created in the index that you specified. The response is an +associative array containing a decoded version of the JSON that Elasticsearch returns: + +[source,php] +---------------------------- +Array +( + [_index] => my_index + [_type] => my_type + [_id] => my_id + [_version] => 1 + [created] => 1 +) + +---------------------------- + +=== Get a document + +Let's get the document that we just indexed. This will simply return the document: + +[source,php] +---------------------------- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'id' => 'my_id' +]; + +$response = $client->get($params); +print_r($response); +---------------------------- + +The response contains some metadata (index, type, etc) as well as a `_source` field...this is the original document +that you sent to Elasticsearch. + +[source,php] +---------------------------- +Array +( + [_index] => my_index + [_type] => my_type + [_id] => my_id + [_version] => 1 + [found] => 1 + [_source] => Array + ( + [testField] => abc + ) + +) +---------------------------- + +=== Search for a document + +Searching is a hallmark of elasticsearch, so let's perform a search. We are going to use the Match query as a demonstration: + +[source,php] +---------------------------- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'body' => [ + 'query' => [ + 'match' => [ + 'testField' => 'abc' + ] + ] + ] +]; + +$response = $client->search($params); +print_r($response); +---------------------------- + +The response is a little different from the previous responses. We see some metadata (`took`, `timed_out`, etc) and +an array named `hits`. This represents your search results. Inside of `hits` is another array named `hits`, which contains +individual search results: + +[source,php] +---------------------------- +Array +( + [took] => 1 + [timed_out] => + [_shards] => Array + ( + [total] => 5 + [successful] => 5 + [failed] => 0 + ) + + [hits] => Array + ( + [total] => 1 + [max_score] => 0.30685282 + [hits] => Array + ( + [0] => Array + ( + [_index] => my_index + [_type] => my_type + [_id] => my_id + [_score] => 0.30685282 + [_source] => Array + ( + [testField] => abc + ) + ) + ) + ) +) +---------------------------- + +=== Delete a document + +Alright, let's go ahead and delete the document that we added previously: + +[source,php] +---------------------------- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'id' => 'my_id' +]; + +$response = $client->delete($params); +print_r($response); +---------------------------- + +You'll notice this is identical syntax to the `get` syntax. The only difference is the operation: `delete` instead of +`get`. The response will confirm the document was deleted: + +[source,php] +---------------------------- +Array +( + [found] => 1 + [_index] => my_index + [_type] => my_type + [_id] => my_id + [_version] => 2 +) +---------------------------- + + +=== Delete an index + +Due to the dynamic nature of elasticsearch, the first document we added automatically built an index with some default settings. Let's delete that index because we want to specify our own settings later: + +[source,php] +---------------------------- +$deleteParams = [ + 'index' => 'my_index' +]; +$response = $client->indices()->delete($deleteParams); +print_r($response); +---------------------------- + +The response: + + +[source,php] +---------------------------- +Array +( + [acknowledged] => 1 +) +---------------------------- + +=== Create an index + +Now that we are starting fresh (no data or index), let's add a new index with some custom settings: + +[source,php] +---------------------------- +$params = [ + 'index' => 'my_index', + 'body' => [ + 'settings' => [ + 'number_of_shards' => 2, + 'number_of_replicas' => 0 + ] + ] +]; + +$response = $client->indices()->create($params); +print_r($response); +---------------------------- + +Elasticsearch will now create that index with your chosen settings, and return an acknowledgement: + +[source,php] +---------------------------- +Array +( + [acknowledged] => 1 +) +---------------------------- + +=== Wrap up + +That was just a crash-course overview of the client and its syntax. If you are familiar with elasticsearch, you'll +notice that the methods are named just like REST endpoints. + +You'll also notice that the client is configured in a manner that facilitates easy discovery via your IDE. All core +actions are available under the `$client` object (indexing, searching, getting, etc). Index and cluster management +are located under the `$client->indices()` and `$client->cluster()` objects, respectively. + +Check out the rest of the Documentation to see how the entire client works. + diff --git a/vendor/elasticsearch/elasticsearch/docs/search-operations.asciidoc b/vendor/elasticsearch/elasticsearch/docs/search-operations.asciidoc new file mode 100644 index 0000000..9b95393 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/search-operations.asciidoc @@ -0,0 +1,274 @@ +== Search Operations + +Well...it isn't called elasticsearch for nothing! Let's talk about search operations in the client. + +The client gives you full access to every query and parameter exposed by the REST API, following the naming scheme as +much as possible. Let's look at a few examples so you can become familiar with the syntax. + +=== Match Query + +Here is a standard curl for a Match query: + +[source,shell] +---- +curl -XGET 'localhost:9200/my_index/my_type/_search' -d '{ + "query" : { + "match" : { + "testField" : "abc" + } + } +}' +---- +{zwsp} + + +And here is the same query constructed in the client: + +[source,php] +---- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'body' => [ + 'query' => [ + 'match' => [ + 'testField' => 'abc' + ] + ] + ] +]; + +$results = $client->search($params); +---- +{zwsp} + + +Notice how the structure and layout of the PHP array is identical to that of the JSON request body. This makes it very +simple to convert JSON examples into PHP. A quick method to check your PHP array (for more complex examples) is to +encode it back to JSON and check by eye: + +[source,php] +---- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'body' => [ + 'query' => [ + 'match' => [ + 'testField' => 'abc' + ] + ] + ] +]; + +print_r(json_encode($params['body'])); + + +{"query":{"match":{"testField":"abc"}}} +---- +{zwsp} + + + +.Using Raw JSON +**** +Sometimes it is convenient to use raw JSON for testing purposes, or when migrating from a different system. You can +use raw JSON as a string in the body, and the client will detect this automatically: + +[source,php] +---- +$json = '{ + "query" : { + "match" : { + "testField" : "abc" + } + } +}'; + +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'body' => $json +]; + +$results = $client->search($params); +---- +**** +{zwsp} + + +Search results follow the same format as Elasticsearch search response, the only difference is that the JSON response is +serialized back into PHP arrays. Working with the search results is as simple as iterating over the array values: + +[source,php] +---- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'body' => [ + 'query' => [ + 'match' => [ + 'testField' => 'abc' + ] + ] + ] +]; + +$results = $client->search($params); + +$milliseconds = $results['took']; +$maxScore = $results['hits']['max_score']; + +$score = $results['hits']['hits'][0]['_score']; +$doc = $results['hits']['hits'][0]['_source']; +---- +{zwsp} + + +=== Bool Queries + +Bool queries can be easily constructed using the client. For example, this query: +[source,shell] +---- +curl -XGET 'localhost:9200/my_index/my_type/_search' -d '{ + "query" : { + "bool" : { + "must": [ + { + "match" : { "testField" : "abc" } + }, + { + "match" : { "testField2" : "xyz" } + } + ] + } + } +}' +---- +{zwsp} + + +Would be structured like this (Note the position of the square brackets): +[source,php] +---- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'body' => [ + 'query' => [ + 'bool' => [ + 'must' => [ + [ 'match' => [ 'testField' => 'abc' ] ], + [ 'match' => [ 'testField2' => 'xyz' ] ], + ] + ] + ] + ] +]; + +$results = $client->search($params); +---- +{zwsp} + + +Notice that the `must` clause accepts an array of arrays. This will be serialized into an array of JSON objects internally, +so the final resulting output will be identical to the curl example. For more details about arrays vs objects in PHP, +see <>. + +=== A more complicated example + +Let's construct a slightly more complicated example: a boolean query that contains both a filter and a query. +This is a very common activity in elasticsearch queries, so it will be a good demonstration. + +The curl version of the query: + +[source,shell] +---- +curl -XGET 'localhost:9200/my_index/my_type/_search' -d '{ + "query" : { + "bool" : { + "filter" : { + "term" : { "my_field" : "abc" } + }, + "should" : { + "match" : { "my_other_field" : "xyz" } + } + } + } +}' +---- +{zwsp} + + +And in PHP: + +[source,php] +---- +$params = [ + 'index' => 'my_index', + 'type' => 'my_type', + 'body' => [ + 'query' => [ + 'bool' => [ + 'filter' => [ + 'term' => [ 'my_field' => 'abc' ] + ], + 'should' => [ + 'match' => [ 'my_other_field' => 'xyz' ] + ] + ] + ] + ] +]; + + +$results = $client->search($params); +---- +{zwsp} + + + +=== Scrolling + +The Scrolling functionality of Elasticsearch is used to paginate over many documents in a bulk manner, such as exporting +all the documents belonging to a single user. It is more efficient than regular search because it doesn't need to maintain +an expensive priority queue ordering the documents. + +Scrolling works by maintaining a "point in time" snapshot of the index which is then used to page over. +This window allows consistent paging even if there is background indexing/updating/deleting. First, you execute a search +request with `scroll` enabled. This returns a "page" of documents, and a scroll_id which is used to continue +paginating through the hits. + +More details about scrolling can be found in the https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-scroll.html[Link: reference documentation]. + +This is an example which can be used as a template for more advanced operations: + +[source,php] +---- +$client = ClientBuilder::create()->build(); +$params = [ + "scroll" => "30s", // how long between scroll requests. should be small! + "size" => 50, // how many results *per shard* you want back + "index" => "my_index", + "body" => [ + "query" => [ + "match_all" => new \stdClass() + ] + ] +]; + +// Execute the search +// The response will contain the first batch of documents +// and a scroll_id +$response = $client->search($params); + +// Now we loop until the scroll "cursors" are exhausted +while (isset($response['hits']['hits']) && count($response['hits']['hits']) > 0) { + + // ** + // Do your work here, on the $response['hits']['hits'] array + // ** + + // When done, get the new scroll_id + // You must always refresh your _scroll_id! It can change sometimes + $scroll_id = $response['_scroll_id']; + + // Execute a Scroll request and repeat + $response = $client->scroll([ + "scroll_id" => $scroll_id, //...using our previously obtained _scroll_id + "scroll" => "30s" // and the same timeout window + ] + ); +} +---- diff --git a/vendor/elasticsearch/elasticsearch/docs/security.asciidoc b/vendor/elasticsearch/elasticsearch/docs/security.asciidoc new file mode 100644 index 0000000..b4484fb --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/security.asciidoc @@ -0,0 +1,115 @@ + +== Security + +The Elasticsearch-PHP client supports two security features: HTTP Authentication and SSL encryption. + +=== HTTP Authentication + +If your Elasticsearch server is protected by HTTP Authentication, you need to provide the credentials to ES-PHP so +that requests can be authenticated server-side. Authentication credentials are provided as part of the host array +when instantiating the client: + +[source,php] +---- +$hosts = [ + 'http://user:pass@localhost:9200', // HTTP Basic Authentication + 'http://user2:pass2@other-host.com:9200' // Different credentials on different host +]; + +$client = ClientBuilder::create() + ->setHosts($hosts) + ->build(); +---- + +Credentials are provided per-host, which allows each host to have their own set of credentials. All requests sent to the +cluster will use the appropriate credentials depending on the node being talked to. + +=== SSL Encryption + +Configuring SSL is a little more complex. You need to identify if your certificate has been signed by a public +Certificate Authority (CA), or if it is a self-signed certificate. + +[NOTE] +.A note on libcurl version +================= +If you believe the client is configured to correctly use SSL, but it simply is not working, check your libcurl +version. On certain platforms, various features may or may not be available depending on version number of libcurl. +For example, the `--cacert` option was not added to the OSX version of libcurl until version 7.37.1. The `--cacert` +option is equivalent to PHP's `CURLOPT_CAINFO` constant, meaning that custom certificate paths will not work on lower +versions. + +If you are encountering problems, update your libcurl version and/or check the http://curl.haxx.se/changes.html[curl changelog]. +================= + +==== Public CA Certificates + +If your certificate has been signed by a public Certificate Authority and your server has up-to-date root certificates, +you only need to use `https` in the host path. The client will automatically verify SSL certificates: + +[source,php] +---- +$hosts = [ + 'https://localhost:9200' <1> +]; + +$client = ClientBuilder::create() + ->setHosts($hosts) + ->build(); +---- +<1> Note that `https` is used, not `http` + + +If your server has out-dated root certificates, you may need to use a certificate bundle. For PHP clients, the best +way is to use https://github.com/Kdyby/CurlCaBundle[Kdyby/CurlCaBundle]. Once installed, you need to tell the client to +use your certificates instead of the system-wide bundle. To do this, specify the path to verify: + + +[source,php] +---- +$hosts = ['https://localhost:9200']; +$caBundle = \Kdyby\CurlCaBundle\CertificateHelper::getCaInfoFile(); + +$client = ClientBuilder::create() + ->setHosts($hosts) + ->setSSLVerification($caBundle) + ->build(); +---- + +==== Self-signed Certificates + +Self-signed certificates are certs that have not been signed by a public CA. They are signed by your own organization. +Self-signed certificates are often used for internal purposes, when you can securely spread the root certificate +yourself. It should not be used when being exposed to public consumers, since this leaves the client vulnerable to +man-in-the-middle attacks. + +If you are using a self-signed certificate, you need to provide the certificate to the client. This is the same syntax +as specifying a new root bundle, but instead you point to your certificate: + +[source,php] +---- +$hosts = ['https://localhost:9200']; +$myCert = 'path/to/cacert.pem'; + +$client = ClientBuilder::create() + ->setHosts($hosts) + ->setSSLVerification($myCert) + ->build(); +---- + + +=== Using Authentication with SSL + +It is possible to use HTTP authentication with SSL. Simply specify `https` in the URI, configure SSL settings as +required and provide authentication credentials. For example, this snippet will authenticate using Basic HTTP auth +and a self-signed certificate: + +[source,php] +---- +$hosts = ['https://user:pass@localhost:9200']; +$myCert = 'path/to/cacert.pem'; + +$client = ClientBuilder::create() + ->setHosts($hosts) + ->setSSLVerification($myCert) + ->build(); +---- \ No newline at end of file diff --git a/vendor/elasticsearch/elasticsearch/docs/selectors.asciidoc b/vendor/elasticsearch/elasticsearch/docs/selectors.asciidoc new file mode 100644 index 0000000..f091c1f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/selectors.asciidoc @@ -0,0 +1,110 @@ + +== Selectors + +The connection pool maintains the list of connections, and decides when nodes should transition from alive to dead (and +vice versa). It has no logic to choose connections, however. That job belongs to the Selector class. + +The selector's job is to return a single connection from a provided array of connections. Like the Connection Pool, +there are several implementations to choose from. + +=== RoundRobinSelector (Default) + +This selector returns connections in a round-robin fashion. Node #1 is selected on the first request, Node #2 on +the second request, etc. This ensures an even load of traffic across your cluster. Round-robin'ing happens on a +per-request basis (e.g. sequential requests go to different nodes). + +The `RoundRobinSelector` is default, but if you wish to explicitily configure it you can do: + +[source,php] +---- +$client = ClientBuilder::create() + ->setSelector('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->build(); +---- + +Note that the implementation is specified via a namespace path to the class. + +=== StickyRoundRobinSelector + +This selector is "sticky", in that it prefers to reuse the same connection repeatedly. For example, Node #1 is chosen +on the first request. Node #1 will continue to be re-used for each subsequent request until that node fails. Upon failure, +the selector will round-robin to the next available node, then "stick" to that node. + +This is an ideal strategy for many PHP scripts. Since PHP scripts are shared-nothing and tend to exit quickly, creating +new connections for each request is often a sub-optimal strategy and introduces a lot of overhead. Instead, it is +better to "stick" to a single connection for the duration of the script. + +By default, this selector will randomize the hosts upon initialization, which will still guarantee an even distribution +of load across the cluster. It changes the round-robin dynamics from per-request to per-script. + +If you are using <<_future_mode>>, the "sticky" behavior of this selector will be non-ideal, since all parallel requests +will go to the same node instead of multiple nodes in your cluster. When using future mode, the default `RoundRobinSelector` +should be preferred. + +If you wish to use this selector, you may do so with: + +[source,php] +---- +$client = ClientBuilder::create() + ->setSelector('\Elasticsearch\ConnectionPool\Selectors\StickyRoundRobinSelector') + ->build(); +---- + +Note that the implementation is specified via a namespace path to the class. + +=== RandomSelector + +This selector simply returns a random node, regardless of state. It is generally just for testing. + +If you wish to use this selector, you may do so with: + +[source,php] +---- +$client = ClientBuilder::create() + ->setSelector('\Elasticsearch\ConnectionPool\Selectors\RandomSelector') + ->build(); +---- + +Note that the implementation is specified via a namespace path to the class. + +=== Custom Selector + +You can implement your own custom selector. Custom selectors must implement `SelectorInterface` + +[source,php] +---- +namespace MyProject\Selectors; + +use Elasticsearch\Connections\ConnectionInterface; +use Elasticsearch\ConnectionPool\Selectors\SelectorInterface + +class MyCustomSelector implements SelectorInterface +{ + + /** + * Selects the first connection + * + * @param array $connections Array of Connection objects + * + * @return ConnectionInterface + */ + public function select($connections) + { + // code here + } + +} +---- +{zwsp} + + +You can then use your custom selector either via object injection or namespace instantiation: + +[source,php] +---- +$mySelector = new MyCustomSelector(); + +$client = ClientBuilder::create() + ->setSelector($mySelector) // object injection + ->setSelector('\MyProject\Selectors\FirstSelector') // or namespace + ->build(); +---- diff --git a/vendor/elasticsearch/elasticsearch/docs/serializers.asciidoc b/vendor/elasticsearch/elasticsearch/docs/serializers.asciidoc new file mode 100644 index 0000000..4b90fd5 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/docs/serializers.asciidoc @@ -0,0 +1,167 @@ + +== Serializers + +The client has three serializers available. You will most likely never need +to change the serializer, unless you have special requirements or are +implementing a new protocol. + +The job of the serializer is to encode the outgoing request body and decode +the incoming response body. In 99% of cases, this is a simple conversion +to/from JSON. + +The default serializer is the `SmartSerializer` + +=== SmartSerializer +==== Serialize() +The `SmartSerializer` inspects the data to be encoded. If the request body +is provided as a string, it is passed directly to Elasticsearch as a string. +This allows users to provide raw JSON, or raw strings for certain endpoints that +dont have structure (such as the Analyze endpoint). + +If the data is an array, it is converted to json. If the data provided was an +empty array, the serializer manually converts the JSON from an empty array (`[]`) +to an empty object (`{}`) so that it is valid JSON for Elasticsearch request +bodies. + +==== Deserialize() +When decoding the response body, the `SmartSerializer` introspects the +`content_type` headers to determine the appropriate encoding. If the data is +encoded as JSON, it is decoded into an array using `json_decode`. Otherwise, +it is returned as a string. + +This functionality is required to cooperate with endpoints such as the `Cat` +endpoints, which return tabular text instead of JSON. + +==== Selecting the SmartSerializer + +The SmartSerializer is selected by default, but if you wish to manually configure it for explicitness, you can +do so by using the `setSerializer()` method on the ClientBuilder object: + +[source,php] +---- +$client = ClientBuilder::create() + ->setSerializer('\Elasticsearch\Serializers\SmartSerializer'); + ->build(); +---- + +Note that the serializer is configured by specifying a namespace path to the serializer. + +=== ArrayToJSONSerializer +==== Serialize() +The `ArrayToJSONSerializer` inspects the data to be encoded. If the request body +is provided as a string, it is passed directly to Elasticsearch as a string. +This allows users to provide raw JSON, or raw strings for certain endpoints that +dont have structure (such as the Analyze endpoint). + +If the data is an array, it is converted to json. If the data provided was an +empty array, the serializer manually converts the JSON from an empty array (`[]`) +to an empty object (`{}`) so that it is valid JSON for Elasticsearch request +bodies. + +==== Deserialize() +When decoding the response body, everything is decoded to JSON from JSON. If +the data is not valid JSON, `null` will be returned. + +==== Selecting the ArrayToJSONSerializer + +You can select `ArrayToJSONSerializer` by using the `setSerializer()` method on the ClientBuilder object: + +[source,php] +---- +$client = ClientBuilder::create() + ->setSerializer('\Elasticsearch\Serializers\ArrayToJSONSerializer'); + ->build(); +---- + +Note that the serializer is configured by specifying a namespace path to the serializer. + +=== EverythingToJSONSerializer +==== Serialize() +The `EverythingToJSONSerializer` tries to convert everything to JSON. + +If the data provided was an empty array, the serializer manually converts the +JSON from an empty array (`[]`) to an empty object (`{}`) so that it is valid +JSON for Elasticsearch request bodies. + +If the data was not an array and/or not convertible to JSON, the method returns +`null`. + +==== Deserialize() +When decoding the response body, everything is decoded to JSON from JSON. If +the data is not valid JSON, `null` will be returned. + +==== Selecting the EverythingToJSONSerializer + +You can select `EverythingToJSONSerializer` by using the `setSerializer()` method on the ClientBuilder object: + +[source,php] +---- +$client = ClientBuilder::create() + ->setSerializer('\Elasticsearch\Serializers\EverythingToJSONSerializer'); + ->build(); +---- + +Note that the serializer is configured by specifying a namespace path to the serializer. + +=== Implementing your own Serializer +If you want to use your own custom serializer, you need to implement the `SerializerInterface` interface. Please +keep in mind that the client uses a single Serializer object for all endpoints and all connections. + + +[source,php] +---- +class MyCustomSerializer implements SerializerInterface +{ + + /** + * Serialize request body + * + * @param string|array $data Request body + * + * @return string + */ + public function serialize($data) + { + // code here + } + + /** + * Deserialize response body + * + * @param string $data Response body + * @param array $headers Response Headers + * + * @return array|string + */ + public function deserialize($data, $headers) + { + // code here + } +} +---- +{zwsp} + + +To then use your custom serializer, you can specify the namespace path in the `setSerializer()` method of the ClientBuilder +object: + +[source,php] +---- +$client = ClientBuilder::create() + ->setSerializer('\MyProject\Serializers\MyCustomSerializer'); + ->build(); +---- + +Alternatively, if your serializer has a constructor or further initialization that should occur before given to the +client, you can instantiate an object and provide that instead: + +[source,php] +---- +$mySerializer = new MyCustomSerializer($a, $b, $c); +$mySerializer->setFoo("bar"); + +$client = ClientBuilder::create() + ->setSerializer($mySerializer); + ->build(); +---- + + diff --git a/vendor/elasticsearch/elasticsearch/phpunit-integration.xml b/vendor/elasticsearch/elasticsearch/phpunit-integration.xml new file mode 100644 index 0000000..e7cd6dd --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/phpunit-integration.xml @@ -0,0 +1,23 @@ + + + + + + + tests/Elasticsearch/Tests/YamlRunnerTest.php + + + + + src + + + diff --git a/vendor/elasticsearch/elasticsearch/phpunit.xml b/vendor/elasticsearch/elasticsearch/phpunit.xml new file mode 100644 index 0000000..841f48e --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/phpunit.xml @@ -0,0 +1,24 @@ + + + + + + + tests + tests/Elasticsearch/Tests/YamlRunnerTest.php + + + + + src + + + diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Client.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Client.php new file mode 100644 index 0000000..0690fc2 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Client.php @@ -0,0 +1,1508 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Client +{ + /** + * @var Transport + */ + public $transport; + + /** + * @var array + */ + protected $params; + + /** + * @var IndicesNamespace + */ + protected $indices; + + /** + * @var ClusterNamespace + */ + protected $cluster; + + /** + * @var NodesNamespace + */ + protected $nodes; + + /** + * @var SnapshotNamespace + */ + protected $snapshot; + + /** + * @var CatNamespace + */ + protected $cat; + + /** + * @var IngestNamespace + */ + protected $ingest; + + /** + * @var TasksNamespace + */ + protected $tasks; + + /** @var callback */ + protected $endpoints; + + /** @var NamespaceBuilderInterface[] */ + protected $registeredNamespaces = []; + + /** + * Client constructor + * + * @param Transport $transport + * @param callable $endpoint + * @param AbstractNamespace[] $registeredNamespaces + */ + public function __construct(Transport $transport, callable $endpoint, array $registeredNamespaces) + { + $this->transport = $transport; + $this->endpoints = $endpoint; + $this->indices = new IndicesNamespace($transport, $endpoint); + $this->cluster = new ClusterNamespace($transport, $endpoint); + $this->nodes = new NodesNamespace($transport, $endpoint); + $this->snapshot = new SnapshotNamespace($transport, $endpoint); + $this->cat = new CatNamespace($transport, $endpoint); + $this->ingest = new IngestNamespace($transport, $endpoint); + $this->tasks = new TasksNamespace($transport, $endpoint); + $this->registeredNamespaces = $registeredNamespaces; + } + + /** + * @param $params + * @return array + */ + public function info($params = []) + { + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Info $endpoint */ + $endpoint = $endpointBuilder('Info'); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * @param $params array Associative array of parameters + * + * @return bool + */ + public function ping($params = []) + { + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Ping $endpoint */ + $endpoint = $endpointBuilder('Ping'); + $endpoint->setParams($params); + + try { + $this->performRequest($endpoint); + } catch (Missing404Exception $exception) { + return false; + } catch (TransportException $exception) { + return false; + } catch (NoNodesAvailableException $exception) { + return false; + } + + return true; + } + + /** + * $params['id'] = (string) The document ID (Required) + * ['index'] = (string) The name of the index (Required) + * ['type'] = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required) + * ['ignore_missing'] = ?? + * ['fields'] = (list) A comma-separated list of fields to return in the response + * ['parent'] = (string) The ID of the parent document + * ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + * ['realtime'] = (boolean) Specify whether to perform the operation in realtime or search mode + * ['refresh'] = (boolean) Refresh the shard containing the document before performing the operation + * ['routing'] = (string) Specific routing value + * ['_source'] = (list) True or false to return the _source field or not, or a list of fields to return + * ['_source_exclude'] = (list) A list of fields to exclude from the returned _source field + * ['_source_include'] = (list) A list of fields to extract and return from the _source field + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function get($params) + { + $id = $this->extractArgument($params, 'id'); + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Get $endpoint */ + $endpoint = $endpointBuilder('Get'); + $endpoint->setID($id) + ->setIndex($index) + ->setType($type); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['id'] = (string) The document ID (Required) + * ['index'] = (string) The name of the index (Required) + * ['type'] = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required) + * ['ignore_missing'] = ?? + * ['parent'] = (string) The ID of the parent document + * ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + * ['realtime'] = (boolean) Specify whether to perform the operation in realtime or search mode + * ['refresh'] = (boolean) Refresh the shard containing the document before performing the operation + * ['routing'] = (string) Specific routing value + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function getSource($params) + { + $id = $this->extractArgument($params, 'id'); + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Get $endpoint */ + $endpoint = $endpointBuilder('Get'); + $endpoint->setID($id) + ->setIndex($index) + ->setType($type) + ->returnOnlySource(); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['id'] = (string) The document ID (Required) + * ['index'] = (string) The name of the index (Required) + * ['type'] = (string) The type of the document (Required) + * ['consistency'] = (enum) Specific write consistency setting for the operation + * ['parent'] = (string) ID of parent document + * ['refresh'] = (boolean) Refresh the index after performing the operation + * ['replication'] = (enum) Specific replication type + * ['routing'] = (string) Specific routing value + * ['timeout'] = (time) Explicit operation timeout + * ['version_type'] = (enum) Specific version type + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function delete($params) + { + $id = $this->extractArgument($params, 'id'); + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + + $this->verifyNotNullOrEmpty("id", $id); + $this->verifyNotNullOrEmpty("type", $type); + $this->verifyNotNullOrEmpty("index", $index); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Delete $endpoint */ + $endpoint = $endpointBuilder('Delete'); + $endpoint->setID($id) + ->setIndex($index) + ->setType($type); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * + * $params['_source'] = (list) True or false to return the _source field or not, or a list of fields to return + * ['_source_exclude'] = (array) A list of fields to exclude from the returned _source field + * ['_source_include'] = (array) A list of fields to extract and return from the _source field + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['analyze_wildcard'] = (bool) Specify whether wildcard and prefix queries should be analyzed (default: false) + * ['analyzer'] = (string) The analyzer to use for the query string + * ['conflicts'] = (enum) What to do when the delete-by-query hits version conflicts? + * ['default_operator'] = (enum) The default operator for query string query (AND or OR) + * ['df'] = (string) The field to use as default where no field prefix is given in the query string + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * ['from'] = (number) Starting offset (default: 0) + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['lenient'] = (bool) Specify whether format-based query failures (such as providing text to a numeric field) should be ignored + * ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + * ['q'] = (string) Query in the Lucene query string syntax + * ['refresh'] = (bool) Should the effected indexes be refreshed? + * ['request_cache'] = (bool) Specify if request cache should be used for this request or not, defaults to index level setting + * ['requests_per_second'] = (number) The throttle for this request in sub-requests per second. -1 means no throttle. + * ['routing'] = (array) A comma-separated list of specific routing values + * ['scroll'] = (number) Specify how long a consistent view of the index should be maintained for scrolled search + * ['scroll_size'] = (number) Size on the scroll request powering the update_by_query + * ['search_timeout'] = (number) Explicit timeout for each search request. Defaults to no timeout. + * ['search_type'] = (enum) Search operation type + * ['size'] = (number) Number of hits to return (default: 10) + * ['slices'] = (integer) The number of slices this task should be divided into. Defaults to 1 meaning the task isn't sliced into subtasks. + * ['sort'] = (array) A comma-separated list of : pairs + * ['stats'] = (array) Specific 'tag' of the request for logging and statistical purposes + * ['terminate_after'] = (number) The maximum number of documents to collect for each shard, upon reaching which the query execution will terminate early. + * ['timeout'] = (number) Time each individual bulk request should wait for shards that are unavailable. + * ['version'] = (bool) Specify whether to return document version as part of a hit + * ['wait_for_active_shards'] = (string) Sets the number of shard copies that must be active before proceeding with the delete by query operation. Defaults to 1, meaning the primary shard only. Set to `all` for all shard copies, otherwise set to any non-negative value less than or equal to the total number of copies for the shard (number of replicas + 1) + * ['wait_for_completion'] = (bool) Should the request should block until the delete-by-query is complete. + * + * @param array $params + * + * @return array + */ + public function deleteByQuery($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + $type = $this->extractArgument($params, 'type'); + + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\DeleteByQuery $endpoint */ + $endpoint = $endpointBuilder('DeleteByQuery'); + $endpoint->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of indices to restrict the results + * ['type'] = (list) A comma-separated list of types to restrict the results + * ['min_score'] = (number) Include only documents with a specific `_score` value in the result + * ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + * ['routing'] = (string) Specific routing value + * ['source'] = (string) The URL-encoded query definition (instead of using the request body) + * ['body'] = (array) A query to restrict the results (optional) + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function count($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Count $endpoint */ + $endpoint = $endpointBuilder('Count'); + $endpoint->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of indices to restrict the results + * ['type'] = (list) A comma-separated list of types to restrict the results + * ['id'] = (string) ID of document + * ['ignore_unavailable'] = (boolean) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + * ['routing'] = (string) Specific routing value + * ['allow_no_indices'] = (boolean) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['body'] = (array) A query to restrict the results (optional) + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['percolate_index'] = (string) The index to count percolate the document into. Defaults to index. + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * ['version'] = (number) Explicit version number for concurrency control + * ['version_type'] = (enum) Specific version type + * + * @param $params array Associative array of parameters + * + * @return array + * + * @deprecated + */ + public function countPercolate($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $id = $this->extractArgument($params, 'id'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\CountPercolate $endpoint */ + $endpoint = $endpointBuilder('CountPercolate'); + $endpoint->setIndex($index) + ->setType($type) + ->setID($id) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (string) The name of the index with a registered percolator query (Required) + * ['type'] = (string) The document type (Required) + * ['prefer_local'] = (boolean) With `true`, specify that a local shard should be used if available, with `false`, use a random shard (default: true) + * ['body'] = (array) The document (`doc`) to percolate against registered queries; optionally also a `query` to limit the percolation to specific registered queries + * + * @param $params array Associative array of parameters + * + * @return array + * + * @deprecated + */ + public function percolate($params) + { + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $id = $this->extractArgument($params, 'id'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Percolate $endpoint */ + $endpoint = $endpointBuilder('Percolate'); + $endpoint->setIndex($index) + ->setType($type) + ->setID($id) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (string) Default index for items which don't provide one + * ['type'] = (string) Default document type for items which don't provide one + * ['ignore_unavailable'] = (boolean) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (boolean) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return array + * + * @deprecated + */ + public function mpercolate($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\MPercolate $endpoint */ + $endpoint = $endpointBuilder('MPercolate'); + $endpoint->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (string) Default index for items which don't provide one + * ['type'] = (string) Default document type for items which don't provide one + * ['term_statistics'] = (boolean) Specifies if total term frequency and document frequency should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + * ['field_statistics'] = (boolean) Specifies if document count, sum of document frequencies and sum of total term frequencies should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + * ['fields'] = (list) A comma-separated list of fields to return. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + * ['offsets'] = (boolean) Specifies if term offsets should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + * ['positions'] = (boolean) Specifies if term positions should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + * ['payloads'] = (boolean) Specifies if term payloads should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\". + * ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) .Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\". + * ['routing'] = (string) Specific routing value. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\". + * ['parent'] = (string) Parent id of documents. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\". + * ['realtime'] = (boolean) Specifies if request is real-time as opposed to near-real-time (default: true). + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function termvectors($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $id = $this->extractArgument($params, 'id'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\TermVectors $endpoint */ + $endpoint = $endpointBuilder('TermVectors'); + $endpoint->setIndex($index) + ->setType($type) + ->setID($id) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (string) Default index for items which don't provide one + * ['type'] = (string) Default document type for items which don't provide one + * ['ids'] = (list) A comma-separated list of documents ids. You must define ids as parameter or set \"ids\" or \"docs\" in the request body + * ['term_statistics'] = (boolean) Specifies if total term frequency and document frequency should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + * ['field_statistics'] = (boolean) Specifies if document count, sum of document frequencies and sum of total term frequencies should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + * ['fields'] = (list) A comma-separated list of fields to return. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + * ['offsets'] = (boolean) Specifies if term offsets should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + * ['positions'] = (boolean) Specifies if term positions should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"." + * ['payloads'] = (boolean) Specifies if term payloads should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\". + * ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) .Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\". + * ['routing'] = (string) Specific routing value. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\". + * ['parent'] = (string) Parent id of documents. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\". + * ['realtime'] = (boolean) Specifies if request is real-time as opposed to near-real-time (default: true). + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function mtermvectors($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\MTermVectors $endpoint */ + $endpoint = $endpointBuilder('MTermVectors'); + $endpoint->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['id'] = (string) The document ID (Required) + * ['index'] = (string) The name of the index (Required) + * ['type'] = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required) + * ['parent'] = (string) The ID of the parent document + * ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + * ['realtime'] = (boolean) Specify whether to perform the operation in realtime or search mode + * ['refresh'] = (boolean) Refresh the shard containing the document before performing the operation + * ['routing'] = (string) Specific routing value + * + * @param $params array Associative array of parameters + * + * @return array | boolean + */ + public function exists($params) + { + $id = $this->extractArgument($params, 'id'); + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + + //manually make this verbose so we can check status code + $params['client']['verbose'] = true; + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Exists $endpoint */ + $endpoint = $endpointBuilder('Exists'); + $endpoint->setID($id) + ->setIndex($index) + ->setType($type); + $endpoint->setParams($params); + + return BooleanRequestWrapper::performRequest($endpoint, $this->transport); + } + + /** + * $params['index'] = (string) The name of the index + * ['type'] = (string) The type of the document + * ['fields'] = (list) A comma-separated list of fields to return in the response + * ['parent'] = (string) The ID of the parent document + * ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + * ['realtime'] = (boolean) Specify whether to perform the operation in realtime or search mode + * ['refresh'] = (boolean) Refresh the shard containing the document before performing the operation + * ['routing'] = (string) Specific routing value + * ['body'] = (array) Document identifiers; can be either `docs` (containing full document information) or `ids` (when index and type is provided in the URL. + * ['_source'] = (list) True or false to return the _source field or not, or a list of fields to return + * ['_source_exclude'] = (list) A list of fields to exclude from the returned _source field + * ['_source_include'] = (list) A list of fields to extract and return from the _source field + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function mget($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Mget $endpoint */ + $endpoint = $endpointBuilder('Mget'); + $endpoint->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names to use as default + * ['type'] = (list) A comma-separated list of document types to use as default + * ['search_type'] = (enum) Search operation type + * ['body'] = (array|string) The request definitions (metadata-search request definition pairs), separated by newlines + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function msearch($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Msearch $endpoint */ + $endpoint = $endpointBuilder('Msearch'); + $endpoint->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (string) The name of the index (Required) + * ['type'] = (string) The type of the document (Required) + * ['id'] = (string) Specific document ID (when the POST method is used) + * ['consistency'] = (enum) Explicit write consistency setting for the operation + * ['parent'] = (string) ID of the parent document + * ['refresh'] = (boolean) Refresh the index after performing the operation + * ['replication'] = (enum) Specific replication type + * ['routing'] = (string) Specific routing value + * ['timeout'] = (time) Explicit operation timeout + * ['timestamp'] = (time) Explicit timestamp for the document + * ['ttl'] = (duration) Expiration time for the document + * ['version'] = (number) Explicit version number for concurrency control + * ['version_type'] = (enum) Specific version type + * ['body'] = (array) The document + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function create($params) + { + $id = $this->extractArgument($params, 'id'); + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Create $endpoint */ + $endpoint = $endpointBuilder('Create'); + $endpoint->setID($id) + ->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (string) Default index for items which don't provide one + * ['type'] = (string) Default document type for items which don't provide one + * ['consistency'] = (enum) Explicit write consistency setting for the operation + * ['refresh'] = (boolean) Refresh the index after performing the operation + * ['replication'] = (enum) Explicitly set the replication type + * ['fields'] = (list) Default comma-separated list of fields to return in the response for updates + * ['body'] = (array) The document + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function bulk($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Bulk $endpoint */ + $endpoint = $endpointBuilder('Bulk'); + $endpoint->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (string) The name of the index (Required) + * ['type'] = (string) The type of the document (Required) + * ['id'] = (string) Specific document ID (when the POST method is used) + * ['consistency'] = (enum) Explicit write consistency setting for the operation + * ['op_type'] = (enum) Explicit operation type + * ['parent'] = (string) ID of the parent document + * ['refresh'] = (boolean) Refresh the index after performing the operation + * ['replication'] = (enum) Specific replication type + * ['routing'] = (string) Specific routing value + * ['timeout'] = (time) Explicit operation timeout + * ['timestamp'] = (time) Explicit timestamp for the document + * ['ttl'] = (duration) Expiration time for the document + * ['version'] = (number) Explicit version number for concurrency control + * ['version_type'] = (enum) Specific version type + * ['body'] = (array) The document + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function index($params) + { + $id = $this->extractArgument($params, 'id'); + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Index $endpoint */ + $endpoint = $endpointBuilder('Index'); + $endpoint->setID($id) + ->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['refresh'] = (boolean) Should the effected indexes be refreshed? + * ['timeout'] = (time) Time each individual bulk request should wait for shards that are unavailable + * ['consistency'] = (enum) Explicit write consistency setting for the operation + * ['wait_for_completion'] = (boolean) Should the request should block until the reindex is complete + * ['requests_per_second'] = (float) The throttle for this request in sub-requests per second. 0 means set no throttle + * ['body'] = (array) The search definition using the Query DSL and the prototype for the index request (Required) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function reindex($params) + { + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + /** @var \Elasticsearch\Endpoints\Reindex $endpoint */ + $endpoint = $endpointBuilder('Reindex'); + $endpoint->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names to restrict the operation; use `_all` or empty string to perform the operation on all indices + * ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones + * ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + * ['routing'] = (string) Specific routing value + * ['source'] = (string) The URL-encoded request definition (instead of using request body) + * ['body'] = (array) The request definition + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function suggest($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Suggest $endpoint */ + $endpoint = $endpointBuilder('Suggest'); + $endpoint->setIndex($index) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['id'] = (string) The document ID (Required) + * ['index'] = (string) The name of the index (Required) + * ['type'] = (string) The type of the document (Required) + * ['analyze_wildcard'] = (boolean) Specify whether wildcards and prefix queries in the query string query should be analyzed (default: false) + * ['analyzer'] = (string) The analyzer for the query string query + * ['default_operator'] = (enum) The default operator for query string query (AND or OR) + * ['df'] = (string) The default field for query string query (default: _all) + * ['fields'] = (list) A comma-separated list of fields to return in the response + * ['lenient'] = (boolean) Specify whether format-based query failures (such as providing text to a numeric field) should be ignored + * ['lowercase_expanded_terms'] = (boolean) Specify whether query terms should be lowercased + * ['parent'] = (string) The ID of the parent document + * ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + * ['q'] = (string) Query in the Lucene query string syntax + * ['routing'] = (string) Specific routing value + * ['source'] = (string) The URL-encoded query definition (instead of using the request body) + * ['_source'] = (list) True or false to return the _source field or not, or a list of fields to return + * ['_source_exclude'] = (list) A list of fields to exclude from the returned _source field + * ['_source_include'] = (list) A list of fields to extract and return from the _source field + * ['body'] = (string) The URL-encoded query definition (instead of using the request body) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function explain($params) + { + $id = $this->extractArgument($params, 'id'); + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Explain $endpoint */ + $endpoint = $endpointBuilder('Explain'); + $endpoint->setID($id) + ->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices + * ['type'] = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types + * ['analyzer'] = (string) The analyzer to use for the query string + * ['analyze_wildcard'] = (boolean) Specify whether wildcard and prefix queries should be analyzed (default: false) + * ['default_operator'] = (enum) The default operator for query string query (AND or OR) + * ['df'] = (string) The field to use as default where no field prefix is given in the query string + * ['explain'] = (boolean) Specify whether to return detailed information about score computation as part of a hit + * ['fields'] = (list) A comma-separated list of fields to return as part of a hit + * ['from'] = (number) Starting offset (default: 0) + * ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones + * ['indices_boost'] = (list) Comma-separated list of index boosts + * ['lenient'] = (boolean) Specify whether format-based query failures (such as providing text to a numeric field) should be ignored + * ['lowercase_expanded_terms'] = (boolean) Specify whether query terms should be lowercased + * ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + * ['q'] = (string) Query in the Lucene query string syntax + * ['query_cache'] = (boolean) Enable query cache for this request + * ['request_cache'] = (boolean) Enable request cache for this request + * ['routing'] = (list) A comma-separated list of specific routing values + * ['scroll'] = (duration) Specify how long a consistent view of the index should be maintained for scrolled search + * ['search_type'] = (enum) Search operation type + * ['size'] = (number) Number of hits to return (default: 10) + * ['sort'] = (list) A comma-separated list of : pairs + * ['source'] = (string) The URL-encoded request definition using the Query DSL (instead of using request body) + * ['_source'] = (list) True or false to return the _source field or not, or a list of fields to return + * ['_source_exclude'] = (list) A list of fields to exclude from the returned _source field + * ['_source_include'] = (list) A list of fields to extract and return from the _source field + * ['stats'] = (list) Specific 'tag' of the request for logging and statistical purposes + * ['suggest_field'] = (string) Specify which field to use for suggestions + * ['suggest_mode'] = (enum) Specify suggest mode + * ['suggest_size'] = (number) How many suggestions to return in response + * ['suggest_text'] = (text) The source text for which the suggestions should be returned + * ['timeout'] = (time) Explicit operation timeout + * ['terminate_after'] = (number) The maximum number of documents to collect for each shard, upon reaching which the query execution will terminate early. + * ['version'] = (boolean) Specify whether to return document version as part of a hit + * ['body'] = (array|string) The search definition using the Query DSL + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function search($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Search $endpoint */ + $endpoint = $endpointBuilder('Search'); + $endpoint->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices + * ['type'] = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types + * ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random) + * ['routing'] = (string) Specific routing value + * ['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function searchShards($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\SearchShards $endpoint */ + $endpoint = $endpointBuilder('SearchShards'); + $endpoint->setIndex($index) + ->setType($type); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices + * ['type'] = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function searchTemplate($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Search $endpoint */ + $endpoint = $endpointBuilder('SearchTemplate'); + $endpoint->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['scroll_id'] = (string) The scroll ID for scrolled search + * ['scroll'] = (duration) Specify how long a consistent view of the index should be maintained for scrolled search + * ['body'] = (string) The scroll ID for scrolled search + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function scroll($params = array()) + { + $scrollID = $this->extractArgument($params, 'scroll_id'); + $body = $this->extractArgument($params, 'body'); + $scroll = $this->extractArgument($params, 'scroll'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Scroll $endpoint */ + $endpoint = $endpointBuilder('Scroll'); + $endpoint->setScrollID($scrollID) + ->setScroll($scroll) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['scroll_id'] = (string) The scroll ID for scrolled search + * ['scroll'] = (duration) Specify how long a consistent view of the index should be maintained for scrolled search + * ['body'] = (string) The scroll ID for scrolled search + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function clearScroll($params = array()) + { + $scrollID = $this->extractArgument($params, 'scroll_id'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\ClearScroll $endpoint */ + $endpoint = $endpointBuilder('ClearScroll'); + $endpoint->setScrollID($scrollID) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['id'] = (string) Document ID (Required) + * ['index'] = (string) The name of the index (Required) + * ['type'] = (string) The type of the document (Required) + * ['consistency'] = (enum) Explicit write consistency setting for the operation + * ['fields'] = (list) A comma-separated list of fields to return in the response + * ['lang'] = (string) The script language (default: mvel) + * ['parent'] = (string) ID of the parent document + * ['refresh'] = (boolean) Refresh the index after performing the operation + * ['replication'] = (enum) Specific replication type + * ['retry_on_conflict'] = (number) Specify how many times should the operation be retried when a conflict occurs (default: 0) + * ['routing'] = (string) Specific routing value + * ['script'] = () The URL-encoded script definition (instead of using request body) + * ['timeout'] = (time) Explicit operation timeout + * ['timestamp'] = (time) Explicit timestamp for the document + * ['ttl'] = (duration) Expiration time for the document + * ['version_type'] = (number) Explicit version number for concurrency control + * ['body'] = (array) The request definition using either `script` or partial `doc` + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function update($params) + { + $id = $this->extractArgument($params, 'id'); + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Update $endpoint */ + $endpoint = $endpointBuilder('Update'); + $endpoint->setID($id) + ->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names to search; use `_all` or + * empty string to perform the operation on all indices (Required) + * ['type'] = (list) A comma-separated list of document types to search; leave empty to + * perform the operation on all types + * ['analyzer'] = (string) The analyzer to use for the query string + * ['analyze_wildcard'] = (boolean) Specify whether wildcard and prefix queries should be analyzed + * (default: false) + * ['default_operator'] = (enum) The default operator for query string query (AND or OR) (AND,OR) + * (default: OR) + * ['df'] = (string) The field to use as default where no field prefix is given in the + * query string + * ['explain'] = (boolean) Specify whether to return detailed information about score + * computation as part of a hit + * ['fields'] = (list) A comma-separated list of fields to return as part of a hit + * ['fielddata_fields'] = (list) A comma-separated list of fields to return as the field data + * representation of a field for each hit + * ['from'] = (number) Starting offset (default: 0) + * ['ignore_unavailable'] = (boolean) Whether specified concrete indices should be ignored when + * unavailable (missing or closed) + * ['allow_no_indices'] = (boolean) Whether to ignore if a wildcard indices expression resolves into + * no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['conflicts'] = (enum) What to do when the reindex hits version conflicts? (abort,proceed) + * (default: abort) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are + * open, closed or both. (open,closed,none,all) (default: open) + * ['lenient'] = (boolean) Specify whether format-based query failures (such as providing + * text to a numeric field) should be ignored + * ['lowercase_expanded_terms'] = (boolean) Specify whether query terms should be lowercased + * ['preference'] = (string) Specify the node or shard the operation should be performed on + * (default: random) + * ['q'] = (string) Query in the Lucene query string syntax + * ['routing'] = (list) A comma-separated list of specific routing values + * ['scroll'] = (duration) Specify how long a consistent view of the index should be + * maintained for scrolled search + * ['search_type'] = (enum) Search operation type (query_then_fetch,dfs_query_then_fetch) + * ['search_timeout'] = (time) Explicit timeout for each search request. Defaults to no timeout. + * ['size'] = (number) Number of hits to return (default: 10) + * ['sort'] = (list) A comma-separated list of : pairs + * ['_source'] = (list) True or false to return the _source field or not, or a list of + * fields to return + * ['_source_exclude'] = (list) A list of fields to exclude from the returned _source field + * ['_source_include'] = (list) A list of fields to extract and return from the _source field + * ['terminate_after'] = (number) The maximum number of documents to collect for each shard, upon + * reaching which the query execution will terminate early. + * ['stats'] = (list) Specific 'tag' of the request for logging and statistical purposes + * ['suggest_field'] = (string) Specify which field to use for suggestions + * ['suggest_mode'] = (enum) Specify suggest mode (missing,popular,always) (default: missing) + * ['suggest_size'] = (number) How many suggestions to return in response + * ['suggest_text'] = (text) The source text for which the suggestions should be returned + * ['timeout'] = (time) Time each individual bulk request should wait for shards that are + * unavailable. (default: 1m) + * ['track_scores'] = (boolean) Whether to calculate and return scores even if they are not used + * for sorting + * ['version'] = (boolean) Specify whether to return document version as part of a hit + * ['version_type'] = (boolean) Should the document increment the version number (internal) on + * hit or not (reindex) + * ['request_cache'] = (boolean) Specify if request cache should be used for this request or not, + * defaults to index level setting + * ['refresh'] = (boolean) Should the effected indexes be refreshed? + * ['consistency'] = (enum) Explicit write consistency setting for the operation + * (one,quorum,all) + * ['scroll_size'] = (integer) Size on the scroll request powering the update_by_query + * ['wait_for_completion'] = (boolean) Should the request should block until the reindex is complete. + * (default: false) + * ['body'] = The search definition using the Query DSL + * + * @param array $params + * + * @return array + */ + public function updateByQuery($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + $body = $this->extractArgument($params, 'body'); + + $type = $this->extractArgument($params, 'type'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\UpdateByQuery $endpoint */ + $endpoint = $endpointBuilder('UpdateByQuery'); + $endpoint->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['id'] = (string) The script ID (Required) + * ['lang'] = (string) The script language (Required) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function getScript($params) + { + $id = $this->extractArgument($params, 'id'); + $lang = $this->extractArgument($params, 'lang'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Script\Get $endpoint */ + $endpoint = $endpointBuilder('Script\Get'); + $endpoint->setID($id) + ->setLang($lang); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['id'] = (string) The script ID (Required) + * ['lang'] = (string) The script language (Required) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function deleteScript($params) + { + $id = $this->extractArgument($params, 'id'); + $lang = $this->extractArgument($params, 'lang'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Script\Delete $endpoint */ + $endpoint = $endpointBuilder('Script\Delete'); + $endpoint->setID($id) + ->setLang($lang); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['id'] = (string) The script ID (Required) + * ['lang'] = (string) The script language (Required) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function putScript($params) + { + $id = $this->extractArgument($params, 'id'); + $lang = $this->extractArgument($params, 'lang'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Script\Put $endpoint */ + $endpoint = $endpointBuilder('Script\Put'); + $endpoint->setID($id) + ->setLang($lang) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['id'] = (string) The search template ID (Required) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function getTemplate($params) + { + $id = $this->extractArgument($params, 'id'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Template\Get $endpoint */ + $endpoint = $endpointBuilder('Template\Get'); + $endpoint->setID($id); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['id'] = (string) The search template ID (Required) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function deleteTemplate($params) + { + $id = $this->extractArgument($params, 'id'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Template\Delete $endpoint */ + $endpoint = $endpointBuilder('Template\Delete'); + $endpoint->setID($id); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['id'] = (string) The search template ID (Required) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function putTemplate($params) + { + $id = $this->extractArgument($params, 'id'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Template\Put $endpoint */ + $endpoint = $endpointBuilder('Template\Put'); + $endpoint->setID($id) + ->setBody($body) + ->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of indices to restrict the results + * ['fields'] = (list) A comma-separated list of fields for to get field statistics for (min value, max value, and more) + * ['level'] = (enum) Defines if field stats should be returned on a per index level or on a cluster wide level + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function fieldStats($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\FieldStats $endpoint */ + $endpoint = $endpointBuilder('FieldStats'); + $endpoint->setIndex($index) + ->setBody($body) + ->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['id'] = (string) ID of the template to render + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function renderSearchTemplate($params = array()) + { + $body = $this->extractArgument($params, 'body'); + $id = $this->extractArgument($params, 'id'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\RenderSearchTemplate $endpoint */ + $endpoint = $endpointBuilder('RenderSearchTemplate'); + $endpoint->setBody($body) + ->setID($id); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * Operate on the Indices Namespace of commands + * + * @return IndicesNamespace + */ + public function indices() + { + return $this->indices; + } + + /** + * Operate on the Cluster namespace of commands + * + * @return ClusterNamespace + */ + public function cluster() + { + return $this->cluster; + } + + /** + * Operate on the Nodes namespace of commands + * + * @return NodesNamespace + */ + public function nodes() + { + return $this->nodes; + } + + /** + * Operate on the Snapshot namespace of commands + * + * @return SnapshotNamespace + */ + public function snapshot() + { + return $this->snapshot; + } + + /** + * Operate on the Cat namespace of commands + * + * @return CatNamespace + */ + public function cat() + { + return $this->cat; + } + + /** + * Operate on the Ingest namespace of commands + * + * @return IngestNamespace + */ + public function ingest() + { + return $this->ingest; + } + + /** + * Operate on the Tasks namespace of commands + * + * @return TasksNamespace + */ + public function tasks() + { + return $this->tasks; + } + + /** + * Catchall for registered namespaces + * + * @param $name + * @param $arguments + * @return Object + * @throws BadMethodCallException if the namespace cannot be found + */ + public function __call($name, $arguments) + { + if (isset($this->registeredNamespaces[$name])) { + return $this->registeredNamespaces[$name]; + } + throw new BadMethodCallException("Namespace [$name] not found"); + } + + /** + * @param array $params + * @param string $arg + * + * @return null|mixed + */ + public function extractArgument(&$params, $arg) + { + if (is_object($params) === true) { + $params = (array) $params; + } + + if (array_key_exists($arg, $params) === true) { + $val = $params[$arg]; + unset($params[$arg]); + + return $val; + } else { + return null; + } + } + + private function verifyNotNullOrEmpty($name, $var) + { + if ($var === null) { + throw new InvalidArgumentException("$name cannot be null."); + } + + if (is_string($var)) { + if (strlen($var) === 0) { + throw new InvalidArgumentException("$name cannot be an empty string"); + } + } + + if (is_array($var)) { + if (strlen(implode("", $var)) === 0) { + throw new InvalidArgumentException("$name cannot be an array of empty strings"); + } + } + } + + /** + * @param $endpoint AbstractEndpoint + * + * @throws \Exception + * @return array + */ + private function performRequest(AbstractEndpoint $endpoint) + { + $promise = $this->transport->performRequest( + $endpoint->getMethod(), + $endpoint->getURI(), + $endpoint->getParams(), + $endpoint->getBody(), + $endpoint->getOptions() + ); + + return $this->transport->resultOrFuture($promise, $endpoint->getOptions()); + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ClientBuilder.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ClientBuilder.php new file mode 100644 index 0000000..a4e8633 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ClientBuilder.php @@ -0,0 +1,696 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class ClientBuilder +{ + /** @var Transport */ + private $transport; + + /** @var callback */ + private $endpoint; + + /** @var NamespaceBuilderInterface[] */ + private $registeredNamespacesBuilders = []; + + /** @var ConnectionFactoryInterface */ + private $connectionFactory; + + private $handler; + + /** @var LoggerInterface */ + private $logger; + + /** @var LoggerInterface */ + private $tracer; + + /** @var string */ + private $connectionPool = '\Elasticsearch\ConnectionPool\StaticNoPingConnectionPool'; + + /** @var string */ + private $serializer = '\Elasticsearch\Serializers\SmartSerializer'; + + /** @var string */ + private $selector = '\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector'; + + /** @var array */ + private $connectionPoolArgs = [ + 'randomizeHosts' => true + ]; + + /** @var array */ + private $hosts; + + /** @var array */ + private $connectionParams; + + /** @var int */ + private $retries; + + /** @var bool */ + private $sniffOnStart = false; + + /** @var null|array */ + private $sslCert = null; + + /** @var null|array */ + private $sslKey = null; + + /** @var null|bool|string */ + private $sslVerification = null; + + /** @var bool */ + private $allowBadJSON = false; + + /** + * @return ClientBuilder + */ + public static function create() + { + return new static(); + } + + /** + * Can supply first parm to Client::__construct() when invoking manually or with dependency injection + * @return this->ransport + * + */ + public function getTransport() + { + return $this->transport; + } + + /** + * Can supply second parm to Client::__construct() when invoking manually or with dependency injection + * @return this->endpoint + * + */ + public function getEndpoint() + { + return $this->endpoint; + } + + /** + * Can supply third parm to Client::__construct() when invoking manually or with dependency injection + * @return this->registeredNamespacesBuilders + * + */ + public function getRegisteredNamespacesBuilders() + { + return $this->registeredNamespacesBuilders; + } + + /** + * Build a new client from the provided config. Hash keys + * should correspond to the method name e.g. ['connectionPool'] + * corresponds to setConnectionPool(). + * + * Missing keys will use the default for that setting if applicable + * + * Unknown keys will throw an exception by default, but this can be silenced + * by setting `quiet` to true + * + * @param array $config hash of settings + * @param bool $quiet False if unknown settings throw exception, true to silently + * ignore unknown settings + * @throws Common\Exceptions\RuntimeException + * @return \Elasticsearch\Client + */ + public static function fromConfig($config, $quiet = false) + { + $builder = new self; + foreach ($config as $key => $value) { + $method = "set$key"; + if (method_exists($builder, $method)) { + $builder->$method($value); + unset($config[$key]); + } + } + + if ($quiet === false && count($config) > 0) { + $unknown = implode(array_keys($config)); + throw new RuntimeException("Unknown parameters provided: $unknown"); + } + return $builder->build(); + } + + /** + * @param array $singleParams + * @param array $multiParams + * @throws \RuntimeException + * @return callable + */ + public static function defaultHandler($multiParams = [], $singleParams = []) + { + $future = null; + if (extension_loaded('curl')) { + $config = array_merge([ 'mh' => curl_multi_init() ], $multiParams); + if (function_exists('curl_reset')) { + $default = new CurlHandler($singleParams); + $future = new CurlMultiHandler($config); + } else { + $default = new CurlMultiHandler($config); + } + } else { + throw new \RuntimeException('Elasticsearch-PHP requires cURL, or a custom HTTP handler.'); + } + + return $future ? Middleware::wrapFuture($default, $future) : $default; + } + + /** + * @param array $params + * @throws \RuntimeException + * @return CurlMultiHandler + */ + public static function multiHandler($params = []) + { + if (function_exists('curl_multi_init')) { + return new CurlMultiHandler(array_merge([ 'mh' => curl_multi_init() ], $params)); + } else { + throw new \RuntimeException('CurlMulti handler requires cURL.'); + } + } + + /** + * @return CurlHandler + * @throws \RuntimeException + */ + public static function singleHandler() + { + if (function_exists('curl_reset')) { + return new CurlHandler(); + } else { + throw new \RuntimeException('CurlSingle handler requires cURL.'); + } + } + + /** + * @param $path string + * @return \Monolog\Logger\Logger + */ + public static function defaultLogger($path, $level = Logger::WARNING) + { + $log = new Logger('log'); + $handler = new StreamHandler($path, $level); + $log->pushHandler($handler); + + return $log; + } + + /** + * @param \Elasticsearch\Connections\ConnectionFactoryInterface $connectionFactory + * @return $this + */ + public function setConnectionFactory(ConnectionFactoryInterface $connectionFactory) + { + $this->connectionFactory = $connectionFactory; + + return $this; + } + + /** + * @param \Elasticsearch\ConnectionPool\AbstractConnectionPool|string $connectionPool + * @param array $args + * @throws \InvalidArgumentException + * @return $this + */ + public function setConnectionPool($connectionPool, array $args = []) + { + if (is_string($connectionPool)) { + $this->connectionPool = $connectionPool; + $this->connectionPoolArgs = $args; + } elseif (is_object($connectionPool)) { + $this->connectionPool = $connectionPool; + } else { + throw new InvalidArgumentException("Serializer must be a class path or instantiated object extending AbstractConnectionPool"); + } + + return $this; + } + + /** + * @param callable $endpoint + * @return $this + */ + public function setEndpoint($endpoint) + { + $this->endpoint = $endpoint; + + return $this; + } + + /** + * @param NamespaceBuilderInterface $namespaceBuilder + * @return $this + */ + public function registerNamespace(NamespaceBuilderInterface $namespaceBuilder) + { + $this->registeredNamespacesBuilders[] = $namespaceBuilder; + + return $this; + } + + /** + * @param \Elasticsearch\Transport $transport + * @return $this + */ + public function setTransport($transport) + { + $this->transport = $transport; + + return $this; + } + + /** + * @param mixed $handler + * @return $this + */ + public function setHandler($handler) + { + $this->handler = $handler; + + return $this; + } + + /** + * @param \Psr\Log\LoggerInterface $logger + * @return $this + */ + public function setLogger($logger) + { + $this->logger = $logger; + + return $this; + } + + /** + * @param \Psr\Log\LoggerInterface $tracer + * @return $this + */ + public function setTracer($tracer) + { + $this->tracer = $tracer; + + return $this; + } + + /** + * @param \Elasticsearch\Serializers\SerializerInterface|string $serializer + * @throws \InvalidArgumentException + * @return $this + */ + public function setSerializer($serializer) + { + $this->parseStringOrObject($serializer, $this->serializer, 'SerializerInterface'); + + return $this; + } + + /** + * @param array $hosts + * @return $this + */ + public function setHosts($hosts) + { + $this->hosts = $hosts; + + return $this; + } + + /** + * @param array $params + * @return $this + */ + public function setConnectionParams(array $params) + { + $this->connectionParams = $params; + + return $this; + } + + /** + * @param int $retries + * @return $this + */ + public function setRetries($retries) + { + $this->retries = $retries; + + return $this; + } + + /** + * @param \Elasticsearch\ConnectionPool\Selectors\SelectorInterface|string $selector + * @throws \InvalidArgumentException + * @return $this + */ + public function setSelector($selector) + { + $this->parseStringOrObject($selector, $this->selector, 'SelectorInterface'); + + return $this; + } + + /** + * @param boolean $sniffOnStart + * @return $this + */ + public function setSniffOnStart($sniffOnStart) + { + $this->sniffOnStart = $sniffOnStart; + + return $this; + } + + /** + * @param $cert + * @param null|string $password + * @return $this + */ + public function setSSLCert($cert, $password = null) + { + $this->sslCert = [$cert, $password]; + + return $this; + } + + /** + * @param $key + * @param null|string $password + * @return $this + */ + public function setSSLKey($key, $password = null) + { + $this->sslKey = [$key, $password]; + + return $this; + } + + /** + * @param bool|string $value + * @return $this + */ + public function setSSLVerification($value = true) + { + $this->sslVerification = $value; + + return $this; + } + + public function allowBadJSONSerialization() + { + $this->allowBadJSON = true; + return $this; + } + + /** + * @return Client + */ + public function build() + { + if(!defined('JSON_PRESERVE_ZERO_FRACTION') && $this->allowBadJSON === false) { + throw new RuntimeException("Your version of PHP / json-ext does not support the constant 'JSON_PRESERVE_ZERO_FRACTION',". + " which is important for proper type mapping in Elasticsearch. Please upgrade your PHP or json-ext.\n". + "If you are unable to upgrade, and are willing to accept the consequences, you may use the allowBadJSONSerialization()". + " method on the ClientBuilder to bypass this limitation."); + } + + $this->buildLoggers(); + + if (is_null($this->handler)) { + $this->handler = ClientBuilder::defaultHandler(); + } + + $sslOptions = null; + if (isset($this->sslKey)) { + $sslOptions['ssl_key'] = $this->sslKey; + } + if (isset($this->sslCert)) { + $sslOptions['cert'] = $this->sslCert; + } + if (isset($this->sslVerification)) { + $sslOptions['verify'] = $this->sslVerification; + } + + if (!is_null($sslOptions)) { + $sslHandler = function (callable $handler, array $sslOptions) { + return function (array $request) use ($handler, $sslOptions) { + // Add our custom headers + foreach ($sslOptions as $key => $value) { + $request['client'][$key] = $value; + } + + // Send the request using the handler and return the response. + return $handler($request); + }; + }; + $this->handler = $sslHandler($this->handler, $sslOptions); + } + + if (is_null($this->serializer)) { + $this->serializer = new SmartSerializer(); + } elseif (is_string($this->serializer)) { + $this->serializer = new $this->serializer; + } + + if (is_null($this->connectionFactory)) { + if (is_null($this->connectionParams)) { + $this->connectionParams = []; + } + + // Make sure we are setting Content-type and Accept (unless the user has explicitly + // overridden it + if (isset($this->connectionParams['client']['headers']) === false) { + $this->connectionParams['client']['headers'] = [ + 'Content-type' => ['application/json'], + 'Accept' => ['application/json'] + ]; + } else { + if (isset($this->connectionParams['client']['headers']['Content-type']) === false) { + $this->connectionParams['client']['headers']['Content-type'] = ['application/json']; + } + if (isset($this->connectionParams['client']['headers']['Accept']) === false) { + $this->connectionParams['client']['headers']['Accept'] = ['application/json']; + } + } + + $this->connectionFactory = new ConnectionFactory($this->handler, $this->connectionParams, $this->serializer, $this->logger, $this->tracer); + } + + if (is_null($this->hosts)) { + $this->hosts = $this->getDefaultHost(); + } + + if (is_null($this->selector)) { + $this->selector = new Selectors\RoundRobinSelector(); + } elseif (is_string($this->selector)) { + $this->selector = new $this->selector; + } + + $this->buildTransport(); + + if (is_null($this->endpoint)) { + $serializer = $this->serializer; + + $this->endpoint = function ($class) use ($serializer) { + $fullPath = '\\Elasticsearch\\Endpoints\\' . $class; + if ($class === 'Bulk' || $class === 'Msearch' || $class === 'MPercolate') { + return new $fullPath($serializer); + } else { + return new $fullPath(); + } + }; + } + + $registeredNamespaces = []; + foreach ($this->registeredNamespacesBuilders as $builder) { + /** @var $builder NamespaceBuilderInterface */ + $registeredNamespaces[$builder->getName()] = $builder->getObject($this->transport, $this->serializer); + } + + return $this->instantiate($this->transport, $this->endpoint, $registeredNamespaces); + } + + /** + * @param Transport $transport + * @param callable $endpoint + * @param Object[] $registeredNamespaces + * @return Client + */ + protected function instantiate(Transport $transport, callable $endpoint, array $registeredNamespaces) + { + return new Client($transport, $endpoint, $registeredNamespaces); + } + + private function buildLoggers() + { + if (is_null($this->logger)) { + $this->logger = new NullLogger(); + } + + if (is_null($this->tracer)) { + $this->tracer = new NullLogger(); + } + } + + private function buildTransport() + { + $connections = $this->buildConnectionsFromHosts($this->hosts); + + if (is_string($this->connectionPool)) { + $this->connectionPool = new $this->connectionPool( + $connections, + $this->selector, + $this->connectionFactory, + $this->connectionPoolArgs); + } elseif (is_null($this->connectionPool)) { + $this->connectionPool = new StaticNoPingConnectionPool( + $connections, + $this->selector, + $this->connectionFactory, + $this->connectionPoolArgs); + } + + if (is_null($this->retries)) { + $this->retries = count($connections); + } + + if (is_null($this->transport)) { + $this->transport = new Transport($this->retries, $this->sniffOnStart, $this->connectionPool, $this->logger); + } + } + + private function parseStringOrObject($arg, &$destination, $interface) + { + if (is_string($arg)) { + $destination = new $arg; + } elseif (is_object($arg)) { + $destination = $arg; + } else { + throw new InvalidArgumentException("Serializer must be a class path or instantiated object implementing $interface"); + } + } + + /** + * @return array + */ + private function getDefaultHost() + { + return ['localhost:9200']; + } + + /** + * @param array $hosts + * + * @throws \InvalidArgumentException + * @return \Elasticsearch\Connections\Connection[] + */ + private function buildConnectionsFromHosts($hosts) + { + if (is_array($hosts) === false) { + $this->logger->error("Hosts parameter must be an array of strings, or an array of Connection hashes."); + throw new InvalidArgumentException('Hosts parameter must be an array of strings, or an array of Connection hashes.'); + } + + $connections = []; + foreach ($hosts as $host) { + if (is_string($host)) { + $host = $this->prependMissingScheme($host); + $host = $this->extractURIParts($host); + } else if (is_array($host)) { + $host = $this->normalizeExtendedHost($host); + } else { + $this->logger->error("Could not parse host: ".print_r($host, true)); + throw new RuntimeException("Could not parse host: ".print_r($host, true)); + } + $connections[] = $this->connectionFactory->create($host); + } + + return $connections; + } + + /** + * @param $host + * @return array + */ + private function normalizeExtendedHost($host) { + if (isset($host['host']) === false) { + $this->logger->error("Required 'host' was not defined in extended format: ".print_r($host, true)); + throw new RuntimeException("Required 'host' was not defined in extended format: ".print_r($host, true)); + } + + if (isset($host['scheme']) === false) { + $host['scheme'] = 'http'; + } + if (isset($host['port']) === false) { + $host['port'] = '9200'; + } + return $host; + } + + /** + * @param array $host + * + * @throws \InvalidArgumentException + * @return array + */ + private function extractURIParts($host) + { + $parts = parse_url($host); + + if ($parts === false) { + throw new InvalidArgumentException("Could not parse URI"); + } + + if (isset($parts['port']) !== true) { + $parts['port'] = 9200; + } + + return $parts; + } + + /** + * @param string $host + * + * @return string + */ + private function prependMissingScheme($host) + { + if (!filter_var($host, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED)) { + $host = 'http://' . $host; + } + + return $host; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/EmptyLogger.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/EmptyLogger.php new file mode 100644 index 0000000..89ced83 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/EmptyLogger.php @@ -0,0 +1,35 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class EmptyLogger extends AbstractLogger implements LoggerInterface +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return null + */ + public function log($level, $message, array $context = array()) + { + return; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/AlreadyExpiredException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/AlreadyExpiredException.php new file mode 100644 index 0000000..411c70a --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/AlreadyExpiredException.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class AlreadyExpiredException extends \Exception implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadMethodCallException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadMethodCallException.php new file mode 100644 index 0000000..d8dea6c --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadMethodCallException.php @@ -0,0 +1,18 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class BadMethodCallException extends \BadMethodCallException implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadRequest400Exception.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadRequest400Exception.php new file mode 100644 index 0000000..1c652d7 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadRequest400Exception.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class BadRequest400Exception extends \Exception implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ClientErrorResponseException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ClientErrorResponseException.php new file mode 100644 index 0000000..844bbcc --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ClientErrorResponseException.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class ClientErrorResponseException extends TransportException implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Conflict409Exception.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Conflict409Exception.php new file mode 100644 index 0000000..d7f10a4 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Conflict409Exception.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Conflict409Exception extends \Exception implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/CouldNotConnectToHost.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/CouldNotConnectToHost.php new file mode 100644 index 0000000..b1ccc22 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/CouldNotConnectToHost.php @@ -0,0 +1,19 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class CouldNotConnectToHost extends TransportException implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/CouldNotResolveHostException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/CouldNotResolveHostException.php new file mode 100644 index 0000000..283afdf --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/CouldNotResolveHostException.php @@ -0,0 +1,19 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class CouldNotResolveHostException extends TransportException implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/OperationTimeoutException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/OperationTimeoutException.php new file mode 100644 index 0000000..12c1722 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Curl/OperationTimeoutException.php @@ -0,0 +1,19 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class OperationTimeoutException extends TransportException implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ElasticsearchException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ElasticsearchException.php new file mode 100644 index 0000000..a5cab88 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ElasticsearchException.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +interface ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Forbidden403Exception.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Forbidden403Exception.php new file mode 100644 index 0000000..2b84c64 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Forbidden403Exception.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Forbidden403Exception extends \Exception implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/InvalidArgumentException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/InvalidArgumentException.php new file mode 100644 index 0000000..65e932b --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/InvalidArgumentException.php @@ -0,0 +1,18 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class InvalidArgumentException extends \InvalidArgumentException implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/MaxRetriesException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/MaxRetriesException.php new file mode 100644 index 0000000..15b2833 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/MaxRetriesException.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class MaxRetriesException extends TransportException implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Missing404Exception.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Missing404Exception.php new file mode 100644 index 0000000..76bc87a --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Missing404Exception.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Missing404Exception extends \Exception implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoDocumentsToGetException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoDocumentsToGetException.php new file mode 100644 index 0000000..75beb9f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoDocumentsToGetException.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class NoDocumentsToGetException extends ServerErrorResponseException implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoNodesAvailableException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoNodesAvailableException.php new file mode 100644 index 0000000..63a1793 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoNodesAvailableException.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class NoNodesAvailableException extends \Exception implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoShardAvailableException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoShardAvailableException.php new file mode 100644 index 0000000..71b9a4a --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/NoShardAvailableException.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class NoShardAvailableException extends ServerErrorResponseException implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RequestTimeout408Exception.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RequestTimeout408Exception.php new file mode 100644 index 0000000..8b668a8 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RequestTimeout408Exception.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class RequestTimeout408Exception extends BadRequest400Exception implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RoutingMissingException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RoutingMissingException.php new file mode 100644 index 0000000..efa3cbd --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RoutingMissingException.php @@ -0,0 +1,17 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class RoutingMissingException extends ServerErrorResponseException implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RuntimeException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RuntimeException.php new file mode 100644 index 0000000..2fc381a --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/RuntimeException.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class RuntimeException extends \RuntimeException implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ScriptLangNotSupportedException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ScriptLangNotSupportedException.php new file mode 100644 index 0000000..255c3a5 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ScriptLangNotSupportedException.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class ScriptLangNotSupportedException extends BadRequest400Exception implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Serializer/JsonErrorException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Serializer/JsonErrorException.php new file mode 100644 index 0000000..626d4ae --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Serializer/JsonErrorException.php @@ -0,0 +1,69 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class JsonErrorException extends \Exception implements ElasticsearchException +{ + /** + * @var mixed + */ + private $input; + + /** + * @var mixed + */ + private $result; + + private static $messages = array( + JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded', + JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', + JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', + JSON_ERROR_SYNTAX => 'Syntax error', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded', + JSON_ERROR_RECURSION => 'One or more recursive references in the value to be encoded', + JSON_ERROR_INF_OR_NAN => 'One or more NAN or INF values in the value to be encoded', + JSON_ERROR_UNSUPPORTED_TYPE => 'A value of a type that cannot be encoded was given', + + // JSON_ERROR_* constant values that are available on PHP >= 7.0 + 9 => 'Decoding of value would result in invalid PHP property name', //JSON_ERROR_INVALID_PROPERTY_NAME + 10 => 'Attempted to decode nonexistent UTF-16 code-point' //JSON_ERROR_UTF16 + ); + + public function __construct($code, $input, $result, $previous = null) + { + if (isset(self::$messages[$code]) !== true) { + throw new \InvalidArgumentException(sprintf('Encountered unknown JSON error code: [%d]', $code)); + } + + parent::__construct(self::$messages[$code], $code, $previous); + $this->input = $input; + $this->result = $result; + } + + /** + * @return mixed + */ + public function getInput() + { + return $this->input; + } + + /** + * @return mixed + */ + public function getResult() + { + return $this->result; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ServerErrorResponseException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ServerErrorResponseException.php new file mode 100644 index 0000000..9841254 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ServerErrorResponseException.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class ServerErrorResponseException extends TransportException implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/TransportException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/TransportException.php new file mode 100644 index 0000000..6dce5b4 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/TransportException.php @@ -0,0 +1,16 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class TransportException extends \Exception implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/UnexpectedValueException.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/UnexpectedValueException.php new file mode 100644 index 0000000..2a63e80 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/UnexpectedValueException.php @@ -0,0 +1,18 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class UnexpectedValueException extends \UnexpectedValueException implements ElasticsearchException +{ +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/AbstractConnectionPool.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/AbstractConnectionPool.php new file mode 100644 index 0000000..625eaa4 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/AbstractConnectionPool.php @@ -0,0 +1,86 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +abstract class AbstractConnectionPool implements ConnectionPoolInterface +{ + /** + * Array of connections + * + * @var ConnectionInterface[] + */ + protected $connections; + + /** + * Array of initial seed connections + * + * @var ConnectionInterface[] + */ + protected $seedConnections; + + /** + * Selector object, used to select a connection on each request + * + * @var SelectorInterface + */ + protected $selector; + + /** @var array */ + protected $connectionPoolParams; + + /** @var \Elasticsearch\Connections\ConnectionFactory */ + protected $connectionFactory; + + /** + * Constructor + * + * @param ConnectionInterface[] $connections The Connections to choose from + * @param SelectorInterface $selector A Selector instance to perform the selection logic for the available connections + * @param ConnectionFactoryInterface $factory ConnectionFactory instance + * @param array $connectionPoolParams + */ + public function __construct($connections, SelectorInterface $selector, ConnectionFactoryInterface $factory, $connectionPoolParams) + { + $paramList = array('connections', 'selector', 'connectionPoolParams'); + foreach ($paramList as $param) { + if (isset($$param) === false) { + throw new InvalidArgumentException('`' . $param . '` parameter must not be null'); + } + } + + if (isset($connectionPoolParams['randomizeHosts']) === true + && $connectionPoolParams['randomizeHosts'] === true) { + shuffle($connections); + } + + $this->connections = $connections; + $this->seedConnections = $connections; + $this->selector = $selector; + $this->connectionPoolParams = $connectionPoolParams; + $this->connectionFactory = $factory; + } + + /** + * @param bool $force + * + * @return Connection + */ + abstract public function nextConnection($force = false); + + abstract public function scheduleCheck(); +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/ConnectionPoolInterface.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/ConnectionPoolInterface.php new file mode 100644 index 0000000..d10fc35 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/ConnectionPoolInterface.php @@ -0,0 +1,29 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +interface ConnectionPoolInterface +{ + /** + * @param bool $force + * + * @return ConnectionInterface + */ + public function nextConnection($force = false); + + /** + * @return void + */ + public function scheduleCheck(); +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RandomSelector.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RandomSelector.php new file mode 100644 index 0000000..b544292 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RandomSelector.php @@ -0,0 +1,29 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class RandomSelector implements SelectorInterface +{ + /** + * Select a random connection from the provided array + * + * @param ConnectionInterface[] $connections an array of ConnectionInterface instances to choose from + * + * @return \Elasticsearch\Connections\ConnectionInterface + */ + public function select($connections) + { + return $connections[array_rand($connections)]; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RoundRobinSelector.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RoundRobinSelector.php new file mode 100644 index 0000000..e8b9784 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RoundRobinSelector.php @@ -0,0 +1,36 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class RoundRobinSelector implements SelectorInterface +{ + /** + * @var int + */ + private $current = 0; + + /** + * Select the next connection in the sequence + * + * @param ConnectionInterface[] $connections an array of ConnectionInterface instances to choose from + * + * @return \Elasticsearch\Connections\ConnectionInterface + */ + public function select($connections) + { + $this->current += 1; + + return $connections[$this->current % count($connections)]; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/SelectorInterface.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/SelectorInterface.php new file mode 100644 index 0000000..72dfd19 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/SelectorInterface.php @@ -0,0 +1,24 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +interface SelectorInterface +{ + /** + * Perform logic to select a single ConnectionInterface instance from the array provided + * + * @param ConnectionInterface[] $connections an array of ConnectionInterface instances to choose from + * + * @return \Elasticsearch\Connections\ConnectionInterface + */ + public function select($connections); +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/StickyRoundRobinSelector.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/StickyRoundRobinSelector.php new file mode 100644 index 0000000..f44a68c --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/StickyRoundRobinSelector.php @@ -0,0 +1,47 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class StickyRoundRobinSelector implements SelectorInterface +{ + /** + * @var int + */ + private $current = 0; + + /** + * @var int + */ + private $currentCounter = 0; + + /** + * Use current connection unless it is dead, otherwise round-robin + * + * @param ConnectionInterface[] $connections Array of connections to choose from + * + * @return ConnectionInterface + */ + public function select($connections) + { + /** @var ConnectionInterface[] $connections */ + if ($connections[$this->current]->isAlive()) { + return $connections[$this->current]; + } + + $this->currentCounter += 1; + $this->current = $this->currentCounter % count($connections); + + return $connections[$this->current]; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/SimpleConnectionPool.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/SimpleConnectionPool.php new file mode 100644 index 0000000..f77e8d7 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/SimpleConnectionPool.php @@ -0,0 +1,34 @@ +selector->select($this->connections); + } + + public function scheduleCheck() + { + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/SniffingConnectionPool.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/SniffingConnectionPool.php new file mode 100644 index 0000000..89b1f81 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/SniffingConnectionPool.php @@ -0,0 +1,155 @@ +setConnectionPoolParams($connectionPoolParams); + $this->nextSniff = time() + $this->sniffingInterval; + } + + /** + * @param bool $force + * + * @return Connection + * @throws \Elasticsearch\Common\Exceptions\NoNodesAvailableException + */ + public function nextConnection($force = false) + { + $this->sniff($force); + + $size = count($this->connections); + while ($size--) { + /** @var Connection $connection */ + $connection = $this->selector->select($this->connections); + if ($connection->isAlive() === true || $connection->ping() === true) { + return $connection; + } + } + + if ($force === true) { + throw new NoNodesAvailableException("No alive nodes found in your cluster"); + } + + return $this->nextConnection(true); + } + + public function scheduleCheck() + { + $this->nextSniff = -1; + } + + /** + * @param bool $force + */ + private function sniff($force = false) + { + if ($force === false && $this->nextSniff >= time()) { + return; + } + + $total = count($this->connections); + + while ($total--) { + /** @var Connection $connection */ + $connection = $this->selector->select($this->connections); + + if ($connection->isAlive() xor $force) { + continue; + } + + if ($this->sniffConnection($connection) === true) { + return; + } + } + + if ($force === true) { + return; + } + + foreach ($this->seedConnections as $connection) { + if ($this->sniffConnection($connection) === true) { + return; + } + } + } + + /** + * @param Connection $connection + * @return bool + */ + private function sniffConnection(Connection $connection) + { + try { + $response = $connection->sniff(); + } catch (OperationTimeoutException $exception) { + return false; + } + + $nodes = $this->parseClusterState($connection->getTransportSchema(), $response); + + if (count($nodes) === 0) { + return false; + } + + $this->connections = array(); + + foreach ($nodes as $node) { + $nodeDetails = array( + 'host' => $node['host'], + 'port' => $node['port'] + ); + $this->connections[] = $this->connectionFactory->create($nodeDetails); + } + + $this->nextSniff = time() + $this->sniffingInterval; + + return true; + } + + private function parseClusterState($transportSchema, $nodeInfo) + { + $pattern = '/\/([^:]*):([0-9]+)\]/'; + $schemaAddress = $transportSchema . '_address'; + $hosts = array(); + + foreach ($nodeInfo['nodes'] as $node) { + if (isset($node[$schemaAddress]) === true) { + if (preg_match($pattern, $node[$schemaAddress], $match) === 1) { + $hosts[] = array( + 'host' => $match[1], + 'port' => (int) $match[2], + ); + } + } + } + + return $hosts; + } + + private function setConnectionPoolParams($connectionPoolParams) + { + if (isset($connectionPoolParams['sniffingInterval']) === true) { + $this->sniffingInterval = $connectionPoolParams['sniffingInterval']; + } + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticConnectionPool.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticConnectionPool.php new file mode 100644 index 0000000..102dda3 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticConnectionPool.php @@ -0,0 +1,93 @@ +scheduleCheck(); + } + + /** + * @param bool $force + * + * @return Connection + * @throws \Elasticsearch\Common\Exceptions\NoNodesAvailableException + */ + public function nextConnection($force = false) + { + $skipped = array(); + + $total = count($this->connections); + while ($total--) { + /** @var Connection $connection */ + $connection = $this->selector->select($this->connections); + if ($connection->isAlive() === true) { + return $connection; + } + + if ($this->readyToRevive($connection) === true) { + if ($connection->ping() === true) { + return $connection; + } + } else { + $skipped[] = $connection; + } + } + + // All "alive" nodes failed, force pings on "dead" nodes + foreach ($skipped as $connection) { + if ($connection->ping() === true) { + return $connection; + } + } + + throw new NoNodesAvailableException("No alive nodes found in your cluster"); + } + + public function scheduleCheck() + { + foreach ($this->connections as $connection) { + $connection->markDead(); + } + } + + /** + * @param Connection $connection + * + * @return bool + */ + private function readyToRevive(Connection $connection) + { + $timeout = min( + $this->pingTimeout * pow(2, $connection->getPingFailures()), + $this->maxPingTimeout + ); + + if ($connection->getLastPing() + $timeout < time()) { + return true; + } else { + return false; + } + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticNoPingConnectionPool.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticNoPingConnectionPool.php new file mode 100644 index 0000000..b7b056e --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticNoPingConnectionPool.php @@ -0,0 +1,76 @@ +connections); + while ($total--) { + /** @var Connection $connection */ + $connection = $this->selector->select($this->connections); + if ($connection->isAlive() === true) { + return $connection; + } + + if ($this->readyToRevive($connection) === true) { + return $connection; + } + } + + throw new NoNodesAvailableException("No alive nodes found in your cluster"); + } + + public function scheduleCheck() + { + } + + /** + * @param \Elasticsearch\Connections\Connection $connection + * + * @return bool + */ + private function readyToRevive(Connection $connection) + { + $timeout = min( + $this->pingTimeout * pow(2, $connection->getPingFailures()), + $this->maxPingTimeout + ); + + if ($connection->getLastPing() + $timeout < time()) { + return true; + } else { + return false; + } + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/Connection.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/Connection.php new file mode 100644 index 0000000..f8aa71a --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/Connection.php @@ -0,0 +1,718 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Connection implements ConnectionInterface +{ + /** @var callable */ + protected $handler; + + /** @var SerializerInterface */ + protected $serializer; + + /** + * @var string + */ + protected $transportSchema = 'http'; // TODO depreciate this default + + /** + * @var string + */ + protected $host; + + /** + * @var string || null + */ + protected $path; + + /** + * @var LoggerInterface + */ + protected $log; + + /** + * @var LoggerInterface + */ + protected $trace; + + /** + * @var array + */ + protected $connectionParams; + + /** @var array */ + protected $headers = []; + + /** @var bool */ + protected $isAlive = false; + + /** @var float */ + private $pingTimeout = 1; //TODO expose this + + /** @var int */ + private $lastPing = 0; + + /** @var int */ + private $failedPings = 0; + + private $lastRequest = array(); + + /** + * Constructor + * + * @param $handler + * @param array $hostDetails + * @param array $connectionParams Array of connection-specific parameters + * @param \Elasticsearch\Serializers\SerializerInterface $serializer + * @param \Psr\Log\LoggerInterface $log Logger object + * @param \Psr\Log\LoggerInterface $trace + */ + public function __construct($handler, $hostDetails, $connectionParams, + SerializerInterface $serializer, LoggerInterface $log, LoggerInterface $trace) + { + if (isset($hostDetails['port']) !== true) { + $hostDetails['port'] = 9200; + } + + if (isset($hostDetails['scheme'])) { + $this->transportSchema = $hostDetails['scheme']; + } + + if (isset($hostDetails['user']) && isset($hostDetails['pass'])) { + $connectionParams['client']['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_BASIC; + $connectionParams['client']['curl'][CURLOPT_USERPWD] = $hostDetails['user'].':'.$hostDetails['pass']; + } + + if (isset($connectionParams['client']['headers']) === true) { + $this->headers = $connectionParams['client']['headers']; + unset($connectionParams['client']['headers']); + } + + $host = $hostDetails['host'].':'.$hostDetails['port']; + $path = null; + if (isset($hostDetails['path']) === true) { + $path = $hostDetails['path']; + } + $this->host = $host; + $this->path = $path; + $this->log = $log; + $this->trace = $trace; + $this->connectionParams = $connectionParams; + $this->serializer = $serializer; + + $this->handler = $this->wrapHandler($handler, $log, $trace); + } + + /** + * @param $method + * @param $uri + * @param null $params + * @param null $body + * @param array $options + * @param \Elasticsearch\Transport $transport + * @return mixed + */ + public function performRequest($method, $uri, $params = null, $body = null, $options = [], Transport $transport = null) + { + if (isset($body) === true) { + $body = $this->serializer->serialize($body); + } + + $request = [ + 'http_method' => $method, + 'scheme' => $this->transportSchema, + 'uri' => $this->getURI($uri, $params), + 'body' => $body, + 'headers' => array_merge([ + 'host' => [$this->host] + ], $this->headers) + ]; + + $request = array_merge_recursive($request, $this->connectionParams, $options); + + // RingPHP does not like if client is empty + if (empty($request['client'])) { + unset($request['client']); + } + + $handler = $this->handler; + $future = $handler($request, $this, $transport, $options); + + return $future; + } + + /** @return string */ + public function getTransportSchema() + { + return $this->transportSchema; + } + + /** @return array */ + public function getLastRequestInfo() + { + return $this->lastRequest; + } + + private function wrapHandler(callable $handler, LoggerInterface $logger, LoggerInterface $tracer) + { + return function (array $request, Connection $connection, Transport $transport = null, $options) use ($handler, $logger, $tracer) { + + $this->lastRequest = []; + $this->lastRequest['request'] = $request; + + // Send the request using the wrapped handler. + $response = Core::proxy($handler($request), function ($response) use ($connection, $transport, $logger, $tracer, $request, $options) { + + $this->lastRequest['response'] = $response; + + if (isset($response['error']) === true) { + if ($response['error'] instanceof ConnectException || $response['error'] instanceof RingException) { + $this->log->warning("Curl exception encountered."); + + $exception = $this->getCurlRetryException($request, $response); + + $this->logRequestFail( + $request['http_method'], + $response['effective_url'], + $request['body'], + $request['headers'], + $response['status'], + $response['body'], + $response['transfer_stats']['total_time'], + $exception + ); + + $node = $connection->getHost(); + $this->log->warning("Marking node $node dead."); + $connection->markDead(); + + // If the transport has not been set, we are inside a Ping or Sniff, + // so we don't want to retrigger retries anyway. + // + // TODO this could be handled better, but we are limited because connectionpools do not + // have access to Transport. Architecturally, all of this needs to be refactored + if (isset($transport) === true) { + $transport->connectionPool->scheduleCheck(); + + $neverRetry = isset($request['client']['never_retry']) ? $request['client']['never_retry'] : false; + $shouldRetry = $transport->shouldRetry($request); + $shouldRetryText = ($shouldRetry) ? 'true' : 'false'; + + $this->log->warning("Retries left? $shouldRetryText"); + if ($shouldRetry && !$neverRetry) { + return $transport->performRequest( + $request['http_method'], + $request['uri'], + [], + $request['body'], + $options + ); + } + } + + $this->log->warning("Out of retries, throwing exception from $node"); + // Only throw if we run out of retries + throw $exception; + } else { + // Something went seriously wrong, bail + $exception = new TransportException($response['error']->getMessage()); + $this->logRequestFail( + $request['http_method'], + $response['effective_url'], + $request['body'], + $request['headers'], + $response['status'], + $response['body'], + $response['transfer_stats']['total_time'], + $exception + ); + throw $exception; + } + } else { + $connection->markAlive(); + + if (isset($response['body']) === true) { + $response['body'] = stream_get_contents($response['body']); + $this->lastRequest['response']['body'] = $response['body']; + } + + if ($response['status'] >= 400 && $response['status'] < 500) { + $ignore = isset($request['client']['ignore']) ? $request['client']['ignore'] : []; + $this->process4xxError($request, $response, $ignore); + } elseif ($response['status'] >= 500) { + $ignore = isset($request['client']['ignore']) ? $request['client']['ignore'] : []; + $this->process5xxError($request, $response, $ignore); + } + + // No error, deserialize + $response['body'] = $this->serializer->deserialize($response['body'], $response['transfer_stats']); + } + $this->logRequestSuccess( + $request['http_method'], + $response['effective_url'], + $request['body'], + $request['headers'], + $response['status'], + $response['body'], + $response['transfer_stats']['total_time'] + ); + + return isset($request['client']['verbose']) && $request['client']['verbose'] === true ? $response : $response['body']; + + }); + + return $response; + }; + } + + /** + * @param string $uri + * @param array $params + * + * @return string + */ + private function getURI($uri, $params) + { + if (isset($params) === true && !empty($params)) { + array_walk($params, function (&$value, &$key) { + if ($value === true) { + $value = 'true'; + } else if ($value === false) { + $value = 'false'; + } + }); + + $uri .= '?' . http_build_query($params); + } + + if ($this->path !== null) { + $uri = $this->path . $uri; + } + + return $uri; + } + + /** + * Log a successful request + * + * @param string $method + * @param string $fullURI + * @param string $body + * @param array $headers + * @param string $statusCode + * @param string $response + * @param string $duration + * + * @return void + */ + public function logRequestSuccess($method, $fullURI, $body, $headers, $statusCode, $response, $duration) + { + $this->log->debug('Request Body', array($body)); + $this->log->info( + 'Request Success:', + array( + 'method' => $method, + 'uri' => $fullURI, + 'headers' => $headers, + 'HTTP code' => $statusCode, + 'duration' => $duration, + ) + ); + $this->log->debug('Response', array($response)); + + // Build the curl command for Trace. + $curlCommand = $this->buildCurlCommand($method, $fullURI, $body); + $this->trace->info($curlCommand); + $this->trace->debug( + 'Response:', + array( + 'response' => $response, + 'method' => $method, + 'uri' => $fullURI, + 'HTTP code' => $statusCode, + 'duration' => $duration, + ) + ); + } + + /** + * Log a a failed request + * + * @param string $method + * @param string $fullURI + * @param string $body + * @param array $headers + * @param null|string $statusCode + * @param null|string $response + * @param string $duration + * @param \Exception|null $exception + * + * @return void + */ + public function logRequestFail($method, $fullURI, $body, $headers, $statusCode, $response, $duration, \Exception $exception) + { + $this->log->debug('Request Body', array($body)); + $this->log->warning( + 'Request Failure:', + array( + 'method' => $method, + 'uri' => $fullURI, + 'headers' => $headers, + 'HTTP code' => $statusCode, + 'duration' => $duration, + 'error' => $exception->getMessage(), + ) + ); + $this->log->warning('Response', array($response)); + + // Build the curl command for Trace. + $curlCommand = $this->buildCurlCommand($method, $fullURI, $body); + $this->trace->info($curlCommand); + $this->trace->debug( + 'Response:', + array( + 'response' => $response, + 'method' => $method, + 'uri' => $fullURI, + 'HTTP code' => $statusCode, + 'duration' => $duration, + ) + ); + } + + /** + * @return bool + */ + public function ping() + { + $options = [ + 'client' => [ + 'timeout' => $this->pingTimeout, + 'never_retry' => true, + 'verbose' => true + ] + ]; + try { + $response = $this->performRequest('HEAD', '/', null, null, $options); + $response = $response->wait(); + } catch (TransportException $exception) { + $this->markDead(); + + return false; + } + + if ($response['status'] === 200) { + $this->markAlive(); + + return true; + } else { + $this->markDead(); + + return false; + } + } + + /** + * @return array + */ + public function sniff() + { + $options = [ + 'client' => [ + 'timeout' => $this->pingTimeout, + 'never_retry' => true + ] + ]; + + return $this->performRequest('GET', '/_nodes/_all/clear', null, null, $options); + } + + /** + * @return bool + */ + public function isAlive() + { + return $this->isAlive; + } + + public function markAlive() + { + $this->failedPings = 0; + $this->isAlive = true; + $this->lastPing = time(); + } + + public function markDead() + { + $this->isAlive = false; + $this->failedPings += 1; + $this->lastPing = time(); + } + + /** + * @return int + */ + public function getLastPing() + { + return $this->lastPing; + } + + /** + * @return int + */ + public function getPingFailures() + { + return $this->failedPings; + } + + /** + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * @return null|string + */ + public function getUserPass() + { + if (isset($this->connectionParams['client']['curl'][CURLOPT_USERPWD]) === true) { + return $this->connectionParams['client']['curl'][CURLOPT_USERPWD]; + } + return null; + } + + /** + * @return null|string + */ + public function getPath() + { + return $this->path; + } + + /** + * @param $request + * @param $response + * @return \Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost|\Elasticsearch\Common\Exceptions\Curl\CouldNotResolveHostException|\Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException|\Elasticsearch\Common\Exceptions\MaxRetriesException + */ + protected function getCurlRetryException($request, $response) + { + $exception = null; + $message = $response['error']->getMessage(); + $exception = new MaxRetriesException($message); + switch ($response['curl']['errno']) { + case 6: + $exception = new CouldNotResolveHostException($message, null, $exception); + break; + case 7: + $exception = new CouldNotConnectToHost($message, null, $exception); + break; + case 28: + $exception = new OperationTimeoutException($message, null, $exception); + break; + } + + return $exception; + } + + /** + * Construct a string cURL command + * + * @param string $method HTTP method + * @param string $uri Full URI of request + * @param string $body Request body + * + * @return string + */ + private function buildCurlCommand($method, $uri, $body) + { + if (strpos($uri, '?') === false) { + $uri .= '?pretty=true'; + } else { + str_replace('?', '?pretty=true', $uri); + } + + $curlCommand = 'curl -X' . strtoupper($method); + $curlCommand .= " '" . $uri . "'"; + + if (isset($body) === true && $body !== '') { + $curlCommand .= " -d '" . $body . "'"; + } + + return $curlCommand; + } + + /** + * @param $request + * @param $response + * @param $ignore + * @throws \Elasticsearch\Common\Exceptions\AlreadyExpiredException|\Elasticsearch\Common\Exceptions\BadRequest400Exception|\Elasticsearch\Common\Exceptions\Conflict409Exception|\Elasticsearch\Common\Exceptions\Forbidden403Exception|\Elasticsearch\Common\Exceptions\Missing404Exception|\Elasticsearch\Common\Exceptions\ScriptLangNotSupportedException|null + */ + private function process4xxError($request, $response, $ignore) + { + $statusCode = $response['status']; + $responseBody = $response['body']; + + /** @var \Exception $exception */ + $exception = $this->tryDeserialize400Error($response); + + if (array_search($response['status'], $ignore) !== false) { + return; + } + + if ($statusCode === 400 && strpos($responseBody, "AlreadyExpiredException") !== false) { + $exception = new AlreadyExpiredException($responseBody, $statusCode); + } elseif ($statusCode === 403) { + $exception = new Forbidden403Exception($responseBody, $statusCode); + } elseif ($statusCode === 404) { + $exception = new Missing404Exception($responseBody, $statusCode); + } elseif ($statusCode === 409) { + $exception = new Conflict409Exception($responseBody, $statusCode); + } elseif ($statusCode === 400 && strpos($responseBody, 'script_lang not supported') !== false) { + $exception = new ScriptLangNotSupportedException($responseBody. $statusCode); + } elseif ($statusCode === 408) { + $exception = new RequestTimeout408Exception($responseBody, $statusCode); + } else { + $exception = new BadRequest400Exception($responseBody, $statusCode); + } + + $this->logRequestFail( + $request['http_method'], + $response['effective_url'], + $request['body'], + $request['headers'], + $response['status'], + $response['body'], + $response['transfer_stats']['total_time'], + $exception + ); + + throw $exception; + } + + /** + * @param $request + * @param $response + * @param $ignore + * @throws \Elasticsearch\Common\Exceptions\NoDocumentsToGetException|\Elasticsearch\Common\Exceptions\NoShardAvailableException|\Elasticsearch\Common\Exceptions\RoutingMissingException|\Elasticsearch\Common\Exceptions\ServerErrorResponseException + */ + private function process5xxError($request, $response, $ignore) + { + $statusCode = $response['status']; + $responseBody = $response['body']; + + /** @var \Exception $exception */ + $exception = $this->tryDeserialize500Error($response); + + $exceptionText = "[$statusCode Server Exception] ".$exception->getMessage(); + $this->log->error($exceptionText); + $this->log->error($exception->getTraceAsString()); + + if (array_search($statusCode, $ignore) !== false) { + return; + } + + if ($statusCode === 500 && strpos($responseBody, "RoutingMissingException") !== false) { + $exception = new RoutingMissingException($exception->getMessage(), $statusCode, $exception); + } elseif ($statusCode === 500 && preg_match('/ActionRequestValidationException.+ no documents to get/', $responseBody) === 1) { + $exception = new NoDocumentsToGetException($exception->getMessage(), $statusCode, $exception); + } elseif ($statusCode === 500 && strpos($responseBody, 'NoShardAvailableActionException') !== false) { + $exception = new NoShardAvailableException($exception->getMessage(), $statusCode, $exception); + } else { + $exception = new ServerErrorResponseException($responseBody, $statusCode); + } + + $this->logRequestFail( + $request['http_method'], + $response['effective_url'], + $request['body'], + $request['headers'], + $response['status'], + $response['body'], + $response['transfer_stats']['total_time'], + $exception + ); + + throw $exception; + } + + private function tryDeserialize400Error($response) + { + return $this->tryDeserializeError($response, 'Elasticsearch\Common\Exceptions\BadRequest400Exception'); + } + + private function tryDeserialize500Error($response) + { + return $this->tryDeserializeError($response, 'Elasticsearch\Common\Exceptions\ServerErrorResponseException'); + } + + private function tryDeserializeError($response, $errorClass) + { + $error = $this->serializer->deserialize($response['body'], $response['transfer_stats']); + if (is_array($error) === true) { + // 2.0 structured exceptions + if (isset($error['error']['reason']) === true) { + + // Try to use root cause first (only grabs the first root cause) + $root = $error['error']['root_cause']; + if (isset($root) && isset($root[0])) { + $cause = $root[0]['reason']; + $type = $root[0]['type']; + } else { + $cause = $error['error']['reason']; + $type = $error['error']['type']; + } + + $original = new $errorClass($response['body'], $response['status']); + + return new $errorClass("$type: $cause", $response['status'], $original); + } elseif (isset($error['error']) === true) { + // <2.0 semi-structured exceptions + $original = new $errorClass($response['body'], $response['status']); + + return new $errorClass($error['error'], $response['status'], $original); + } + + // <2.0 "i just blew up" nonstructured exception + // $error is an array but we don't know the format, reuse the response body instead + return new $errorClass($response['body'], $response['status']); + } + + // <2.0 "i just blew up" nonstructured exception + return new $errorClass($response['body']); + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactory.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactory.php new file mode 100644 index 0000000..88d6dc9 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactory.php @@ -0,0 +1,67 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class ConnectionFactory implements ConnectionFactoryInterface +{ + /** @var array */ + private $connectionParams; + + /** @var SerializerInterface */ + private $serializer; + + /** @var LoggerInterface */ + private $logger; + + /** @var LoggerInterface */ + private $tracer; + + /** @var callable */ + private $handler; + + /** + * Constructor + * + * @param callable $handler + * @param array $connectionParams + * @param SerializerInterface $serializer + * @param LoggerInterface $logger + * @param LoggerInterface $tracer + */ + public function __construct(callable $handler, array $connectionParams, SerializerInterface $serializer, LoggerInterface $logger, LoggerInterface $tracer) + { + $this->handler = $handler; + $this->connectionParams = $connectionParams; + $this->logger = $logger; + $this->tracer = $tracer; + $this->serializer = $serializer; + } + /** + * @param $hostDetails + * + * @return ConnectionInterface + */ + public function create($hostDetails) + { + return new Connection( + $this->handler, + $hostDetails, + $this->connectionParams, + $this->serializer, + $this->logger, + $this->tracer + ); + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactoryInterface.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactoryInterface.php new file mode 100644 index 0000000..242a321 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactoryInterface.php @@ -0,0 +1,35 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +interface ConnectionFactoryInterface +{ + /** + * @param $handler + * @param array $connectionParams + * @param SerializerInterface $serializer + * @param LoggerInterface $logger + * @param LoggerInterface $tracer + */ + public function __construct(callable $handler, array $connectionParams, + SerializerInterface $serializer, LoggerInterface $logger, LoggerInterface $tracer); + + /** + * @param $hostDetails + * + * @return ConnectionInterface + */ + public function create($hostDetails); +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionInterface.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionInterface.php new file mode 100644 index 0000000..44495db --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionInterface.php @@ -0,0 +1,99 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +interface ConnectionInterface +{ + /** + * Constructor + * + * @param $handler + * @param array $hostDetails + * @param array $connectionParams connection-specific parameters + * @param \Elasticsearch\Serializers\SerializerInterface $serializer + * @param \Psr\Log\LoggerInterface $log Logger object + * @param \Psr\Log\LoggerInterface $trace Logger object + */ + public function __construct($handler, $hostDetails, $connectionParams, + SerializerInterface $serializer, LoggerInterface $log, LoggerInterface $trace); + + /** + * Get the transport schema for this connection + * + * @return string + */ + public function getTransportSchema(); + + /** + * Get the hostname for this connection + * + * @return string + */ + public function getHost(); + + /** + * Get the username:password string for this connection, null if not set + * + * @return null|string + */ + public function getUserPass(); + + /** + * Get the URL path suffix, null if not set + * + * @return null|string; + */ + public function getPath(); + + /** + * Check to see if this instance is marked as 'alive' + * + * @return bool + */ + public function isAlive(); + + /** + * Mark this instance as 'alive' + * + * @return void + */ + public function markAlive(); + + /** + * Mark this instance as 'dead' + * + * @return void + */ + public function markDead(); + + /** + * Return an associative array of information about the last request + * + * @return array + */ + public function getLastRequestInfo(); + + /** + * @param $method + * @param $uri + * @param null $params + * @param null $body + * @param array $options + * @param \Elasticsearch\Transport $transport + * @return mixed + */ + public function performRequest($method, $uri, $params = null, $body = null, $options = [], Transport $transport); +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/AbstractEndpoint.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/AbstractEndpoint.php new file mode 100644 index 0000000..3330705 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/AbstractEndpoint.php @@ -0,0 +1,303 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +abstract class AbstractEndpoint +{ + /** @var array */ + protected $params = array(); + + /** @var string */ + protected $index = null; + + /** @var string */ + protected $type = null; + + /** @var string|int */ + protected $id = null; + + /** @var string */ + protected $method = null; + + /** @var array */ + protected $body = null; + + /** @var array */ + private $options = []; + + /** @var SerializerInterface */ + protected $serializer; + + /** + * @return string[] + */ + abstract public function getParamWhitelist(); + + /** + * @return string + */ + abstract public function getURI(); + + /** + * @return string + */ + abstract public function getMethod(); + + + /** + * Set the parameters for this endpoint + * + * @param string[] $params Array of parameters + * @return $this + */ + public function setParams($params) + { + if (is_object($params) === true) { + $params = (array) $params; + } + + $this->checkUserParams($params); + $params = $this->convertCustom($params); + $this->extractOptions($params); + $this->params = $this->convertArraysToStrings($params); + + return $this; + } + + /** + * @return array + */ + public function getParams() + { + return $this->params; + } + + /** + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * @return string|null + */ + public function getIndex() + { + return $this->index; + } + + /** + * @param string $index + * + * @return $this + */ + public function setIndex($index) + { + if ($index === null) { + return $this; + } + + if (is_array($index) === true) { + $index = array_map('trim', $index); + $index = implode(",", $index); + } + + $this->index = urlencode($index); + + return $this; + } + + /** + * @return string|null + */ + public function getType() + { + return $this->type; + } + + /** + * @param string $type + * + * @return $this + */ + public function setType($type) + { + if ($type === null) { + return $this; + } + + if (is_array($type) === true) { + $type = array_map('trim', $type); + $type = implode(",", $type); + } + + $this->type = urlencode($type); + + return $this; + } + + /** + * @param int|string $docID + * + * @return $this + */ + public function setID($docID) + { + if ($docID === null) { + return $this; + } + + $this->id = urlencode($docID); + + return $this; + } + + /** + * @return array + */ + public function getBody() + { + return $this->body; + } + + /** + * @param string $endpoint + * + * @return string + */ + protected function getOptionalURI($endpoint) + { + $uri = array(); + $uri[] = $this->getOptionalIndex(); + $uri[] = $this->getOptionalType(); + $uri[] = $endpoint; + $uri = array_filter($uri); + + return '/' . implode('/', $uri); + } + + /** + * @return string + */ + private function getOptionalIndex() + { + if (isset($this->index) === true) { + return $this->index; + } else { + return '_all'; + } + } + + /** + * @return string + */ + private function getOptionalType() + { + if (isset($this->type) === true) { + return $this->type; + } else { + return ''; + } + } + + /** + * @param array $params + * + * @throws \Elasticsearch\Common\Exceptions\UnexpectedValueException + */ + private function checkUserParams($params) + { + if (isset($params) !== true) { + return; //no params, just return. + } + + $whitelist = array_merge($this->getParamWhitelist(), array('client', 'custom', 'filter_path', 'human')); + + $invalid = array_diff(array_keys($params), $whitelist); + if (count($invalid) > 0) { + sort($invalid); + sort($whitelist); + throw new UnexpectedValueException(sprintf( + (count($invalid) > 1 ? '"%s" are not valid parameters.' : '"%s" is not a valid parameter.').' Allowed parameters are "%s"', + implode('", "', $invalid), + implode('", "', $whitelist) + )); + } + } + + /** + * @param $params Note: this is passed by-reference! + */ + private function extractOptions(&$params) + { + // Extract out client options, then start transforming + if (isset($params['client']) === true) { + $this->options['client'] = $params['client']; + unset($params['client']); + } + + $ignore = isset($this->options['client']['ignore']) ? $this->options['client']['ignore'] : null; + if (isset($ignore) === true) { + if (is_string($ignore)) { + $this->options['client']['ignore'] = explode(",", $ignore); + } elseif (is_array($ignore)) { + $this->options['client']['ignore'] = $ignore; + } else { + $this->options['client']['ignore'] = [$ignore]; + } + } + } + + private function convertCustom($params) + { + if (isset($params['custom']) === true) { + foreach ($params['custom'] as $k => $v) { + $params[$k] = $v; + } + unset($params['custom']); + } + + return $params; + } + + private function convertArraysToStrings($params) + { + foreach ($params as $key => &$value) { + if (!($key === 'client' || $key == 'custom') && is_array($value) === true) { + if ($this->isNestedArray($value) !== true) { + $value = implode(",", $value); + } + } + } + + return $params; + } + + private function isNestedArray($a) + { + foreach ($a as $v) { + if (is_array($v)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Bulk.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Bulk.php new file mode 100644 index 0000000..3bc15bc --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Bulk.php @@ -0,0 +1,83 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Bulk extends AbstractEndpoint implements BulkEndpointInterface +{ + /** + * @param SerializerInterface $serializer + */ + public function __construct(SerializerInterface $serializer) + { + $this->serializer = $serializer; + } + + /** + * @param string|array|\Traversable $body + * + * @return $this + */ + public function setBody($body) + { + if (empty($body)) { + return $this; + } + + if (is_array($body) === true || $body instanceof \Traversable) { + foreach ($body as $item) { + $this->body .= $this->serializer->serialize($item) . "\n"; + } + } else { + $this->body = $body; + } + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + return $this->getOptionalURI('_bulk'); + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'consistency', + 'refresh', + 'replication', + 'type', + 'fields', + 'pipeline', + '_source', + '_source_include', + '_source_exclude', + 'pipeline' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/BulkEndpointInterface.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/BulkEndpointInterface.php new file mode 100644 index 0000000..c7da254 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/BulkEndpointInterface.php @@ -0,0 +1,25 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +interface BulkEndpointInterface +{ + /** + * Constructor + * + * @param SerializerInterface $serializer A serializer + */ + public function __construct(SerializerInterface $serializer); +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Aliases.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Aliases.php new file mode 100644 index 0000000..4fbebcc --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Aliases.php @@ -0,0 +1,76 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Aliases extends AbstractEndpoint +{ + // A comma-separated list of alias names to return + private $name; + + /** + * @param $name + * + * @return $this + */ + public function setName($name) + { + if (isset($name) !== true) { + return $this; + } + + $this->name = $name; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $name = $this->name; + $uri = "/_cat/aliases"; + + if (isset($name) === true) { + $uri = "/_cat/aliases/$name"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout', + 'h', + 'help', + 'v', + 'format', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Allocation.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Allocation.php new file mode 100644 index 0000000..3084385 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Allocation.php @@ -0,0 +1,76 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Allocation extends AbstractEndpoint +{ + // A comma-separated list of node IDs or names to limit the returned information + private $node_id; + + /** + * @param $node_id + * + * @return $this + */ + public function setNodeId($node_id) + { + if (isset($node_id) !== true) { + return $this; + } + + $this->node_id = $node_id; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $node_id = $this->node_id; + $uri = "/_cat/allocation"; + + if (isset($node_id) === true) { + $uri = "/_cat/allocation/$node_id"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'bytes', + 'local', + 'master_timeout', + 'h', + 'help', + 'v', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Count.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Count.php new file mode 100644 index 0000000..de64d3f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Count.php @@ -0,0 +1,56 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Count extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_cat/count"; + + if (isset($index) === true) { + $uri = "/_cat/count/$index"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout', + 'h', + 'help', + 'v', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Fielddata.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Fielddata.php new file mode 100644 index 0000000..61a2303 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Fielddata.php @@ -0,0 +1,74 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Fielddata extends AbstractEndpoint +{ + private $fields; + + /** + * @param $fields + * + * @return $this + */ + public function setFields($fields) + { + if (isset($fields) !== true) { + return $this; + } + + $this->fields = $fields; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $fields = $this->fields; + $uri = "/_cat/fielddata"; + + if (isset($fields) === true) { + $uri = "/_cat/fielddata/$fields"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout', + 'h', + 'help', + 'v', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Health.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Health.php new file mode 100644 index 0000000..1dbb519 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Health.php @@ -0,0 +1,52 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Health extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $uri = "/_cat/health"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout', + 'h', + 'help', + 'ts', + 'v', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Help.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Help.php new file mode 100644 index 0000000..945db52 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Help.php @@ -0,0 +1,47 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Help extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $uri = "/_cat"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'help', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Indices.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Indices.php new file mode 100644 index 0000000..595f5aa --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Indices.php @@ -0,0 +1,60 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ + +class Indices extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_cat/indices"; + + if (isset($index) === true) { + $uri = "/_cat/indices/$index"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'bytes', + 'local', + 'master_timeout', + 'h', + 'help', + 'pri', + 'v', + 'health', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Master.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Master.php new file mode 100644 index 0000000..4fdec3d --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Master.php @@ -0,0 +1,51 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Master extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $uri = "/_cat/master"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout', + 'h', + 'help', + 'v', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/NodeAttrs.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/NodeAttrs.php new file mode 100644 index 0000000..e8e773c --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/NodeAttrs.php @@ -0,0 +1,51 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class NodeAttrs extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $uri = "/_cat/nodeattrs"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout', + 'h', + 'help', + 'v', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Nodes.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Nodes.php new file mode 100644 index 0000000..4c2b565 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Nodes.php @@ -0,0 +1,52 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Nodes extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $uri = "/_cat/nodes"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout', + 'h', + 'help', + 'v', + 's', + 'full_id', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/PendingTasks.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/PendingTasks.php new file mode 100644 index 0000000..0cc9519 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/PendingTasks.php @@ -0,0 +1,51 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class PendingTasks extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $uri = "/_cat/pending_tasks"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout', + 'h', + 'help', + 'v', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Plugins.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Plugins.php new file mode 100644 index 0000000..39b9b7e --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Plugins.php @@ -0,0 +1,51 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Plugins extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $uri = "/_cat/plugins"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout', + 'h', + 'help', + 'v', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Recovery.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Recovery.php new file mode 100644 index 0000000..1acf6c5 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Recovery.php @@ -0,0 +1,57 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Recovery extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_cat/recovery"; + + if (isset($index) === true) { + $uri = "/_cat/recovery/$index"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'bytes', + 'local', + 'master_timeout', + 'h', + 'help', + 'v', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Repositories.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Repositories.php new file mode 100644 index 0000000..43cc121 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Repositories.php @@ -0,0 +1,51 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Repositories extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $uri = "/_cat/repositories"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout', + 'h', + 'help', + 'v', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Segments.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Segments.php new file mode 100644 index 0000000..ef969fb --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Segments.php @@ -0,0 +1,63 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ + +class Segments extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_cat/segments"; + + if (isset($index) === true) { + $uri = "/_cat/segments/$index"; + } + + return $uri; + } + + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'h', + 'help', + 'v', + 's', + 'format', + ); + } + + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Shards.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Shards.php new file mode 100644 index 0000000..78d8f33 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Shards.php @@ -0,0 +1,57 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Shards extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_cat/shards"; + + if (isset($index) === true) { + $uri = "/_cat/shards/$index"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'bytes', + 'local', + 'master_timeout', + 'h', + 'help', + 'v', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Snapshots.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Snapshots.php new file mode 100644 index 0000000..75de772 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Snapshots.php @@ -0,0 +1,73 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Snapshots extends AbstractEndpoint +{ + private $repository; + + /** + * @param $fields + * + * @return $this + */ + public function setRepository($repository) + { + if (isset($repository) !== true) { + return $this; + } + + $this->repository = $repository; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $repository = $this->repository; + if (isset($this->repository) === true) { + return "/_cat/snapshots/$repository/"; + } + + return "/_cat/snapshots/"; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout', + 'h', + 'help', + 'v', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Tasks.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Tasks.php new file mode 100644 index 0000000..92cc033 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/Tasks.php @@ -0,0 +1,52 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Tasks extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + return "/_cat/tasks"; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'format', + 'node_id', + 'actions', + 'detailed', + 'parent_node', + 'parent_task', + 'h', + 'help', + 'v', + 's' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/ThreadPool.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/ThreadPool.php new file mode 100644 index 0000000..e0f0e8d --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cat/ThreadPool.php @@ -0,0 +1,55 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ + +class ThreadPool extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $uri = "/_cat/thread_pool"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout', + 'h', + 'help', + 'v', + 'full_id', + 'size', + 'thread_pool_patterns', + 's', + 'format', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/ClearScroll.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/ClearScroll.php new file mode 100644 index 0000000..68466a7 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/ClearScroll.php @@ -0,0 +1,93 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class ClearScroll extends AbstractEndpoint +{ + // A comma-separated list of scroll IDs to clear + private $scrollId; + + /** + * @param $scroll_id + * + * @return $this + */ + public function setScrollId($scrollId) + { + if (isset($scrollId) !== true) { + return $this; + } + + $this->scrollId = $scrollId; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + return "/_search/scroll/"; + } + + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return array + */ + public function getBody() + { + if (isset($this->body)) { + return $this->body; + } + if (is_array($this->scrollId)) { + return ['scroll_id' => $this->scrollId]; + } + return ['scroll_id' => [$this->scrollId]]; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'DELETE'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/AllocationExplain.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/AllocationExplain.php new file mode 100644 index 0000000..25014bf --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/AllocationExplain.php @@ -0,0 +1,62 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class AllocationExplain extends AbstractEndpoint +{ + + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + return "/_cluster/allocation/explain"; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'include_yes_decisions', + 'include_disk_info', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Health.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Health.php new file mode 100644 index 0000000..10e8a7c --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Health.php @@ -0,0 +1,59 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Health extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_cluster/health"; + + if (isset($index) === true) { + $uri = "/_cluster/health/$index"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'level', + 'local', + 'master_timeout', + 'timeout', + 'wait_for_active_shards', + 'wait_for_nodes', + 'wait_for_relocating_shards', + 'wait_for_status', + 'wait_for_events', + 'wait_for_no_relocating_shards' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/AbstractNodesEndpoint.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/AbstractNodesEndpoint.php new file mode 100644 index 0000000..3b817b1 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/AbstractNodesEndpoint.php @@ -0,0 +1,47 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +abstract class AbstractNodesEndpoint extends AbstractEndpoint +{ + /** @var string A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes */ + protected $nodeID; + + /** + * @param $nodeID + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * + * @return $this + */ + public function setNodeID($nodeID) + { + if (isset($nodeID) !== true) { + return $this; + } + + if (!(is_array($nodeID) === true || is_string($nodeID) === true)) { + throw new InvalidArgumentException("invalid node_id"); + } + + if (is_array($nodeID) === true) { + $nodeID = implode(',', $nodeID); + } + + $this->nodeID = $nodeID; + + return $this; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/HotThreads.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/HotThreads.php new file mode 100644 index 0000000..eeb4a96 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/HotThreads.php @@ -0,0 +1,51 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class HotThreads extends AbstractNodesEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $node_id = $this->nodeID; + $uri = "/_cluster/nodes/hotthreads"; + + if (isset($node_id) === true) { + $uri = "/_cluster/nodes/$node_id/hotthreads"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'interval', + 'snapshots', + 'threads', + 'type', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Info.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Info.php new file mode 100644 index 0000000..bde5305 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Info.php @@ -0,0 +1,77 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Info extends AbstractNodesEndpoint +{ + // A comma-separated list of metrics you wish returned. Leave empty to return all. + private $metric; + + /** + * @param $metric + * + * @return $this + */ + public function setMetric($metric) + { + if (isset($metric) !== true) { + return $this; + } + + if (is_array($metric) === true) { + $metric = implode(",", $metric); + } + + $this->metric = $metric; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $node_id = $this->nodeID; + $metric = $this->metric; + $uri = "/_nodes"; + + if (isset($node_id) === true && isset($metric) === true) { + $uri = "/_nodes/$node_id/$metric"; + } elseif (isset($metric) === true) { + $uri = "/_nodes/$metric"; + } elseif (isset($node_id) === true) { + $uri = "/_nodes/$node_id"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'flat_settings', + 'human', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Shutdown.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Shutdown.php new file mode 100644 index 0000000..6b7a6f2 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Shutdown.php @@ -0,0 +1,49 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Shutdown extends AbstractNodesEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $node_id = $this->nodeID; + $uri = "/_shutdown"; + + if (isset($node_id) === true) { + $uri = "/_cluster/nodes/$node_id/_shutdown"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'delay', + 'exit', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Stats.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Stats.php new file mode 100644 index 0000000..94dd6cf --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Nodes/Stats.php @@ -0,0 +1,111 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Stats extends AbstractNodesEndpoint +{ + // Limit the information returned to the specified metrics + private $metric; + + // Limit the information returned for `indices` metric to the specific index metrics. Isn't used if `indices` (or `all`) metric isn't specified. + private $indexMetric; + + /** + * @param $metric + * + * @return $this + */ + public function setMetric($metric) + { + if (isset($metric) !== true) { + return $this; + } + + if (is_array($metric) === true) { + $metric = implode(",", $metric); + } + + $this->metric = $metric; + + return $this; + } + + /** + * @param $indexMetric + * + * @return $this + */ + public function setIndexMetric($indexMetric) + { + if (isset($indexMetric) !== true) { + return $this; + } + + if (is_array($indexMetric) === true) { + $indexMetric = implode(",", $indexMetric); + } + + $this->indexMetric = $indexMetric; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $metric = $this->metric; + $index_metric = $this->indexMetric; + $node_id = $this->nodeID; + $uri = "/_nodes/stats"; + + if (isset($node_id) === true && isset($metric) === true && isset($index_metric) === true) { + $uri = "/_nodes/$node_id/stats/$metric/$index_metric"; + } elseif (isset($metric) === true && isset($index_metric) === true) { + $uri = "/_nodes/stats/$metric/$index_metric"; + } elseif (isset($node_id) === true && isset($metric) === true) { + $uri = "/_nodes/$node_id/stats/$metric"; + } elseif (isset($metric) === true) { + $uri = "/_nodes/stats/$metric"; + } elseif (isset($node_id) === true) { + $uri = "/_nodes/$node_id/stats"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'completion_fields', + 'fielddata_fields', + 'fields', + 'groups', + 'human', + 'level', + 'types', + 'include_segment_file_sizes', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/PendingTasks.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/PendingTasks.php new file mode 100644 index 0000000..3ceac3a --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/PendingTasks.php @@ -0,0 +1,46 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class PendingTasks extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $uri = "/_cluster/pending_tasks"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Reroute.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Reroute.php new file mode 100644 index 0000000..4113053 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Reroute.php @@ -0,0 +1,68 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Reroute extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $uri = "/_cluster/reroute"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'dry_run', + 'filter_metadata', + 'master_timeout', + 'timeout', + 'explain', + 'metric' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Settings/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Settings/Get.php new file mode 100644 index 0000000..12a5349 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Settings/Get.php @@ -0,0 +1,48 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ + +class Get extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $uri = "/_cluster/settings"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'flat_settings', + 'master_timeout', + 'timeout', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Settings/Put.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Settings/Put.php new file mode 100644 index 0000000..522e7b1 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Settings/Put.php @@ -0,0 +1,63 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Put extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $uri = "/_cluster/settings"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'flat_settings', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'PUT'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/State.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/State.php new file mode 100644 index 0000000..94af3b1 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/State.php @@ -0,0 +1,82 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class State extends AbstractEndpoint +{ + // Limit the information returned to the specified metrics + private $metric; + + /** + * @param $metric + * + * @return $this + */ + public function setMetric($metric) + { + if (isset($metric) !== true) { + return $this; + } + + if (is_array($metric) === true) { + $metric = implode(",", $metric); + } + + $this->metric = $metric; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $metric = $this->metric; + $uri = "/_cluster/state"; + + if (isset($metric) === true && isset($index) === true) { + $uri = "/_cluster/state/$metric/$index"; + } elseif (isset($metric) === true) { + $uri = "/_cluster/state/$metric"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout', + 'flat_settings', + 'index_templates', + 'expand_wildcards', + 'ignore_unavailable', + 'allow_no_indices' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Stats.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Stats.php new file mode 100644 index 0000000..729b611 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Cluster/Stats.php @@ -0,0 +1,70 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Stats extends AbstractEndpoint +{ + // A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes + private $nodeID; + + /** + * @param $node_id + * + * @return $this + */ + public function setNodeID($node_id) + { + if (isset($node_id) !== true) { + return $this; + } + + $this->nodeID = $node_id; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $node_id = $this->nodeID; + $uri = "/_cluster/stats"; + + if (isset($node_id) === true) { + $uri = "/_cluster/stats/nodes/$node_id"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'flat_settings', + 'human', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Count.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Count.php new file mode 100644 index 0000000..7bedb4b --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Count.php @@ -0,0 +1,86 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Count extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $type = $this->type; + $uri = "/_count"; + + if (isset($index) === true && isset($type) === true) { + $uri = "/$index/$type/_count"; + } elseif (isset($type) === true) { + $uri = "/_all/$type/_count"; + } elseif (isset($index) === true) { + $uri = "/$index/_count"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'min_score', + 'preference', + 'routing', + 'source', + 'q', + 'df', + 'default_operator', + 'analyzer', + 'lowercase_expanded_terms', + 'analyze_wildcard', + 'lenient', + 'lowercase_expanded_terms' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/CountPercolate.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/CountPercolate.php new file mode 100644 index 0000000..f87d6b5 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/CountPercolate.php @@ -0,0 +1,90 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class CountPercolate extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for CountPercolate' + ); + } + + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for CountPercolate' + ); + } + + $index = $this->index; + $type = $this->type; + $id = $this->id; + $uri = "/$index/$type/_percolate/count"; + + if (isset($id) === true) { + $uri = "/$index/$type/$id/_percolate/count"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'routing', + 'preference', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'percolate_index', + 'percolate_type', + 'version', + 'version_type' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Create.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Create.php new file mode 100644 index 0000000..bbecabe --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Create.php @@ -0,0 +1,107 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Create extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Create' + ); + } + + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for Create' + ); + } + + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for Create' + ); + } + + $id = $this->id; + $index = $this->index; + $type = $this->type; + return "/$index/$type/$id/_create"; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'consistency', + 'op_type', + 'parent', + 'percolate', + 'refresh', + 'replication', + 'routing', + 'timeout', + 'timestamp', + 'ttl', + 'version', + 'version_type', + 'pipeline' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'PUT'; + } + + /** + * @return array + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + */ + public function getBody() + { + if (isset($this->body) !== true) { + throw new Exceptions\RuntimeException('Document body must be set for create request'); + } else { + return $this->body; + } + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Delete.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Delete.php new file mode 100644 index 0000000..3f51bae --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Delete.php @@ -0,0 +1,75 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Delete extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for Delete' + ); + } + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Delete' + ); + } + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for Delete' + ); + } + $id = $this->id; + $index = $this->index; + $type = $this->type; + $uri = "/$index/$type/$id"; + + if (isset($index) === true && isset($type) === true && isset($id) === true) { + $uri = "/$index/$type/$id"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'consistency', + 'parent', + 'refresh', + 'replication', + 'routing', + 'timeout', + 'version', + 'version_type', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'DELETE'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/DeleteByQuery.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/DeleteByQuery.php new file mode 100644 index 0000000..1826589 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/DeleteByQuery.php @@ -0,0 +1,103 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class DeleteByQuery extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (!$this->index) { + throw new Exceptions\RuntimeException( + 'index is required for Deletebyquery' + ); + } + + $uri = "/{$this->index}/_delete_by_query"; + if ($this->type) { + $uri = "/{$this->index}/{$this->type}/_delete_by_query"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + '_source', + '_source_exclude', + '_source_include', + 'allow_no_indices', + 'analyze_wildcard', + 'analyzer', + 'conflicts', + 'default_operator', + 'df', + 'expand_wildcards', + 'from', + 'ignore_unavailable', + 'lenient', + 'preference', + 'query', + 'refresh', + 'request_cache', + 'requests_per_second', + 'routing', + 'scroll', + 'scroll_size', + 'search_timeout', + 'search_type', + 'size', + 'slices', + 'sort', + 'stats', + 'terminate_after', + 'timeout', + 'version', + 'wait_for_active_shards', + 'wait_for_completion', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Exists.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Exists.php new file mode 100644 index 0000000..a9333d5 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Exists.php @@ -0,0 +1,74 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Exists extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for Exists' + ); + } + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Exists' + ); + } + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for Exists' + ); + } + $id = $this->id; + $index = $this->index; + $type = $this->type; + $uri = "/$index/$type/$id"; + + if (isset($index) === true && isset($type) === true && isset($id) === true) { + $uri = "/$index/$type/$id"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'parent', + 'preference', + 'realtime', + 'refresh', + 'routing', + 'version', + 'stored_fields' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'HEAD'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Explain.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Explain.php new file mode 100644 index 0000000..0858f88 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Explain.php @@ -0,0 +1,100 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Explain extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for Explain' + ); + } + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Explain' + ); + } + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for Explain' + ); + } + $id = $this->id; + $index = $this->index; + $type = $this->type; + $uri = "/$index/$type/$id/_explain"; + + if (isset($index) === true && isset($type) === true && isset($id) === true) { + $uri = "/$index/$type/$id/_explain"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'analyze_wildcard', + 'analyzer', + 'default_operator', + 'df', + 'fields', + 'lenient', + 'lowercase_expanded_terms', + 'parent', + 'preference', + 'q', + 'routing', + 'source', + '_source', + '_source_exclude', + '_source_include', + 'stored_fields' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/FieldStats.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/FieldStats.php new file mode 100644 index 0000000..1ca80d8 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/FieldStats.php @@ -0,0 +1,73 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class FieldStats extends AbstractEndpoint +{ + + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_field_stats"; + + if (isset($index) === true) { + $uri = "/$index/_field_stats"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'fields', + 'level', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'fields' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Get.php new file mode 100644 index 0000000..34fa666 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Get.php @@ -0,0 +1,113 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + /** @var bool */ + private $returnOnlySource = false; + + /** @var bool */ + private $checkOnlyExistance = false; + + /** + * @return $this + */ + public function returnOnlySource() + { + $this->returnOnlySource = true; + + return $this; + } + + /** + * @return $this + */ + public function checkOnlyExistance() + { + $this->checkOnlyExistance = true; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for Get' + ); + } + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Get' + ); + } + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for Get' + ); + } + $id = $this->id; + $index = $this->index; + $type = $this->type; + $uri = "/$index/$type/$id"; + + if (isset($index) === true && isset($type) === true && isset($id) === true) { + $uri = "/$index/$type/$id"; + } + + if ($this->returnOnlySource === true) { + $uri .= '/_source'; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'fields', + 'parent', + 'preference', + 'realtime', + 'refresh', + 'routing', + '_source', + '_source_exclude', + '_source_include', + 'version', + 'version_type', + 'stored_fields' + ); + } + + /** + * @return string + */ + public function getMethod() + { + if ($this->checkOnlyExistance === true) { + return 'HEAD'; + } else { + return 'GET'; + } + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Index.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Index.php new file mode 100644 index 0000000..ba3c82a --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Index.php @@ -0,0 +1,123 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Index extends AbstractEndpoint +{ + /** @var bool */ + private $createIfAbsent = false; + + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return $this + */ + public function createIfAbsent() + { + $this->createIfAbsent = true; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Index' + ); + } + + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for Index' + ); + } + + $id = $this->id; + $index = $this->index; + $type = $this->type; + $uri = "/$index/$type"; + + if (isset($id) === true) { + $uri = "/$index/$type/$id"; + } + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'consistency', + 'op_type', + 'parent', + 'percolate', + 'refresh', + 'replication', + 'routing', + 'timeout', + 'timestamp', + 'ttl', + 'version', + 'version_type', + 'pipeline' + ); + } + + /** + * @return string + */ + public function getMethod() + { + if (isset($this->id) === true) { + return 'PUT'; + } else { + return 'POST'; + } + } + + /** + * @return array + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + */ + public function getBody() + { + if (isset($this->body) !== true) { + throw new Exceptions\RuntimeException('Document body must be set for index request'); + } else { + return $this->body; + } + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/AbstractAliasEndpoint.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/AbstractAliasEndpoint.php new file mode 100644 index 0000000..c3ebf84 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/AbstractAliasEndpoint.php @@ -0,0 +1,38 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +abstract class AbstractAliasEndpoint extends AbstractEndpoint +{ + /** @var null|string */ + protected $name = null; + + /** + * @param $name + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * + * @return $this + */ + public function setName($name) + { + if (is_string($name) !== true) { + throw new InvalidArgumentException('Name must be a string'); + } + $this->name = urlencode($name); + + return $this; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Delete.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Delete.php new file mode 100644 index 0000000..2ed4681 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Delete.php @@ -0,0 +1,83 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Delete extends AbstractEndpoint +{ + // A comma-separated list of aliases to delete (supports wildcards); use `_all` to delete all aliases for the specified indices. + private $name; + + /** + * @param $name + * + * @return $this + */ + public function setName($name) + { + if (isset($name) !== true) { + return $this; + } + + $this->name = $name; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Delete' + ); + } + if (isset($this->name) !== true) { + throw new Exceptions\RuntimeException( + 'name is required for Delete' + ); + } + $index = $this->index; + $name = $this->name; + $uri = "/$index/_alias/$name"; + + if (isset($index) === true && isset($name) === true) { + $uri = "/$index/_alias/$name"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'timeout', + 'master_timeout', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'DELETE'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Exists.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Exists.php new file mode 100644 index 0000000..abc978a --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Exists.php @@ -0,0 +1,77 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Exists extends AbstractEndpoint +{ + // A comma-separated list of alias names to return + private $name; + + /** + * @param $name + * + * @return $this + */ + public function setName($name) + { + if (isset($name) !== true) { + return $this; + } + + $this->name = $name; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $name = $this->name; + $uri = "/_alias/$name"; + + if (isset($index) === true && isset($name) === true) { + $uri = "/$index/_alias/$name"; + } elseif (isset($index) === true) { + $uri = "/$index/_alias"; + } elseif (isset($name) === true) { + $uri = "/_alias/$name"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'local', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'HEAD'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Get.php new file mode 100644 index 0000000..8b2ae03 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Get.php @@ -0,0 +1,77 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + // A comma-separated list of alias names to return + private $name; + + /** + * @param $name + * + * @return $this + */ + public function setName($name) + { + if (isset($name) !== true) { + return $this; + } + + $this->name = $name; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $name = $this->name; + $uri = "/_alias"; + + if (isset($index) === true && isset($name) === true) { + $uri = "/$index/_alias/$name"; + } elseif (isset($index) === true) { + $uri = "/$index/_alias"; + } elseif (isset($name) === true) { + $uri = "/_alias/$name"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'local', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Put.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Put.php new file mode 100644 index 0000000..a91d625 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Alias/Put.php @@ -0,0 +1,97 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Put extends AbstractEndpoint +{ + // The name of the alias to be created or updated + private $name; + + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @param $name + * + * @return $this + */ + public function setName($name) + { + if (isset($name) !== true) { + return $this; + } + + $this->name = $name; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->name) !== true) { + throw new Exceptions\RuntimeException( + 'name is required for Put' + ); + } + + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Put' + ); + } + $index = $this->index; + $name = $this->name; + $uri = "/$index/_alias/$name"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'timeout', + 'master_timeout', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'PUT'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Aliases/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Aliases/Get.php new file mode 100644 index 0000000..4e9287f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Aliases/Get.php @@ -0,0 +1,75 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + // A comma-separated list of alias names to filter + private $name; + + /** + * @param $name + * + * @return $this + */ + public function setName($name) + { + if (isset($name) !== true) { + return $this; + } + + $this->name = $name; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $name = $this->name; + $uri = "/_aliases"; + + if (isset($index) === true && isset($name) === true) { + $uri = "/$index/_aliases/$name"; + } elseif (isset($name) === true) { + $uri = "/_aliases/$name"; + } elseif (isset($index) === true) { + $uri = "/$index/_aliases"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'timeout', + 'local', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Aliases/Update.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Aliases/Update.php new file mode 100644 index 0000000..a715b0f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Aliases/Update.php @@ -0,0 +1,77 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Update extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $uri = "/_aliases"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'timeout', + 'master_timeout', + ); + } + + /** + * @return array + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + */ + public function getBody() + { + if (isset($this->body) !== true) { + throw new Exceptions\RuntimeException('Body is required for Update Aliases'); + } + + return $this->body; + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Analyze.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Analyze.php new file mode 100644 index 0000000..ac77879 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Analyze.php @@ -0,0 +1,79 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Analyze extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_analyze"; + + if (isset($index) === true) { + $uri = "/$index/_analyze"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'analyzer', + 'field', + 'filter', + 'index', + 'prefer_local', + 'text', + 'tokenizer', + 'format', + 'char_filter', + 'explain', + 'attributes', + 'format' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Cache/Clear.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Cache/Clear.php new file mode 100644 index 0000000..6789d3e --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Cache/Clear.php @@ -0,0 +1,64 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Clear extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_cache/clear"; + + if (isset($index) === true) { + $uri = "/$index/_cache/clear"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'field_data', + 'fielddata', + 'fields', + 'filter', + 'filter_cache', + 'filter_keys', + 'id', + 'id_cache', + 'index', + 'recycler', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'request_cache', + 'request' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ClearCache.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ClearCache.php new file mode 100644 index 0000000..4b502ba --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ClearCache.php @@ -0,0 +1,62 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class ClearCache extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_cache/clear"; + + if (isset($index) === true) { + $uri = "/$index/_cache/clear"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'field_data', + 'fielddata', + 'fields', + 'filter', + 'filter_cache', + 'filter_keys', + 'id', + 'id_cache', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'index', + 'recycler', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Close.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Close.php new file mode 100644 index 0000000..3d5bf7e --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Close.php @@ -0,0 +1,61 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Close extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Close' + ); + } + $index = $this->index; + $uri = "/$index/_close"; + + if (isset($index) === true) { + $uri = "/$index/_close"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'timeout', + 'master_timeout', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Create.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Create.php new file mode 100644 index 0000000..505d252 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Create.php @@ -0,0 +1,77 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Create extends AbstractEndpoint +{ + /** + * @param array|object $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Create' + ); + } + $index = $this->index; + $uri = "/$index"; + + if (isset($index) === true) { + $uri = "/$index"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'timeout', + 'master_timeout', + 'update_all_types', + 'wait_for_active_shards' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'PUT'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Delete.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Delete.php new file mode 100644 index 0000000..b832e71 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Delete.php @@ -0,0 +1,51 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Delete extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/$index"; + + if (isset($index) === true) { + $uri = "/$index"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'timeout', + 'master_timeout', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'DELETE'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Exists.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Exists.php new file mode 100644 index 0000000..ac45e3f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Exists.php @@ -0,0 +1,60 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Exists extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Exists' + ); + } + $index = $this->index; + $uri = "/$index"; + + if (isset($index) === true) { + $uri = "/$index"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'local', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'HEAD'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Exists/Types.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Exists/Types.php new file mode 100644 index 0000000..a5b4b67 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Exists/Types.php @@ -0,0 +1,63 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Types extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Types Exists' + ); + } + + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for Types Exists' + ); + } + + $index = $this->index; + $type = $this->type; + $uri = "/$index/$type"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'HEAD'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Field/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Field/Get.php new file mode 100644 index 0000000..c991234 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Field/Get.php @@ -0,0 +1,88 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + // A comma-separated list of fields + private $field; + + /** + * @param $field + * + * @return $this + */ + public function setField($field) + { + if (isset($field) !== true) { + return $this; + } + + $this->field = $field; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->field) !== true) { + throw new Exceptions\RuntimeException( + 'field is required for Get' + ); + } + $index = $this->index; + $type = $this->type; + $field = $this->field; + $uri = "/_mapping/field/$field"; + + if (isset($index) === true && isset($type) === true && isset($field) === true) { + $uri = "/$index/_mapping/$type/field/$field"; + } elseif (isset($type) === true && isset($field) === true) { + $uri = "/_mapping/$type/field/$field"; + } elseif (isset($index) === true && isset($field) === true) { + $uri = "/$index/_mapping/field/$field"; + } elseif (isset($field) === true) { + $uri = "/_mapping/field/$field"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'include_defaults', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'local', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Flush.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Flush.php new file mode 100644 index 0000000..f8d7c4b --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Flush.php @@ -0,0 +1,66 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Flush extends AbstractEndpoint +{ + protected $synced = false; + + public function setSynced($synced) + { + $this->synced = $synced; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_flush"; + + if (isset($index) === true) { + $uri = "/$index/_flush"; + } + + if ($this->synced === true) { + $uri .= "/synced"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'force', + 'full', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'wait_if_ongoing' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ForceMerge.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ForceMerge.php new file mode 100644 index 0000000..3e6e0b4 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ForceMerge.php @@ -0,0 +1,57 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class ForceMerge extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_forcemerge"; + + if (isset($index) === true) { + $uri = "/$index/_forcemerge"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'flush', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'max_num_segments', + 'only_expunge_deletes', + 'operation_threading', + 'wait_for_merge', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Gateway/Snapshot.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Gateway/Snapshot.php new file mode 100644 index 0000000..b492cea --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Gateway/Snapshot.php @@ -0,0 +1,52 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Snapshot extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_gateway/snapshot"; + + if (isset($index) === true) { + $uri = "/$index/_gateway/snapshot"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Get.php new file mode 100644 index 0000000..58a7de7 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Get.php @@ -0,0 +1,79 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + private $feature; + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Get' + ); + } + $index = $this->index; + $feature = $this->feature; + $uri = "/$index"; + + if (isset($feature) === true) { + $uri = "/$index/$feature"; + } + + return $uri; + } + + public function setFeature($feature) + { + if (isset($feature) !== true) { + return $this; + } + + if (is_array($feature) === true) { + $feature = implode(",", $feature); + } + + $this->feature = $feature; + + return $this; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'human' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Delete.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Delete.php new file mode 100644 index 0000000..87ac13d --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Delete.php @@ -0,0 +1,63 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Delete extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Delete' + ); + } + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for Delete' + ); + } + $index = $this->index; + $type = $this->type; + $uri = "/$index/$type/_mapping"; + + if (isset($index) === true && isset($type) === true) { + $uri = "/$index/$type/_mapping"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'master_timeout', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'DELETE'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Get.php new file mode 100644 index 0000000..88568e6 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Get.php @@ -0,0 +1,59 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $type = $this->type; + $uri = "/_mapping"; + + if (isset($index) === true && isset($type) === true) { + $uri = "/$index/_mapping/$type"; + } elseif (isset($type) === true) { + $uri = "/_mapping/$type"; + } elseif (isset($index) === true) { + $uri = "/$index/_mapping"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'wildcard_expansion', + 'local', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/GetField.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/GetField.php new file mode 100644 index 0000000..068be33 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/GetField.php @@ -0,0 +1,79 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class GetField extends AbstractEndpoint +{ + /** @var string */ + private $fields; + + /** + * @param string|array $fields + * + * @return $this + */ + public function setFields($fields) + { + if (isset($fields) !== true) { + return $this; + } + + if (is_array($fields) === true) { + $fields = implode(",", $fields); + } + + $this->fields = $fields; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->fields) !== true) { + throw new Exceptions\RuntimeException( + 'fields is required for Get Field Mapping' + ); + } + $uri = $this->getOptionalURI('_mapping/field'); + + return $uri.'/'.$this->fields; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'include_defaults', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'local' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Put.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Put.php new file mode 100644 index 0000000..1639ca7 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Mapping/Put.php @@ -0,0 +1,96 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Put extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for Put' + ); + } + $index = $this->index; + $type = $this->type; + $uri = "/_mapping/$type"; + + if (isset($index) === true && isset($type) === true) { + $uri = "/$index/$type/_mapping"; + } elseif (isset($type) === true) { + $uri = "/_mapping/$type"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_conflicts', + 'timeout', + 'master_timeout', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'update_all_types' + ); + } + + /** + * @return array + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + */ + public function getBody() + { + if (isset($this->body) !== true) { + throw new Exceptions\RuntimeException('Body is required for Put Mapping'); + } + + return $this->body; + } + + /** + * @return string + */ + public function getMethod() + { + return 'PUT'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Open.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Open.php new file mode 100644 index 0000000..6808659 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Open.php @@ -0,0 +1,61 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Open extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Open' + ); + } + $index = $this->index; + $uri = "/$index/_open"; + + if (isset($index) === true) { + $uri = "/$index/_open"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'timeout', + 'master_timeout', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Recovery.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Recovery.php new file mode 100644 index 0000000..11e11cf --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Recovery.php @@ -0,0 +1,52 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Recovery extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_recovery"; + + if (isset($index) === true) { + $uri = "/$index/_recovery"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'detailed', + 'active_only', + 'human' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Refresh.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Refresh.php new file mode 100644 index 0000000..e7938d1 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Refresh.php @@ -0,0 +1,54 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Refresh extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_refresh"; + + if (isset($index) === true) { + $uri = "/$index/_refresh"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'force', + 'operation_threading', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Rollover.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Rollover.php new file mode 100644 index 0000000..bc188ea --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Rollover.php @@ -0,0 +1,109 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Rollover extends AbstractEndpoint +{ + private $alias; + private $newIndex; + + /** + * @param string $alias + * + * @return $this + */ + public function setAlias($alias) + { + if ($alias === null) { + return $this; + } + + $this->alias = urlencode($alias); + return $this; + } + + /** + * @param string $newIndex + * + * @return $this + */ + public function setNewIndex($newIndex) + { + if ($newIndex === null) { + return $this; + } + + $this->newIndex = urlencode($newIndex); + return $this; + } + + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->alias) !== true) { + throw new Exceptions\RuntimeException( + 'alias name is required for Rollover' + ); + } + + $uri = "/{$this->alias}/_rollover"; + + if (isset($this->newIndex) === true) { + $uri .= "/{$this->newIndex}"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'timeout', + 'master_timeout', + 'wait_for_active_shards', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Seal.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Seal.php new file mode 100644 index 0000000..c6f5138 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Seal.php @@ -0,0 +1,50 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Seal extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_seal"; + + if (isset($index) === true) { + $uri = "/$index/_seal"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array(); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Segments.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Segments.php new file mode 100644 index 0000000..8ade291 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Segments.php @@ -0,0 +1,54 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Segments extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_segments"; + + if (isset($index) === true) { + $uri = "/$index/_segments"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'human', + 'operation_threading', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Settings/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Settings/Get.php new file mode 100644 index 0000000..943bfd9 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Settings/Get.php @@ -0,0 +1,79 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + // The name of the settings that should be included + private $name; + + /** + * @param $name + * + * @return $this + */ + public function setName($name) + { + if (isset($name) !== true) { + return $this; + } + + $this->name = $name; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $name = $this->name; + $uri = "/_settings"; + + if (isset($index) === true && isset($name) === true) { + $uri = "/$index/_settings/$name"; + } elseif (isset($name) === true) { + $uri = "/_settings/$name"; + } elseif (isset($index) === true) { + $uri = "/$index/_settings"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'flat_settings', + 'local', + 'include_defaults' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Settings/Put.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Settings/Put.php new file mode 100644 index 0000000..57f211a --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Settings/Put.php @@ -0,0 +1,86 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Put extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_settings"; + + if (isset($index) === true) { + $uri = "/$index/_settings"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'master_timeout', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'flat_settings', + 'preserve_existing' + ); + } + + /** + * @return array + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + */ + public function getBody() + { + if (isset($this->body) !== true) { + throw new Exceptions\RuntimeException('Body is required for Put Settings'); + } + + return $this->body; + } + + /** + * @return string + */ + public function getMethod() + { + return 'PUT'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ShardStores.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ShardStores.php new file mode 100644 index 0000000..10dc447 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ShardStores.php @@ -0,0 +1,59 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ + +class ShardStores extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_shard_stores"; + + if (isset($index) === true) { + $uri = "/$index/_shard_stores"; + } + + return $uri; + } + + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'status', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'operation_threading' + ); + } + + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Shrink.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Shrink.php new file mode 100644 index 0000000..b4e7832 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Shrink.php @@ -0,0 +1,101 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * + * @link http://elastic.co + */ +class Shrink extends AbstractEndpoint +{ + // The name of the target index to shrink into + private $target; + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @param $target + * + * @return $this + */ + public function setTarget($target) + { + if (isset($target) !== true) { + return $this; + } + $this->target = $target; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\BadMethodCallException + * + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Shrink' + ); + } + if (isset($this->target) !== true) { + throw new Exceptions\RuntimeException( + 'target is required for Shrink' + ); + } + $index = $this->index; + $target = $this->target; + $uri = "/$index/_shrink/$target"; + if (isset($index) === true && isset($target) === true) { + $uri = "/$index/_shrink/$target"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'timeout', + 'master_timeout', + ); + } + + /** + * @return string + */ + public function getMethod() + { + //TODO Fix Me! + return 'PUT'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Snapshotindex.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Snapshotindex.php new file mode 100644 index 0000000..e30530b --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Snapshotindex.php @@ -0,0 +1,52 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Snapshotindex extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_gateway/snapshot"; + + if (isset($index) === true) { + $uri = "/$index/_gateway/snapshot"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Stats.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Stats.php new file mode 100644 index 0000000..bbdc1d2 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Stats.php @@ -0,0 +1,86 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Stats extends AbstractEndpoint +{ + // Limit the information returned the specific metrics. + private $metric; + + /** + * @param $metric + * + * @return $this + */ + public function setMetric($metric) + { + if (isset($metric) !== true) { + return $this; + } + + if (is_array($metric)) { + $metric = implode(",", $metric); + } + + $this->metric = $metric; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $metric = $this->metric; + $uri = "/_stats"; + + if (isset($index) === true && isset($metric) === true) { + $uri = "/$index/_stats/$metric"; + } elseif (isset($index) === true) { + $uri = "/$index/_stats"; + } elseif (isset($metric) === true) { + $uri = "/_stats/$metric"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'completion_fields', + 'fielddata_fields', + 'fields', + 'groups', + 'human', + 'level', + 'types', + 'metric', + 'include_segment_file_sizes' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Status.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Status.php new file mode 100644 index 0000000..fc52f84 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Status.php @@ -0,0 +1,56 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Status extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_status"; + + if (isset($index) === true) { + $uri = "/$index/_status"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'human', + 'operation_threading', + 'recovery', + 'snapshot', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/AbstractTemplateEndpoint.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/AbstractTemplateEndpoint.php new file mode 100644 index 0000000..cde0225 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/AbstractTemplateEndpoint.php @@ -0,0 +1,32 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +abstract class AbstractTemplateEndpoint extends AbstractEndpoint +{ + /** @var string */ + protected $name; + + /** + * @param $name + * + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Delete.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Delete.php new file mode 100644 index 0000000..044dce6 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Delete.php @@ -0,0 +1,78 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ + +class Delete extends AbstractEndpoint +{ + // The name of the template + private $name; + + /** + * @param $name + * + * @return $this + */ + public function setName($name) + { + if (isset($name) !== true) { + return $this; + } + + $this->name = $name; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->name) !== true) { + throw new Exceptions\RuntimeException( + 'name is required for Delete' + ); + } + $name = $this->name; + $uri = "/_template/$name"; + + if (isset($name) === true) { + $uri = "/_template/$name"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'timeout', + 'master_timeout', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'DELETE'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Exists.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Exists.php new file mode 100644 index 0000000..ebf6fdf --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Exists.php @@ -0,0 +1,77 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Exists extends AbstractEndpoint +{ + // The name of the template + private $name; + + /** + * @param $name + * + * @return $this + */ + public function setName($name) + { + if (isset($name) !== true) { + return $this; + } + + $this->name = $name; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->name) !== true) { + throw new Exceptions\RuntimeException( + 'name is required for Exists' + ); + } + $name = $this->name; + $uri = "/_template/$name"; + + if (isset($name) === true) { + $uri = "/_template/$name"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'local', + 'master_timeout' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'HEAD'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Get.php new file mode 100644 index 0000000..7747206 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Get.php @@ -0,0 +1,73 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + // The name of the template + private $name; + + /** + * @param $name + * + * @return $this + */ + public function setName($name) + { + if (isset($name) !== true) { + return $this; + } + + $this->name = $name; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + $name = $this->name; + $uri = "/_template"; + + if (isset($name) === true) { + $uri = "/_template/$name"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'flat_settings', + 'local', + 'master_timeout' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Put.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Put.php new file mode 100644 index 0000000..3aca046 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Template/Put.php @@ -0,0 +1,110 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Put extends AbstractEndpoint +{ + // The name of the template + private $name; + + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @param $name + * + * @return $this + */ + public function setName($name) + { + if (isset($name) !== true) { + return $this; + } + + $this->name = $name; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->name) !== true) { + throw new Exceptions\RuntimeException( + 'name is required for Put' + ); + } + $name = $this->name; + $uri = "/_template/$name"; + + if (isset($name) === true) { + $uri = "/_template/$name"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'order', + 'timeout', + 'master_timeout', + 'flat_settings', + 'create' + ); + } + + /** + * @return array + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + */ + public function getBody() + { + if (isset($this->body) !== true) { + throw new Exceptions\RuntimeException('Body is required for Put Template'); + } + + return $this->body; + } + + /** + * @return string + */ + public function getMethod() + { + return 'PUT'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Type/Exists.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Type/Exists.php new file mode 100644 index 0000000..b295189 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Type/Exists.php @@ -0,0 +1,60 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Exists extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Exists' + ); + } + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for Exists' + ); + } + $uri = "/{$this->index}/_mapping/{$this->type}"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'local', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'HEAD'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Upgrade/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Upgrade/Get.php new file mode 100644 index 0000000..d9cb7be --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Upgrade/Get.php @@ -0,0 +1,65 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ + +class Get extends AbstractEndpoint +{ + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_upgrade"; + + if (isset($index) === true) { + $uri = "/$index/_upgrade"; + } + + + return $uri; + } + + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'wait_for_completion', + 'only_ancient_segments', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + ); + } + + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Upgrade/Post.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Upgrade/Post.php new file mode 100644 index 0000000..5b00f68 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Upgrade/Post.php @@ -0,0 +1,65 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ + +class Post extends AbstractEndpoint +{ + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_upgrade"; + + if (isset($index) === true) { + $uri = "/$index/_upgrade"; + } + + + return $uri; + } + + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'wait_for_completion', + 'only_ancient_segments', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + ); + } + + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Validate/Query.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Validate/Query.php new file mode 100644 index 0000000..a963038 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/Validate/Query.php @@ -0,0 +1,71 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Query extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + return $this->getOptionalURI('_validate/query'); + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'explain', + 'ignore_indices', + 'operation_threading', + 'source', + 'q', + 'df', + 'default_operator', + 'analyzer', + 'analyze_wildcard', + 'lenient', + 'lowercase_expanded_terms' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ValidateQuery.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ValidateQuery.php new file mode 100644 index 0000000..df94c02 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Indices/ValidateQuery.php @@ -0,0 +1,77 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class ValidateQuery extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $type = $this->type; + $uri = "/_validate/query"; + + if (isset($index) === true && isset($type) === true) { + $uri = "/$index/$type/_validate/query"; + } elseif (isset($index) === true) { + $uri = "/$index/_validate/query"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'explain', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'operation_threading', + 'source', + 'q', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Info.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Info.php new file mode 100644 index 0000000..dc157d7 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Info.php @@ -0,0 +1,42 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Info extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $uri = "/"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Delete.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Delete.php new file mode 100644 index 0000000..b61e9e8 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Delete.php @@ -0,0 +1,54 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Delete extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for DeletePipeline' + ); + } + $id = $this->id; + $uri = "/_ingest/pipeline/$id"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'master_timeout', + 'timeout' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'DELETE'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Get.php new file mode 100644 index 0000000..d5cf38f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Get.php @@ -0,0 +1,51 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->id) !== true) { + return '/_ingest/pipeline/*'; + } + + $id = $this->id; + + return "/_ingest/pipeline/$id"; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'master_timeout' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Put.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Put.php new file mode 100644 index 0000000..d8707b3 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Pipeline/Put.php @@ -0,0 +1,71 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Put extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for PutPipeline' + ); + } + $id = $this->id; + $uri = "/_ingest/pipeline/$id"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'master_timeout', + 'timeout' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'PUT'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Simulate.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Simulate.php new file mode 100644 index 0000000..f4570bb --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ingest/Simulate.php @@ -0,0 +1,65 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Simulate extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->id) === true) { + return "/_ingest/pipeline/{$this->id}/_simulate"; + } + return "/_ingest/pipeline/_simulate"; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'verbose', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MPercolate.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MPercolate.php new file mode 100644 index 0000000..47d20a8 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MPercolate.php @@ -0,0 +1,78 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class MPercolate extends AbstractEndpoint implements BulkEndpointInterface +{ + /** + * @param SerializerInterface $serializer + */ + public function __construct(SerializerInterface $serializer) + { + $this->serializer = $serializer; + } + + /** + * @param string|array $body + * + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + if (is_array($body) === true) { + $bulkBody = ""; + foreach ($body as $item) { + $bulkBody .= $this->serializer->serialize($item)."\n"; + } + $body = $bulkBody; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + return $this->getOptionalURI('_mpercolate'); + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MTermVectors.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MTermVectors.php new file mode 100644 index 0000000..e723920 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/MTermVectors.php @@ -0,0 +1,70 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class MTermVectors extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + return $this->getOptionalURI('_mtermvectors'); + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ids', + 'term_statistics', + 'field_statistics', + 'fields', + 'offsets', + 'positions', + 'payloads', + 'preference', + 'routing', + 'parent', + 'realtime' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Mget.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Mget.php new file mode 100644 index 0000000..2d7dd56 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Mget.php @@ -0,0 +1,93 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Mget extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $type = $this->type; + $uri = "/_mget"; + + if (isset($index) === true && isset($type) === true) { + $uri = "/$index/$type/_mget"; + } elseif (isset($index) === true) { + $uri = "/$index/_mget"; + } elseif (isset($type) === true) { + $uri = "/_all/$type/_mget"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'fields', + 'preference', + 'realtime', + 'refresh', + '_source', + '_source_exclude', + '_source_include', + 'routing', + 'stored_fields' + ); + } + + /** + * @return array + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + */ + public function getBody() + { + if (isset($this->body) !== true) { + throw new Exceptions\RuntimeException('Body is required for MGet'); + } + + return $this->body; + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Msearch.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Msearch.php new file mode 100644 index 0000000..13e515f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Msearch.php @@ -0,0 +1,104 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Msearch extends AbstractEndpoint +{ + /** + * @param SerializerInterface $serializer + */ + public function __construct(SerializerInterface $serializer) + { + $this->serializer = $serializer; + } + + /** + * @param array|string $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + if (is_array($body) === true) { + $bulkBody = ""; + foreach ($body as $item) { + $bulkBody .= $this->serializer->serialize($item)."\n"; + } + $body = $bulkBody; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $type = $this->type; + $uri = "/_msearch"; + + if (isset($index) === true && isset($type) === true) { + $uri = "/$index/$type/_msearch"; + } elseif (isset($index) === true) { + $uri = "/$index/_msearch"; + } elseif (isset($type) === true) { + $uri = "/_all/$type/_msearch"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'search_type', + 'typed_keys', + ); + } + + /** + * @return array + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + */ + public function getBody() + { + if (isset($this->body) !== true) { + throw new Exceptions\RuntimeException('Body is required for MSearch'); + } + + return $this->body; + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Percolate.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Percolate.php new file mode 100644 index 0000000..4418d7d --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Percolate.php @@ -0,0 +1,98 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Percolate extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Percolate' + ); + } + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for Percolate' + ); + } + $index = $this->index; + $type = $this->type; + $id = $this->id; + $uri = "/$index/$type/_percolate"; + + if (isset($id) === true) { + $uri = "/$index/$type/$id/_percolate"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'routing', + 'preference', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'percolate_index', + 'percolate_type', + 'version', + 'version_type', + 'percolate_format' + ); + } + + /** + * @return array + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + */ + public function getBody() + { + return $this->body; + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ping.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ping.php new file mode 100644 index 0000000..a11d902 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Ping.php @@ -0,0 +1,42 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Ping extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $uri = "/"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'HEAD'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Reindex.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Reindex.php new file mode 100644 index 0000000..5f8b97c --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Reindex.php @@ -0,0 +1,63 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Reindex extends AbstractEndpoint +{ + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'refresh', + 'timeout', + 'consistency', + 'wait_for_completion', + 'requests_per_second', + ); + } + + /** + * @return string + */ + public function getURI() + { + return '/_reindex'; + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } + + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/RenderSearchTemplate.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/RenderSearchTemplate.php new file mode 100644 index 0000000..c31eb72 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/RenderSearchTemplate.php @@ -0,0 +1,77 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ + +class RenderSearchTemplate extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + $id = $this->id; + + $uri = "/_render/template"; + + if (isset($id) === true) { + $uri = "/_render/template/$id"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array(); + } + + /** + * @return array + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + */ + public function getBody() + { + return $this->body; + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Delete.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Delete.php new file mode 100644 index 0000000..887c9f8 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Delete.php @@ -0,0 +1,79 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Delete extends AbstractEndpoint +{ + /** @var String */ + private $lang; + + /** + * @param $lang + * + * @return $this + */ + public function setLang($lang) + { + if (isset($lang) !== true) { + return $this; + } + + $this->lang = $lang; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->lang) !== true) { + throw new Exceptions\RuntimeException( + 'lang is required for Put' + ); + } + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for put' + ); + } + $id = $this->id; + $lang = $this->lang; + $uri = "/_scripts/$lang/$id"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'version', + 'version_type' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'DELETE'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Get.php new file mode 100644 index 0000000..78c01c8 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Get.php @@ -0,0 +1,79 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + /** @var String */ + private $lang; + + /** + * @param $lang + * + * @return $this + */ + public function setLang($lang) + { + if (isset($lang) !== true) { + return $this; + } + + $this->lang = $lang; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->lang) !== true) { + throw new Exceptions\RuntimeException( + 'lang is required for Put' + ); + } + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for put' + ); + } + $id = $this->id; + $lang = $this->lang; + $uri = "/_scripts/$lang/$id"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'version_type', + 'version' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Put.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Put.php new file mode 100644 index 0000000..d10603e --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Script/Put.php @@ -0,0 +1,96 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Put extends AbstractEndpoint +{ + /** @var String */ + private $lang; + + /** + * @param $lang + * + * @return $this + */ + public function setLang($lang) + { + if (isset($lang) !== true) { + return $this; + } + + $this->lang = $lang; + + return $this; + } + + /** + * @param array $body + * + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->lang) !== true) { + throw new Exceptions\RuntimeException( + 'lang is required for Put' + ); + } + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for put' + ); + } + $id = $this->id; + $lang = $this->lang; + $uri = "/_scripts/$lang/$id"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'version_type', + 'version', + 'op_type' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'PUT'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Scroll.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Scroll.php new file mode 100644 index 0000000..eb46fe3 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Scroll.php @@ -0,0 +1,102 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Scroll extends AbstractEndpoint +{ + + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return array + */ + public function getBody() + { + return $this->body; + } + + /** + * @param $scroll + * + * @return $this + */ + public function setScroll($scroll) + { + if (isset($scroll) !== true) { + return $this; + } + + $this->body['scroll'] = $scroll; + + return $this; + } + + /** + * @param $scroll_id + * + * @return $this + */ + public function setScrollId($scroll_id) + { + if (isset($scroll_id) !== true) { + return $this; + } + + $this->body['scroll_id'] = $scroll_id; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $uri = "/_search/scroll"; + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'scroll', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Search.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Search.php new file mode 100644 index 0000000..17815dc --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Search.php @@ -0,0 +1,111 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Search extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $type = $this->type; + $uri = "/_search"; + + if (isset($index) === true && isset($type) === true) { + $uri = "/$index/$type/_search"; + } elseif (isset($index) === true) { + $uri = "/$index/_search"; + } elseif (isset($type) === true) { + $uri = "/_all/$type/_search"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'analyzer', + 'analyze_wildcard', + 'default_operator', + 'df', + 'explain', + 'from', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'indices_boost', + 'lenient', + 'lowercase_expanded_terms', + 'preference', + 'q', + 'query_cache', + 'request_cache', + 'routing', + 'scroll', + 'search_type', + 'size', + 'sort', + 'source', + '_source', + '_source_exclude', + '_source_include', + 'stats', + 'suggest_field', + 'suggest_mode', + 'suggest_size', + 'suggest_text', + 'timeout', + 'version', + 'fielddata_fields', + 'docvalue_fields', + 'filter_path', + 'terminate_after', + 'stored_fields', + 'batched_reduce_size', + 'typed_keys' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/SearchShards.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/SearchShards.php new file mode 100644 index 0000000..85c564a --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/SearchShards.php @@ -0,0 +1,58 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class SearchShards extends AbstractEndpoint +{ + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $type = $this->type; + $uri = "/_search_shards"; + + if (isset($index) === true && isset($type) === true) { + $uri = "/$index/$type/_search_shards"; + } elseif (isset($index) === true) { + $uri = "/$index/_search_shards"; + } elseif (isset($type) === true) { + $uri = "/_all/$type/_search_shards"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'preference', + 'routing', + 'local', + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/SearchTemplate.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/SearchTemplate.php new file mode 100644 index 0000000..7b5c830 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/SearchTemplate.php @@ -0,0 +1,79 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class SearchTemplate extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $type = $this->type; + $uri = "/_search/template"; + + if (isset($index) === true && isset($type) === true) { + $uri = "/$index/$type/_search/template"; + } elseif (isset($index) === true) { + $uri = "/$index/_search/template"; + } elseif (isset($type) === true) { + $uri = "/_all/$type/_search/template"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'preference', + 'routing', + 'scroll', + 'search_type' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Create.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Create.php new file mode 100644 index 0000000..a00a1ce --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Create.php @@ -0,0 +1,119 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Create extends AbstractEndpoint +{ + // A repository name + private $repository; + + // A snapshot name + private $snapshot; + + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @param $repository + * + * @return $this + */ + public function setRepository($repository) + { + if (isset($repository) !== true) { + return $this; + } + + $this->repository = $repository; + + return $this; + } + + /** + * @param $snapshot + * + * @return $this + */ + public function setSnapshot($snapshot) + { + if (isset($snapshot) !== true) { + return $this; + } + + $this->snapshot = $snapshot; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->repository) !== true) { + throw new Exceptions\RuntimeException( + 'repository is required for Create' + ); + } + if (isset($this->snapshot) !== true) { + throw new Exceptions\RuntimeException( + 'snapshot is required for Create' + ); + } + $repository = $this->repository; + $snapshot = $this->snapshot; + $uri = "/_snapshot/$repository/$snapshot"; + + if (isset($repository) === true && isset($snapshot) === true) { + $uri = "/_snapshot/$repository/$snapshot"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'master_timeout', + 'wait_for_completion', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'PUT'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Delete.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Delete.php new file mode 100644 index 0000000..ca28cfc --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Delete.php @@ -0,0 +1,101 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Delete extends AbstractEndpoint +{ + // A repository name + private $repository; + + // A snapshot name + private $snapshot; + + /** + * @param $repository + * + * @return $this + */ + public function setRepository($repository) + { + if (isset($repository) !== true) { + return $this; + } + + $this->repository = $repository; + + return $this; + } + + /** + * @param $snapshot + * + * @return $this + */ + public function setSnapshot($snapshot) + { + if (isset($snapshot) !== true) { + return $this; + } + + $this->snapshot = $snapshot; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->repository) !== true) { + throw new Exceptions\RuntimeException( + 'repository is required for Delete' + ); + } + if (isset($this->snapshot) !== true) { + throw new Exceptions\RuntimeException( + 'snapshot is required for Delete' + ); + } + $repository = $this->repository; + $snapshot = $this->snapshot; + $uri = "/_snapshot/$repository/$snapshot"; + + if (isset($repository) === true && isset($snapshot) === true) { + $uri = "/_snapshot/$repository/$snapshot"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'master_timeout', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'DELETE'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Get.php new file mode 100644 index 0000000..70c6b54 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Get.php @@ -0,0 +1,102 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + // A comma-separated list of repository names + private $repository; + + // A comma-separated list of snapshot names + private $snapshot; + + /** + * @param $repository + * + * @return $this + */ + public function setRepository($repository) + { + if (isset($repository) !== true) { + return $this; + } + + $this->repository = $repository; + + return $this; + } + + /** + * @param $snapshot + * + * @return $this + */ + public function setSnapshot($snapshot) + { + if (isset($snapshot) !== true) { + return $this; + } + + $this->snapshot = $snapshot; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->repository) !== true) { + throw new Exceptions\RuntimeException( + 'repository is required for Get' + ); + } + if (isset($this->snapshot) !== true) { + throw new Exceptions\RuntimeException( + 'snapshot is required for Get' + ); + } + $repository = $this->repository; + $snapshot = $this->snapshot; + $uri = "/_snapshot/$repository/$snapshot"; + + if (isset($repository) === true && isset($snapshot) === true) { + $uri = "/_snapshot/$repository/$snapshot"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'master_timeout', + 'ignore_unavailable' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Create.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Create.php new file mode 100644 index 0000000..94275c7 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Create.php @@ -0,0 +1,107 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Create extends AbstractEndpoint +{ + // A repository name + private $repository; + + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @param $repository + * + * @return $this + */ + public function setRepository($repository) + { + if (isset($repository) !== true) { + return $this; + } + + $this->repository = $repository; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->repository) !== true) { + throw new Exceptions\RuntimeException( + 'repository is required for Create' + ); + } + $repository = $this->repository; + $uri = "/_snapshot/$repository"; + + if (isset($repository) === true) { + $uri = "/_snapshot/$repository"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'master_timeout', + 'timeout', + ); + } + + /** + * @return array + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + */ + public function getBody() + { + if (isset($this->body) !== true) { + throw new Exceptions\RuntimeException('Body is required for Create Repository'); + } + + return $this->body; + } + + /** + * @return string + */ + public function getMethod() + { + return 'PUT'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Delete.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Delete.php new file mode 100644 index 0000000..4e0109f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Delete.php @@ -0,0 +1,77 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Delete extends AbstractEndpoint +{ + // A comma-separated list of repository names + private $repository; + + /** + * @param $repository + * + * @return $this + */ + public function setRepository($repository) + { + if (isset($repository) !== true) { + return $this; + } + + $this->repository = $repository; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->repository) !== true) { + throw new Exceptions\RuntimeException( + 'repository is required for Delete' + ); + } + $repository = $this->repository; + $uri = "/_snapshot/$repository"; + + if (isset($repository) === true) { + $uri = "/_snapshot/$repository"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'master_timeout', + 'timeout', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'DELETE'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Get.php new file mode 100644 index 0000000..57af42b --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Get.php @@ -0,0 +1,70 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + // A comma-separated list of repository names + private $repository; + + /** + * @param $repository + * + * @return $this + */ + public function setRepository($repository) + { + if (isset($repository) !== true) { + return $this; + } + + $this->repository = $repository; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $repository = $this->repository; + $uri = "/_snapshot"; + + if (isset($repository) === true) { + $uri = "/_snapshot/$repository"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'master_timeout', + 'local', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Verify.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Verify.php new file mode 100644 index 0000000..ebd8fa3 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Repository/Verify.php @@ -0,0 +1,74 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Verify extends AbstractEndpoint +{ + // A comma-separated list of repository names + private $repository; + + /** + * @param $repository + * + * @return $this + */ + public function setRepository($repository) + { + if (isset($repository) !== true) { + return $this; + } + + $this->repository = $repository; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + $repository = $this->repository; + if (isset($this->repository) !== true) { + throw new Exceptions\RuntimeException( + 'repository is required for Verify' + ); + } + + $uri = "/_snapshot/$repository/_verify"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'master_timeout', + 'local', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Restore.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Restore.php new file mode 100644 index 0000000..193d203 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Restore.php @@ -0,0 +1,119 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Restore extends AbstractEndpoint +{ + // A repository name + private $repository; + + // A snapshot name + private $snapshot; + + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @param $repository + * + * @return $this + */ + public function setRepository($repository) + { + if (isset($repository) !== true) { + return $this; + } + + $this->repository = $repository; + + return $this; + } + + /** + * @param $snapshot + * + * @return $this + */ + public function setSnapshot($snapshot) + { + if (isset($snapshot) !== true) { + return $this; + } + + $this->snapshot = $snapshot; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->repository) !== true) { + throw new Exceptions\RuntimeException( + 'repository is required for Restore' + ); + } + if (isset($this->snapshot) !== true) { + throw new Exceptions\RuntimeException( + 'snapshot is required for Restore' + ); + } + $repository = $this->repository; + $snapshot = $this->snapshot; + $uri = "/_snapshot/$repository/$snapshot/_restore"; + + if (isset($repository) === true && isset($snapshot) === true) { + $uri = "/_snapshot/$repository/$snapshot/_restore"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'master_timeout', + 'wait_for_completion', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Status.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Status.php new file mode 100644 index 0000000..b8e6aba --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Snapshot/Status.php @@ -0,0 +1,100 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Status extends AbstractEndpoint +{ + // A comma-separated list of repository names + private $repository; + + // A comma-separated list of snapshot names + private $snapshot; + + /** + * @param $repository + * + * @return $this + */ + public function setRepository($repository) + { + if (isset($repository) !== true) { + return $this; + } + + $this->repository = $repository; + + return $this; + } + + /** + * @param $snapshot + * + * @return $this + */ + public function setSnapshot($snapshot) + { + if (isset($snapshot) !== true) { + return $this; + } + + $this->snapshot = $snapshot; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->snapshot) === true && isset($this->repository) !== true) { + throw new Exceptions\RuntimeException( + 'Repository param must be provided if snapshot param is set' + ); + } + + $repository = $this->repository; + $snapshot = $this->snapshot; + $uri = "/_snapshot/_status"; + + if (isset($repository) === true) { + $uri = "/_snapshot/$repository/_status"; + } elseif (isset($repository) === true && isset($snapshot) === true) { + $uri = "/_snapshot/$repository/$snapshot/_status"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'master_timeout', + 'ignore_unavailable' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Source/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Source/Get.php new file mode 100644 index 0000000..0e8ac26 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Source/Get.php @@ -0,0 +1,78 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for Get' + ); + } + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Get' + ); + } + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for Get' + ); + } + $id = $this->id; + $index = $this->index; + $type = $this->type; + $uri = "/$index/$type/$id/_source"; + + if (isset($index) === true && isset($type) === true && isset($id) === true) { + $uri = "/$index/$type/$id/_source"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'parent', + 'preference', + 'realtime', + 'refresh', + 'routing', + '_source', + '_source_exclude', + '_source_include', + 'version', + 'version_type', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Suggest.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Suggest.php new file mode 100644 index 0000000..1afb1f2 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Suggest.php @@ -0,0 +1,85 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Suggest extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @return string + */ + public function getURI() + { + $index = $this->index; + $uri = "/_suggest"; + + if (isset($index) === true) { + $uri = "/$index/_suggest"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'ignore_unavailable', + 'allow_no_indices', + 'expand_wildcards', + 'preference', + 'routing', + 'source', + ); + } + + /** + * @return array + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + */ + public function getBody() + { + if (isset($this->body) !== true) { + throw new Exceptions\RuntimeException('Body is required for Suggest'); + } + + return $this->body; + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/Cancel.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/Cancel.php new file mode 100644 index 0000000..ff2405b --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/Cancel.php @@ -0,0 +1,71 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Cancel extends AbstractEndpoint +{ + private $taskId; + + /** + * @param string $taskId + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setTaskId($taskId) + { + if (isset($taskId) !== true) { + return $this; + } + + $this->taskId = $taskId; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->id) === true) { + return "/_tasks/{$this->taskId}/_cancel"; + } + + return "/_tasks/_cancel"; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'node_id', + 'actions', + 'parent_node', + 'parent_task', + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/Get.php new file mode 100644 index 0000000..4e7318d --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/Get.php @@ -0,0 +1,68 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + private $taskId; + + /** + * @param string $taskId + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setTaskId($taskId) + { + if (isset($taskId) !== true) { + return $this; + } + + $this->taskId = $taskId; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->taskId) === true) { + return "/_tasks/{$this->taskId}"; + } + + return "/_tasks"; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'wait_for_completion' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/TasksList.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/TasksList.php new file mode 100644 index 0000000..b45f206 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Tasks/TasksList.php @@ -0,0 +1,53 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class TasksList extends AbstractEndpoint +{ + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + return "/_tasks"; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'node_id', + 'actions', + 'detailed', + 'parent_node', + 'parent_task', + 'wait_for_completion', + 'group_by', + 'task_id' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Delete.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Delete.php new file mode 100644 index 0000000..b3593ba --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Delete.php @@ -0,0 +1,51 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Delete extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for Delete' + ); + } + $templateId = $this->id; + $uri = "/_search/template/$templateId"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array(); + } + + /** + * @return string + */ + public function getMethod() + { + return 'DELETE'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Get.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Get.php new file mode 100644 index 0000000..954ecdd --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Get.php @@ -0,0 +1,51 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Get extends AbstractEndpoint +{ + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for Get' + ); + } + $templateId = $this->id; + $uri = "/_search/template/$templateId"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array(); + } + + /** + * @return string + */ + public function getMethod() + { + return 'GET'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Put.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Put.php new file mode 100644 index 0000000..075f413 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Template/Put.php @@ -0,0 +1,68 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Put extends AbstractEndpoint +{ + /** + * @param array $body + * + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for Put' + ); + } + + $templateId = $this->id; + $uri = "/_search/template/$templateId"; + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array(); + } + + /** + * @return string + */ + public function getMethod() + { + return 'PUT'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/TermVectors.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/TermVectors.php new file mode 100644 index 0000000..199491d --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/TermVectors.php @@ -0,0 +1,95 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class TermVectors extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for TermVectors' + ); + } + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for TermVectors' + ); + } + if (isset($this->id) !== true && isset($this->body['doc']) !== true) { + throw new Exceptions\RuntimeException( + 'id or doc is required for TermVectors' + ); + } + + $index = $this->index; + $type = $this->type; + $id = $this->id; + $uri = "/$index/$type/_termvectors"; + + if ($id !== null) { + $uri = "/$index/$type/$id/_termvectors"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'term_statistics', + 'field_statistics', + 'fields', + 'offsets', + 'positions', + 'payloads', + 'preference', + 'routing', + 'parent', + 'realtime' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Update.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Update.php new file mode 100644 index 0000000..9627fee --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Update.php @@ -0,0 +1,99 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Update extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + + return $this; + } + + /** + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + * @return string + */ + public function getURI() + { + if (isset($this->id) !== true) { + throw new Exceptions\RuntimeException( + 'id is required for Update' + ); + } + if (isset($this->index) !== true) { + throw new Exceptions\RuntimeException( + 'index is required for Update' + ); + } + if (isset($this->type) !== true) { + throw new Exceptions\RuntimeException( + 'type is required for Update' + ); + } + $id = $this->id; + $index = $this->index; + $type = $this->type; + $uri = "/$index/$type/$id/_update"; + + if (isset($index) === true && isset($type) === true && isset($id) === true) { + $uri = "/$index/$type/$id/_update"; + } + + return $uri; + } + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return array( + 'consistency', + 'fields', + 'lang', + 'parent', + 'refresh', + 'replication', + 'retry_on_conflict', + 'routing', + 'script', + 'timeout', + 'timestamp', + 'ttl', + 'version', + 'version_type', + '_source' + ); + } + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/UpdateByQuery.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/UpdateByQuery.php new file mode 100644 index 0000000..79a19e7 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/UpdateByQuery.php @@ -0,0 +1,119 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elasticsearch.org + */ +class UpdateByQuery extends AbstractEndpoint +{ + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + if (is_array($body) !== true) { + throw new Exceptions\InvalidArgumentException( + 'Body must be an array' + ); + } + $this->body = $body; + + return $this; + } + + + /** + * @throws \Elasticsearch\Common\Exceptions\BadMethodCallException + * @return string + */ + public function getURI() + { + if (!$this->index) { + throw new Exceptions\RuntimeException( + 'index is required for UpdateByQuery' + ); + } + + $uri = "/{$this->index}/_update_by_query"; + if ($this->type) { + $uri = "/{$this->index}/{$this->type}/_update_by_query"; + } + + return $uri; + } + + + /** + * @return string[] + */ + public function getParamWhitelist() + { + return [ + 'analyzer', + 'analyze_wildcard', + 'default_operator', + 'df', + 'explain', + 'fields', + 'fielddata_fields', + 'from', + 'ignore_unavailable', + 'allow_no_indices', + 'conflicts', + 'expand_wildcards', + 'lenient', + 'lowercase_expanded_terms', + 'preference', + 'q', + 'routing', + 'scroll', + 'search_type', + 'search_timeout', + 'size', + 'sort', + '_source', + '_source_exclude', + '_source_include', + 'terminate_after', + 'stats', + 'suggest_field', + 'suggest_mode', + 'suggest_size', + 'suggest_text', + 'timeout', + 'track_scores', + 'version', + 'version_type', + 'request_cache', + 'refresh', + 'consistency', + 'scroll_size', + 'wait_for_completion', + ]; + } + + + /** + * @return string + */ + public function getMethod() + { + return 'POST'; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Helper/Iterators/SearchHitIterator.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Helper/Iterators/SearchHitIterator.php new file mode 100644 index 0000000..38695c3 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Helper/Iterators/SearchHitIterator.php @@ -0,0 +1,161 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + * @see Iterator + */ +class SearchHitIterator implements Iterator, \Countable +{ + + /** + * @var SearchResponseIterator + */ + private $search_responses; + + /** + * @var int + */ + protected $current_key; + + /** + * @var int + */ + protected $current_hit_index; + + /** + * @var array|null + */ + protected $current_hit_data; + + /** + * @var int + */ + protected $count; + + /** + * Constructor + * + * @param SearchResponseIterator $search_responses + */ + public function __construct(SearchResponseIterator $search_responses) + { + $this->search_responses = $search_responses; + } + + /** + * Rewinds the internal SearchResponseIterator and itself + * + * @return void + * @see Iterator::rewind() + */ + public function rewind() + { + $this->current_key = 0; + $this->search_responses->rewind(); + + // The first page may be empty. In that case, the next page is fetched. + $current_page = $this->search_responses->current(); + if ($this->search_responses->valid() && empty($current_page['hits']['hits'])) { + $this->search_responses->next(); + } + + $this->count = 0; + if (isset($current_page['hits']) && isset($current_page['hits']['total'])) { + $this->count = $current_page['hits']['total']; + } + + $this->readPageData(); + } + + /** + * Advances pointer of the current hit to the next one in the current page. If there + * isn't a next hit in the current page, then it advances the current page and moves the + * pointer to the first hit in the page. + * + * @return void + * @see Iterator::next() + */ + public function next() + { + $this->current_key++; + $this->current_hit_index++; + $current_page = $this->search_responses->current(); + if (isset($current_page['hits']['hits'][$this->current_hit_index])) { + $this->current_hit_data = $current_page['hits']['hits'][$this->current_hit_index]; + } else { + $this->search_responses->next(); + $this->readPageData(); + } + } + + /** + * Returns a boolean indicating whether or not the current pointer has valid data + * + * @return bool + * @see Iterator::valid() + */ + public function valid() + { + return is_array($this->current_hit_data); + } + + /** + * Returns the current hit + * + * @return array + * @see Iterator::current() + */ + public function current() + { + return $this->current_hit_data; + } + + /** + * Returns the current hit index. The hit index spans all pages. + * + * @return int + * @see Iterator::key() + */ + public function key() + { + return $this->current_hit_index; + } + + /** + * Advances the internal SearchResponseIterator and resets the current_hit_index to 0 + * + * @internal + */ + private function readPageData() + { + if ($this->search_responses->valid()) { + $current_page = $this->search_responses->current(); + $this->current_hit_index = 0; + $this->current_hit_data = $current_page['hits']['hits'][$this->current_hit_index]; + } else { + $this->current_hit_data = null; + } + } + + /** + * {@inheritDoc} + */ + public function count() + { + if ($this->count === null) { + $this->rewind(); + } + + return $this->count; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Helper/Iterators/SearchResponseIterator.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Helper/Iterators/SearchResponseIterator.php new file mode 100644 index 0000000..f864422 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Helper/Iterators/SearchResponseIterator.php @@ -0,0 +1,175 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + * @see Iterator + */ +class SearchResponseIterator implements Iterator +{ + + /** + * @var Client + */ + private $client; + + /** + * @var array + */ + private $params; + + /** + * @var int + */ + private $current_key; + + /** + * @var array + */ + private $current_scrolled_response; + + /** + * @var string + */ + private $scroll_id; + + /** + * @var duration + */ + private $scroll_ttl; + + /** + * Constructor + * + * @param Client $client + * @param array $params Associative array of parameters + * @see Client::search() + */ + public function __construct(Client $client, array $search_params) + { + $this->client = $client; + $this->params = $search_params; + + if (isset($search_params['scroll'])) { + $this->scroll_ttl = $search_params['scroll']; + } + } + + /** + * Destructor + */ + public function __destruct() + { + $this->clearScroll(); + } + + /** + * Sets the time to live duration of a scroll window + * + * @param string $time_to_live + * @return $this + */ + public function setScrollTimeout($time_to_live) + { + $this->scroll_ttl = $time_to_live; + return $this; + } + + /** + * Clears the current scroll window if there is a scroll_id stored + * + * @return void + */ + private function clearScroll() + { + if (!empty($this->scroll_id)) { + $this->client->clearScroll( + array( + 'scroll_id' => $this->scroll_id, + 'client' => array( + 'ignore' => 404 + ) + ) + ); + $this->scroll_id = null; + } + } + + /** + * Rewinds the iterator by performing the initial search. + * + * + * @return void + * @see Iterator::rewind() + */ + public function rewind() + { + $this->clearScroll(); + $this->current_key = 0; + $this->current_scrolled_response = $this->client->search($this->params); + $this->scroll_id = $this->current_scrolled_response['_scroll_id']; + } + + /** + * Fetches every "page" after the first one using the lastest "scroll_id" + * + * @return void + * @see Iterator::next() + */ + public function next() + { + if ($this->current_key !== 0) { + $this->current_scrolled_response = $this->client->scroll( + array( + 'scroll_id' => $this->scroll_id, + 'scroll' => $this->scroll_ttl + ) + ); + $this->scroll_id = $this->current_scrolled_response['_scroll_id']; + } + $this->current_key++; + } + + /** + * Returns a boolean value indicating if the current page is valid or not + * + * @return bool + * @see Iterator::valid() + */ + public function valid() + { + return isset($this->current_scrolled_response['hits']['hits'][0]); + } + + /** + * Returns the current "page" + * + * @return array + * @see Iterator::current() + */ + public function current() + { + return $this->current_scrolled_response; + } + + /** + * Returns the current "page number" of the current "page" + * + * @return int + * @see Iterator::key() + */ + public function key() + { + return $this->current_key; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/AbstractNamespace.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/AbstractNamespace.php new file mode 100644 index 0000000..13b2e02 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/AbstractNamespace.php @@ -0,0 +1,77 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +abstract class AbstractNamespace +{ + /** @var \Elasticsearch\Transport */ + protected $transport; + + /** @var callback */ + protected $endpoints; + + /** + * Abstract constructor + * + * @param Transport $transport Transport object + * @param $endpoints + */ + public function __construct($transport, $endpoints) + { + $this->transport = $transport; + $this->endpoints = $endpoints; + } + + /** + * @param array $params + * @param string $arg + * + * @return null|mixed + */ + public function extractArgument(&$params, $arg) + { + if (is_object($params) === true) { + $params = (array) $params; + } + + if (array_key_exists($arg, $params) === true) { + $val = $params[$arg]; + unset($params[$arg]); + + return $val; + } else { + return null; + } + } + + /** + * @param $endpoint AbstractEndpoint + * + * @throws \Exception + * @return array + */ + protected function performRequest(AbstractEndpoint $endpoint) + { + $response = $this->transport->performRequest( + $endpoint->getMethod(), + $endpoint->getURI(), + $endpoint->getParams(), + $endpoint->getBody(), + $endpoint->getOptions() + ); + + return $this->transport->resultOrFuture($response, $endpoint->getOptions()); + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/BooleanRequestWrapper.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/BooleanRequestWrapper.php new file mode 100644 index 0000000..eb9c4cd --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/BooleanRequestWrapper.php @@ -0,0 +1,58 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +trait BooleanRequestWrapper +{ + /** + * Perform Request + * + * @param AbstractEndpoint $endpoint The Endpoint to perform this request against + * + * @throws Missing404Exception + * @throws RoutingMissingException + */ + public static function performRequest(AbstractEndpoint $endpoint, Transport $transport) + { + try { + $response = $transport->performRequest( + $endpoint->getMethod(), + $endpoint->getURI(), + $endpoint->getParams(), + $endpoint->getBody(), + $endpoint->getOptions() + ); + + $response = $transport->resultOrFuture($response, $endpoint->getOptions()); + if (!($response instanceof FutureArrayInterface)) { + if ($response['status'] === 200) { + return true; + } else { + return false; + } + } else { + // async mode, can't easily resolve this...punt to user + return $response; + } + } catch (Missing404Exception $exception) { + return false; + } catch (RoutingMissingException $exception) { + return false; + } + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/CatNamespace.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/CatNamespace.php new file mode 100644 index 0000000..dac0ff6 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/CatNamespace.php @@ -0,0 +1,493 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class CatNamespace extends AbstractNamespace +{ + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function aliases($params = array()) + { + $name = $this->extractArgument($params, 'name'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Aliases $endpoint */ + $endpoint = $endpointBuilder('Cat\Aliases'); + $endpoint->setName($name); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * ['bytes'] = (enum) The unit in which to display byte values + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function allocation($params = array()) + { + $nodeID = $this->extractArgument($params, 'node_id'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Allocation $endpoint */ + $endpoint = $endpointBuilder('Cat\Allocation'); + $endpoint->setNodeID($nodeID); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function count($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Count $endpoint */ + $endpoint = $endpointBuilder('Cat\Count'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * ['ts'] = (bool) Set to false to disable timestamping + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function health($params = array()) + { + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Health $endpoint */ + $endpoint = $endpointBuilder('Cat\Health'); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['help'] = (bool) Return help information + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function help($params = array()) + { + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Help $endpoint */ + $endpoint = $endpointBuilder('Cat\Help'); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * ['bytes'] = (enum) The unit in which to display byte values + * ['pri'] = (bool) Set to true to return stats only for primary shards + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function indices($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Indices $endpoint */ + $endpoint = $endpointBuilder('Cat\Indices'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function master($params = array()) + { + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Master $endpoint */ + $endpoint = $endpointBuilder('Cat\Master'); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function nodes($params = array()) + { + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Nodes $endpoint */ + $endpoint = $endpointBuilder('Cat\Nodes'); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function nodeAttrs($params = array()) + { + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\NodeAttrs $endpoint */ + $endpoint = $endpointBuilder('Cat\NodeAttrs'); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function pendingTasks($params = array()) + { + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\PendingTasks $endpoint */ + $endpoint = $endpointBuilder('Cat\PendingTasks'); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * ['bytes'] = (enum) The unit in which to display byte values + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function recovery($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Recovery $endpoint */ + $endpoint = $endpointBuilder('Cat\Recovery'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function repositories($params = array()) + { + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Repositories $endpoint */ + $endpoint = $endpointBuilder('Cat\Repositories'); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * ['bytes'] = (enum) The unit in which to display byte values + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function shards($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Shards $endpoint */ + $endpoint = $endpointBuilder('Cat\Shards'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * ['bytes'] = (enum) The unit in which to display byte values + * ['repository'] = (string) Name of repository from which to fetch the snapshot information + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function snapshots($params = array()) + { + $repository = $this->extractArgument($params, 'repository'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Snapshots $endpoint */ + $endpoint = $endpointBuilder('Cat\Snapshots'); + $endpoint->setRepository($repository); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * ['full_id'] = (bool) Enables displaying the complete node ids + * ['size'] = (enum) The multiplier in which to display values ([ "", "k", "m", "g", "t", "p" ]) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function threadPool($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\ThreadPool $endpoint */ + $endpoint = $endpointBuilder('Cat\ThreadPool'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * ['bytes'] = (enum) The unit in which to display byte values + * ['fields'] = (list) A comma-separated list of fields to return the fielddata size + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function fielddata($params = array()) + { + $fields = $this->extractArgument($params, 'fields'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Fielddata $endpoint */ + $endpoint = $endpointBuilder('Cat\Fielddata'); + $endpoint->setFields($fields); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function plugins($params = array()) + { + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Plugins $endpoint */ + $endpoint = $endpointBuilder('Cat\Plugins'); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function segments($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Segments $endpoint */ + $endpoint = $endpointBuilder('Cat\Segments'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['format'] = (string) a short version of the Accept header, e.g. json, yaml + * ['node_id'] = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes + * ['format'] = (string) a short version of the Accept header, e.g. json, yaml + * ['actions'] = (list) A comma-separated list of actions that should be returned. Leave empty to return all. + * ['detailed'] = (boolean) Return detailed task information (default: false) + * ['parent_node'] = (string) Return tasks with specified parent node. + * ['parent_task'] = (number) Return tasks with specified parent task id. Set to -1 to return all. + * ['h'] = (list) Comma-separated list of column names to display + * ['help'] = (bool) Return help information + * ['v'] = (bool) Verbose mode. Display column headers + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function tasks($params = array()) + { + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cat\Tasks $endpoint */ + $endpoint = $endpointBuilder('Cat\Tasks'); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/ClusterNamespace.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/ClusterNamespace.php new file mode 100644 index 0000000..01a5002 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/ClusterNamespace.php @@ -0,0 +1,205 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class ClusterNamespace extends AbstractNamespace +{ + /** + * $params['index'] = (string) Limit the information returned to a specific index + * ['level'] = (enum) Specify the level of detail for returned information + * ['local'] = (boolean) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['timeout'] = (time) Explicit operation timeout + * ['wait_for_active_shards'] = (number) Wait until the specified number of shards is active + * ['wait_for_nodes'] = (number) Wait until the specified number of nodes is available + * ['wait_for_relocating_shards'] = (number) Wait until the specified number of relocating shards is finished + * ['wait_for_status'] = (enum) Wait until cluster is in a specific state + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function health($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cluster\Health $endpoint */ + $endpoint = $endpointBuilder('Cluster\Health'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['dry_run'] = (boolean) Simulate the operation only and return the resulting state + * ['filter_metadata'] = (boolean) Don't return cluster state metadata (default: false) + * ['body'] = (boolean) Don't return cluster state metadata (default: false) + * ['explain'] = (boolean) Return an explanation of why the commands can or cannot be executed + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function reroute($params = array()) + { + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cluster\Reroute $endpoint */ + $endpoint = $endpointBuilder('Cluster\Reroute'); + $endpoint->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['filter_blocks'] = (boolean) Do not return information about blocks + * ['filter_index_templates'] = (boolean) Do not return information about index templates + * ['filter_indices'] = (list) Limit returned metadata information to specific indices + * ['filter_metadata'] = (boolean) Do not return information about indices metadata + * ['filter_nodes'] = (boolean) Do not return information about nodes + * ['filter_routing_table'] = (boolean) Do not return information about shard allocation (`routing_table` and `routing_nodes`) + * ['local'] = (boolean) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Specify timeout for connection to master + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function state($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $metric = $this->extractArgument($params, 'metric'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cluster\State $endpoint */ + $endpoint = $endpointBuilder('Cluster\State'); + $endpoint->setParams($params) + ->setIndex($index) + ->setMetric($metric); + + return $this->performRequest($endpoint); + } + + /** + * $params['flat_settings'] = (boolean) Return settings in flat format (default: false) + * ['human'] = (boolean) Whether to return time and byte values in human-readable format. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function stats($params = array()) + { + $nodeID = $this->extractArgument($params, 'node_id'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cluster\Stats $endpoint */ + $endpoint = $endpointBuilder('Cluster\Stats'); + $endpoint->setNodeID($nodeID) + ->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['body'] = () + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function putSettings($params = array()) + { + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cluster\Settings\Put $endpoint */ + $endpoint = $endpointBuilder('Cluster\Settings\Put'); + $endpoint->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * @param array $params + * + * @return array + */ + public function getSettings($params = array()) + { + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cluster\Settings\Put $endpoint */ + $endpoint = $endpointBuilder('Cluster\Settings\Get'); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['master_timeout'] = (time) Specify timeout for connection to master + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function pendingTasks($params = array()) + { + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cluster\PendingTasks $endpoint */ + $endpoint = $endpointBuilder('Cluster\PendingTasks'); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['include_yes_decisions'] = (bool) Return 'YES' decisions in explanation (default: false) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function allocationExplain($params = array()) + { + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cluster\AllocationExplain $endpoint */ + $endpoint = $endpointBuilder('Cluster\AllocationExplain'); + $endpoint->setBody($body) + ->setParams($params); + + return $this->performRequest($endpoint); + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IndicesNamespace.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IndicesNamespace.php new file mode 100644 index 0000000..2ad6b15 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IndicesNamespace.php @@ -0,0 +1,1163 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class IndicesNamespace extends AbstractNamespace +{ + /** + * $params['index'] = (list) A comma-separated list of indices to check (Required) + * + * @param $params array Associative array of parameters + * + * @return boolean + */ + public function exists($params) + { + $index = $this->extractArgument($params, 'index'); + + //manually make this verbose so we can check status code + $params['client']['verbose'] = true; + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Exists $endpoint */ + $endpoint = $endpointBuilder('Indices\Exists'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return BooleanRequestWrapper::performRequest($endpoint, $this->transport); + } + + /** + * $params['index'] = (list) A comma-separated list of indices to check (Required) + * ['feature'] = (list) A comma-separated list of features to return + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * ['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * + * @param $params array Associative array of parameters + * + * @return bool + */ + public function get($params) + { + $index = $this->extractArgument($params, 'index'); + $feature = $this->extractArgument($params, 'feature'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Get $endpoint */ + $endpoint = $endpointBuilder('Indices\Get'); + $endpoint->setIndex($index) + ->setFeature($feature) + ->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + * ['operation_threading'] = () TODO: ? + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function segments($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Segments $endpoint */ + $endpoint = $endpointBuilder('Indices\Segments'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['name'] = (string) The name of the template (Required) + * ['timeout'] = (time) Explicit operation timeout + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function deleteTemplate($params) + { + $name = $this->extractArgument($params, 'name'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Template\Delete $endpoint */ + $endpoint = $endpointBuilder('Indices\Template\Delete'); + $endpoint->setName($name); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of indices to delete; use `_all` or empty string to delete all indices + * ['timeout'] = (time) Explicit operation timeout + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function delete($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Delete $endpoint */ + $endpoint = $endpointBuilder('Indices\Delete'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['fields'] = (boolean) A comma-separated list of fields for `fielddata` metric (supports wildcards) + * ['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + * ['indexing_types'] = (list) A comma-separated list of document types to include in the `indexing` statistics + * ['metric_family'] = (enum) Limit the information returned to a specific metric + * ['search_groups'] = (list) A comma-separated list of search groups to include in the `search` statistics + * ['all'] = (boolean) Return all available information + * ['clear'] = (boolean) Reset the default level of detail + * ['docs'] = (boolean) Return information about indexed and deleted documents + * ['fielddata'] = (boolean) Return information about field data + * ['filter_cache'] = (boolean) Return information about filter cache + * ['flush'] = (boolean) Return information about flush operations + * ['get'] = (boolean) Return information about get operations + * ['groups'] = (boolean) A comma-separated list of search groups for `search` statistics + * ['id_cache'] = (boolean) Return information about ID cache + * ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones + * ['indexing'] = (boolean) Return information about indexing operations + * ['merge'] = (boolean) Return information about merge operations + * ['refresh'] = (boolean) Return information about refresh operations + * ['search'] = (boolean) Return information about search operations; use the `groups` parameter to include information for specific search groups + * ['store'] = (boolean) Return information about the size of the index + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function stats($params = array()) + { + $metric = $this->extractArgument($params, 'metric'); + + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Stats $endpoint */ + $endpoint = $endpointBuilder('Indices\Stats'); + $endpoint->setIndex($index) + ->setMetric($metric); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + * ['body'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function putSettings($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Settings\Put $endpoint */ + $endpoint = $endpointBuilder('Indices\Settings\Put'); + $endpoint->setIndex($index) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function snapshotIndex($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Gateway\Snapshot $endpoint */ + $endpoint = $endpointBuilder('Indices\Gateway\Snapshot'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (string) The name of the source index to shrink + * ['target'] = (string) The name of the target index to shrink into + * ['timeout'] = (time) Explicit operation timeout + * ['master_timeout'] = (time) Specify timeout for connection to master + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function shrink($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $target = $this->extractArgument($params, 'target'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Shrink $endpoint */ + $endpoint = $endpointBuilder('Indices\Shrink'); + $endpoint->setIndex($index) + ->setTarget($target); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + * ['type'] = (list) A comma-separated list of document types + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function getMapping($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + $type = $this->extractArgument($params, 'type'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Mapping\Get $endpoint */ + $endpoint = $endpointBuilder('Indices\Mapping\Get'); + $endpoint->setIndex($index) + ->setType($type); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + * ['type'] = (list) A comma-separated list of document types + * ['field'] = (list) A comma-separated list of document fields + * ['include_defaults'] = (bool) specifies default mapping values should be returned + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function getFieldMapping($params = array()) + { + $index = $this->extractArgument($params, 'index'); + $type = $this->extractArgument($params, 'type'); + $fields = $this->extractArgument($params, 'fields'); + + if (!isset($fields)) { + $fields = $this->extractArgument($params, 'field'); + } + + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Mapping\GetField $endpoint */ + $endpoint = $endpointBuilder('Indices\Mapping\GetField'); + $endpoint->setIndex($index) + ->setType($type) + ->setFields($fields); + + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + * ['force'] = (boolean) TODO: ? + * ['full'] = (boolean) TODO: ? + * ['refresh'] = (boolean) Refresh the index after performing the operation + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function flush($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Flush $endpoint */ + $endpoint = $endpointBuilder('Indices\Flush'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + * ['force'] = (boolean) TODO: ? + * ['full'] = (boolean) TODO: ? + * ['refresh'] = (boolean) Refresh the index after performing the operation + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function flushSynced($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Flush $endpoint */ + $endpoint = $endpointBuilder('Indices\Flush'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + $endpoint->setSynced(true); + + return $this->performRequest($endpoint); + } + + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + * ['operation_threading'] = () TODO: ? + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function refresh($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Refresh $endpoint */ + $endpoint = $endpointBuilder('Indices\Refresh'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + * ['detailed'] = (bool) Whether to display detailed information about shard recovery + * ['active_only'] = (bool) Display only those recoveries that are currently on-going + * ['human'] = (bool) Whether to return time and byte values in human-readable format. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function recovery($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Flush $endpoint */ + $endpoint = $endpointBuilder('Indices\Recovery'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` to check the types across all indices (Required) + * ['type'] = (list) A comma-separated list of document types to check (Required) + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return boolean + */ + public function existsType($params) + { + $index = $this->extractArgument($params, 'index'); + + $type = $this->extractArgument($params, 'type'); + + //manually make this verbose so we can check status code + $params['client']['verbose'] = true; + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Type\Exists $endpoint */ + $endpoint = $endpointBuilder('Indices\Type\Exists'); + $endpoint->setIndex($index) + ->setType($type); + $endpoint->setParams($params); + + return BooleanRequestWrapper::performRequest($endpoint, $this->transport); + } + + /** + * $params['index'] = (string) The name of the index with an alias + * ['name'] = (string) The name of the alias to be created or updated + * ['timeout'] = (time) Explicit timestamp for the document + * ['body'] = (time) Explicit timestamp for the document + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function putAlias($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + $name = $this->extractArgument($params, 'name'); + + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Alias\Put $endpoint */ + $endpoint = $endpointBuilder('Indices\Alias\Put'); + $endpoint->setIndex($index) + ->setName($name) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['name'] = (string) The name of the template (Required) + * ['order'] = (number) The order for this template when merging multiple matching ones (higher numbers are merged later, overriding the lower numbers) + * ['timeout'] = (time) Explicit operation timeout + * ['body'] = (time) Explicit operation timeout + * ['create'] = (bool) Whether the index template should only be added if new or can also replace an existing one + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function putTemplate($params) + { + $name = $this->extractArgument($params, 'name'); + + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Template\Put $endpoint */ + $endpoint = $endpointBuilder('Indices\Template\Put'); + $endpoint->setName($name) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names to restrict the operation; use `_all` or empty string to perform the operation on all indices + * ['type'] = (list) A comma-separated list of document types to restrict the operation; leave empty to perform the operation on all types + * ['explain'] = (boolean) Return detailed information about the error + * ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones + * ['operation_threading'] = () TODO: ? + * ['source'] = (string) The URL-encoded query definition (instead of using the request body) + * ['body'] = (string) The URL-encoded query definition (instead of using the request body) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function validateQuery($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + $type = $this->extractArgument($params, 'type'); + + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Validate\Query $endpoint */ + $endpoint = $endpointBuilder('Indices\Validate\Query'); + $endpoint->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['name'] = (list) A comma-separated list of alias names to return (Required) + * ['index'] = (list) A comma-separated list of index names to filter aliases + * ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones + * ['name'] = (list) A comma-separated list of alias names to return + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function getAlias($params) + { + $index = $this->extractArgument($params, 'index'); + + $name = $this->extractArgument($params, 'name'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Alias\Get $endpoint */ + $endpoint = $endpointBuilder('Indices\Alias\Get'); + $endpoint->setIndex($index) + ->setName($name); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` to perform the operation on all indices (Required) + * ['type'] = (string) The name of the document type + * ['ignore_conflicts'] = (boolean) Specify whether to ignore conflicts while updating the mapping (default: false) + * ['timeout'] = (time) Explicit operation timeout + * ['body'] = (time) Explicit operation timeout + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function putMapping($params) + { + $index = $this->extractArgument($params, 'index'); + + $type = $this->extractArgument($params, 'type'); + + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Mapping\Put $endpoint */ + $endpoint = $endpointBuilder('Indices\Mapping\Put'); + $endpoint->setIndex($index) + ->setType($type) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` for all indices (Required) + * ['type'] = (string) The name of the document type to delete (Required) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function deleteMapping($params) + { + $index = $this->extractArgument($params, 'index'); + + $type = $this->extractArgument($params, 'type'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Mapping\Delete $endpoint */ + $endpoint = $endpointBuilder('Indices\Mapping\Delete'); + $endpoint->setIndex($index) + ->setType($type); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['name'] = (string) The name of the template (Required) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function getTemplate($params) + { + $name = $this->extractArgument($params, 'name'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Template\Get $endpoint */ + $endpoint = $endpointBuilder('Indices\Template\Get'); + $endpoint->setName($name); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['name'] = (string) The name of the template (Required) + * + * @param $params array Associative array of parameters + * + * @return boolean + */ + public function existsTemplate($params) + { + $name = $this->extractArgument($params, 'name'); + + //manually make this verbose so we can check status code + $params['client']['verbose'] = true; + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Template\Exists $endpoint */ + $endpoint = $endpointBuilder('Indices\Template\Exists'); + $endpoint->setName($name); + $endpoint->setParams($params); + + return BooleanRequestWrapper::performRequest($endpoint, $this->transport); + } + + /** + * $params['index'] = (string) The name of the index (Required) + * ['timeout'] = (time) Explicit operation timeout + * ['body'] = (time) Explicit operation timeout + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function create($params) + { + $index = $this->extractArgument($params, 'index'); + + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Create $endpoint */ + $endpoint = $endpointBuilder('Indices\Create'); + $endpoint->setIndex($index) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + * ['flush'] = (boolean) Specify whether the index should be flushed after performing the operation (default: true) + * ['max_num_segments'] = (number) The number of segments the index should be merged into (default: dynamic) + * ['only_expunge_deletes'] = (boolean) Specify whether the operation should only expunge deleted documents + * ['operation_threading'] = () TODO: ? + * ['refresh'] = (boolean) Specify whether the index should be refreshed after performing the operation (default: true) + * ['wait_for_merge'] = (boolean) Specify whether the request should block until the merge process is finished (default: true) + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function forceMerge($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\ForceMerge $endpoint */ + $endpoint = $endpointBuilder('Indices\ForceMerge'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (string) The name of the index with an alias (Required) + * ['name'] = (string) The name of the alias to be deleted (Required) + * ['timeout'] = (time) Explicit timestamp for the document + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function deleteAlias($params) + { + $index = $this->extractArgument($params, 'index'); + + $name = $this->extractArgument($params, 'name'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Alias\Delete $endpoint */ + $endpoint = $endpointBuilder('Indices\Alias\Delete'); + $endpoint->setIndex($index) + ->setName($name); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (string) The name of the index (Required) + * ['timeout'] = (time) Explicit operation timeout + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function open($params) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Open $endpoint */ + $endpoint = $endpointBuilder('Indices\Open'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (string) The name of the index to scope the operation + * ['analyzer'] = (string) The name of the analyzer to use + * ['field'] = (string) Use the analyzer configured for this field (instead of passing the analyzer name) + * ['filter'] = (list) A comma-separated list of filters to use for the analysis + * ['prefer_local'] = (boolean) With `true`, specify that a local shard should be used if available, with `false`, use a random shard (default: true) + * ['text'] = (string) The text on which the analysis should be performed (when request body is not used) + * ['tokenizer'] = (string) The name of the tokenizer to use for the analysis + * ['format'] = (enum) Format of the output + * ['body'] = (enum) Format of the output + * ['char_filter'] = (list) A comma-separated list of character filters to use for the analysis + * ['explain'] = (bool) With `true`, outputs more advanced details. (default: false) + * ['attributes'] = (list) A comma-separated list of token attributes to output, this parameter works only with `explain=true` + * ['format'] = (enum) Format of the output (["detailed", "text"]) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function analyze($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Analyze $endpoint */ + $endpoint = $endpointBuilder('Indices\Analyze'); + $endpoint->setIndex($index) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index name to limit the operation + * ['field_data'] = (boolean) Clear field data + * ['fielddata'] = (boolean) Clear field data + * ['fields'] = (list) A comma-separated list of fields to clear when using the `field_data` parameter (default: all) + * ['filter'] = (boolean) Clear filter caches + * ['filter_cache'] = (boolean) Clear filter caches + * ['filter_keys'] = (boolean) A comma-separated list of keys to clear when using the `filter_cache` parameter (default: all) + * ['id'] = (boolean) Clear ID caches for parent/child + * ['id_cache'] = (boolean) Clear ID caches for parent/child + * ['recycler'] = (boolean) Clear the recycler cache + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function clearCache($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Cache\Clear $endpoint */ + $endpoint = $endpointBuilder('Indices\Cache\Clear'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names to filter aliases + * ['timeout'] = (time) Explicit timestamp for the document + * ['body'] = (time) Explicit timestamp for the document + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function updateAliases($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Aliases\Update $endpoint */ + $endpoint = $endpointBuilder('Indices\Aliases\Update'); + $endpoint->setIndex($index) + ->setBody($body); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['local'] = (bool) Return local information, do not retrieve the state from master node (default: false) + * ['timeout'] = (time) Explicit timestamp for the document + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function getAliases($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + $name = $this->extractArgument($params, 'name'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Aliases\Get $endpoint */ + $endpoint = $endpointBuilder('Indices\Aliases\Get'); + $endpoint->setIndex($index) + ->setName($name); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['name'] = (list) A comma-separated list of alias names to return (Required) + * ['index'] = (list) A comma-separated list of index names to filter aliases + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return boolean + */ + public function existsAlias($params) + { + $index = $this->extractArgument($params, 'index'); + + $name = $this->extractArgument($params, 'name'); + + //manually make this verbose so we can check status code + $params['client']['verbose'] = true; + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Alias\Exists $endpoint */ + $endpoint = $endpointBuilder('Indices\Alias\Exists'); + $endpoint->setIndex($index) + ->setName($name); + $endpoint->setParams($params); + + return BooleanRequestWrapper::performRequest($endpoint, $this->transport); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + * ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones + * ['operation_threading'] = () TODO: ? + * ['recovery'] = (boolean) Return information about shard recovery + * ['snapshot'] = (boolean) TODO: ? + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function status($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Status $endpoint */ + $endpoint = $endpointBuilder('Indices\Status'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function getSettings($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + $name = $this->extractArgument($params, 'name'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Settings\Get $endpoint */ + $endpoint = $endpointBuilder('Indices\Settings\Get'); + $endpoint->setIndex($index) + ->setName($name); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (string) The name of the index (Required) + * ['timeout'] = (time) Explicit operation timeout + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function close($params) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Close $endpoint */ + $endpoint = $endpointBuilder('Indices\Close'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (string) The name of the index + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function seal($params) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Seal $endpoint */ + $endpoint = $endpointBuilder('Indices\Seal'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + * ['wait_for_completion']= (boolean) Specify whether the request should block until the all segments are upgraded (default: false) + * ['only_ancient_segments'] = (boolean) If true, only ancient (an older Lucene major release) segments will be upgraded + * ['refresh'] = (boolean) Refresh the index after performing the operation + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function upgrade($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Upgrade\Post $endpoint */ + $endpoint = $endpointBuilder('Indices\Upgrade\Post'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (list) A comma-separated list of index names; use `_all` or empty string for all indices + * ['wait_for_completion']= (boolean) Specify whether the request should block until the all segments are upgraded (default: false) + * ['only_ancient_segments'] = (boolean) If true, only ancient (an older Lucene major release) segments will be upgraded + * ['refresh'] = (boolean) Refresh the index after performing the operation + * ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function getUpgrade($params = array()) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Upgrade\Get $endpoint */ + $endpoint = $endpointBuilder('Indices\Upgrade\Get'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['index'] = (string) A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices + * ['status'] = (list) A comma-separated list of statuses used to filter on shards to get store information for + * ['ignore_unavailable'] = (boolean) Whether specified concrete indices should be ignored when unavailable (missing or closed) + * ['allow_no_indices'] = (boolean) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + * ['expand_wildcards'] = (boolean) Whether to expand wildcard expression to concrete indices that are open, closed or both. + * ['operation_threading'] + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function shardStores($params) + { + $index = $this->extractArgument($params, 'index'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\ShardStores $endpoint */ + $endpoint = $endpointBuilder('Indices\ShardStores'); + $endpoint->setIndex($index); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['newIndex'] = (string) The name of the rollover index + * ['alias'] = (string) The name of the alias to rollover + * ['timeout'] = (time) Explicit operation timeout + * ['master_timeout'] = (time) Specify timeout for connection to master + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function rollover($params) + { + $newIndex = $this->extractArgument($params, 'newIndex'); + $alias = $this->extractArgument($params, 'alias'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Indices\Rollover $endpoint */ + $endpoint = $endpointBuilder('Indices\Rollover'); + $endpoint->setNewIndex($newIndex) + ->setAlias($alias) + ->setParams($params) + ->setBody($body); + + return $this->performRequest($endpoint); + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IngestNamespace.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IngestNamespace.php new file mode 100644 index 0000000..c14313b --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IngestNamespace.php @@ -0,0 +1,114 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class IngestNamespace extends AbstractNamespace +{ + /** + * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['timeout'] = (time) Explicit operation timeout + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function deletePipeline($params = array()) + { + $id = $this->extractArgument($params, 'id'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var Delete $endpoint */ + $endpoint = $endpointBuilder('Ingest\Pipeline\Delete'); + $endpoint->setID($id); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function getPipeline($params = array()) + { + $id = $this->extractArgument($params, 'id'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var Get $endpoint */ + $endpoint = $endpointBuilder('Ingest\Pipeline\Get'); + $endpoint->setID($id); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['timeout'] = (time) Explicit operation timeout + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function putPipeline($params = array()) + { + $body = $this->extractArgument($params, 'body'); + $id = $this->extractArgument($params, 'id'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var Put $endpoint */ + $endpoint = $endpointBuilder('Ingest\Pipeline\Put'); + $endpoint->setID($id) + ->setBody($body) + ->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['verbose'] = (bool) Verbose mode. Display data output for each processor in executed pipeline + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function simulate($params = array()) + { + $body = $this->extractArgument($params, 'body'); + $id = $this->extractArgument($params, 'id'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var Simulate $endpoint */ + $endpoint = $endpointBuilder('Ingest\Simulate'); + $endpoint->setID($id) + ->setBody($body) + ->setParams($params); + + return $this->performRequest($endpoint); + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NamespaceBuilderInterface.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NamespaceBuilderInterface.php new file mode 100644 index 0000000..7171dbb --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NamespaceBuilderInterface.php @@ -0,0 +1,37 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ + +namespace Elasticsearch\Namespaces; + + +use Elasticsearch\Serializers\SerializerInterface; +use Elasticsearch\Transport; + +interface NamespaceBuilderInterface +{ + /** + * Returns the name of the namespace. This is what users will call, e.g. the name + * "foo" will be invoked by the user as `$client->foo()` + * @return string + */ + public function getName(); + + /** + * Returns the actual namespace object which contains your custom methods. The transport + * and serializer objects are provided so that your namespace may do whatever custom + * logic is required. + * + * @param Transport $transport + * @param SerializerInterface $serializer + * @return Object + */ + public function getObject(Transport $transport, SerializerInterface $serializer); +} \ No newline at end of file diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NodesNamespace.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NodesNamespace.php new file mode 100644 index 0000000..e8bbaf9 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NodesNamespace.php @@ -0,0 +1,134 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class NodesNamespace extends AbstractNamespace +{ + /** + * $params['fields'] = (list) A comma-separated list of fields for `fielddata` metric (supports wildcards) + * ['metric_family'] = (enum) Limit the information returned to a certain metric family + * ['metric'] = (enum) Limit the information returned for `indices` family to a specific metric + * ['node_id'] = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes + * ['all'] = (boolean) Return all available information + * ['clear'] = (boolean) Reset the default level of detail + * ['fs'] = (boolean) Return information about the filesystem + * ['http'] = (boolean) Return information about HTTP + * ['indices'] = (boolean) Return information about indices + * ['jvm'] = (boolean) Return information about the JVM + * ['network'] = (boolean) Return information about network + * ['os'] = (boolean) Return information about the operating system + * ['process'] = (boolean) Return information about the Elasticsearch process + * ['thread_pool'] = (boolean) Return information about the thread pool + * ['transport'] = (boolean) Return information about transport + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function stats($params = array()) + { + $nodeID = $this->extractArgument($params, 'node_id'); + + $metric = $this->extractArgument($params, 'metric'); + + $index_metric = $this->extractArgument($params, 'index_metric'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cluster\Nodes\Stats $endpoint */ + $endpoint = $endpointBuilder('Cluster\Nodes\Stats'); + $endpoint->setNodeID($nodeID) + ->setMetric($metric) + ->setIndexMetric($index_metric) + ->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['node_id'] = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes + * ['metric'] = (list) A comma-separated list of metrics you wish returned. Leave empty to return all. + * ['flat_settings'] = (boolean) Return settings in flat format (default: false) + * ['human'] = (boolean) Whether to return time and byte values in human-readable format. + + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function info($params = array()) + { + $nodeID = $this->extractArgument($params, 'node_id'); + $metric = $this->extractArgument($params, 'metric'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cluster\Nodes\Info $endpoint */ + $endpoint = $endpointBuilder('Cluster\Nodes\Info'); + $endpoint->setNodeID($nodeID)->setMetric($metric); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['node_id'] = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes + * ['interval'] = (time) The interval for the second sampling of threads + * ['snapshots'] = (number) Number of samples of thread stacktrace (default: 10) + * ['threads'] = (number) Specify the number of threads to provide information for (default: 3) + * ['type'] = (enum) The type to sample (default: cpu) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function hotThreads($params = array()) + { + $nodeID = $this->extractArgument($params, 'node_id'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cluster\Nodes\HotThreads $endpoint */ + $endpoint = $endpointBuilder('Cluster\Nodes\HotThreads'); + $endpoint->setNodeID($nodeID); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['node_id'] = (list) A comma-separated list of node IDs or names to perform the operation on; use `_local` to perform the operation on the node you're connected to, leave empty to perform the operation on all nodes + * ['delay'] = (time) Set the delay for the operation (default: 1s) + * ['exit'] = (boolean) Exit the JVM as well (default: true) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function shutdown($params = array()) + { + $nodeID = $this->extractArgument($params, 'node_id'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Cluster\Nodes\Shutdown $endpoint */ + $endpoint = $endpointBuilder('Cluster\Nodes\Shutdown'); + $endpoint->setNodeID($nodeID); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/SnapshotNamespace.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/SnapshotNamespace.php new file mode 100644 index 0000000..6f22d94 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/SnapshotNamespace.php @@ -0,0 +1,235 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class SnapshotNamespace extends AbstractNamespace +{ + /** + * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['wait_for_completion'] = (bool) Should this request wait until the operation has completed before returning + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function create($params = array()) + { + $repository = $this->extractArgument($params, 'repository'); + $snapshot = $this->extractArgument($params, 'snapshot'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Snapshot\Create $endpoint */ + $endpoint = $endpointBuilder('Snapshot\Create'); + $endpoint->setRepository($repository) + ->setSnapshot($snapshot) + ->setParams($params) + ->setBody($body); + + return $this->performRequest($endpoint); + } + + /** + * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['timeout'] = (time) Explicit operation timeout + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function createRepository($params = array()) + { + $repository = $this->extractArgument($params, 'repository'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Snapshot\Repository\Create $endpoint */ + $endpoint = $endpointBuilder('Snapshot\Repository\Create'); + $endpoint->setRepository($repository) + ->setBody($body) + ->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function delete($params = array()) + { + $repository = $this->extractArgument($params, 'repository'); + $snapshot = $this->extractArgument($params, 'snapshot'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Snapshot\Delete $endpoint */ + $endpoint = $endpointBuilder('Snapshot\Delete'); + $endpoint->setRepository($repository) + ->setSnapshot($snapshot) + ->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['timeout'] = (time) Explicit operation timeout + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function deleteRepository($params = array()) + { + $repository = $this->extractArgument($params, 'repository'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Snapshot\Repository\Delete $endpoint */ + $endpoint = $endpointBuilder('Snapshot\Repository\Delete'); + $endpoint->setRepository($repository) + ->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function get($params = array()) + { + $repository = $this->extractArgument($params, 'repository'); + $snapshot = $this->extractArgument($params, 'snapshot'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Snapshot\Get $endpoint */ + $endpoint = $endpointBuilder('Snapshot\Get'); + $endpoint->setRepository($repository) + ->setSnapshot($snapshot) + ->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['timeout'] = (time) Explicit operation timeout + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function getRepository($params = array()) + { + $repository = $this->extractArgument($params, 'repository'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Snapshot\Repository\Get $endpoint */ + $endpoint = $endpointBuilder('Snapshot\Repository\Get'); + $endpoint->setRepository($repository) + ->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['wait_for_completion'] = (bool) Should this request wait until the operation has completed before returning + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function restore($params = array()) + { + $repository = $this->extractArgument($params, 'repository'); + $snapshot = $this->extractArgument($params, 'snapshot'); + $body = $this->extractArgument($params, 'body'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Snapshot\Restore $endpoint */ + $endpoint = $endpointBuilder('Snapshot\Restore'); + $endpoint->setRepository($repository) + ->setSnapshot($snapshot) + ->setParams($params) + ->setBody($body); + + return $this->performRequest($endpoint); + } + + /** + * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function status($params = array()) + { + $repository = $this->extractArgument($params, 'repository'); + $snapshot = $this->extractArgument($params, 'snapshot'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Snapshot\Status $endpoint */ + $endpoint = $endpointBuilder('Snapshot\Status'); + $endpoint->setRepository($repository) + ->setSnapshot($snapshot) + ->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['master_timeout'] = (time) Explicit operation timeout for connection to master node + * ['timeout'] = (time) Explicit operation timeout + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function verifyRepository($params = array()) + { + $repository = $this->extractArgument($params, 'repository'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var \Elasticsearch\Endpoints\Snapshot\Repository\Verify $endpoint */ + $endpoint = $endpointBuilder('Snapshot\Repository\Verify'); + $endpoint->setRepository($repository) + ->setParams($params); + + return $this->performRequest($endpoint); + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/TasksNamespace.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/TasksNamespace.php new file mode 100644 index 0000000..6782292 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/TasksNamespace.php @@ -0,0 +1,91 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class TasksNamespace extends AbstractNamespace +{ + /** + * $params['wait_for_completion'] = (bool) Wait for the matching tasks to complete (default: false) + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function get($params = array()) + { + $id = $this->extractArgument($params, 'task_id'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var Get $endpoint */ + $endpoint = $endpointBuilder('Tasks\Get'); + $endpoint->setTaskId($id) + ->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['node_id'] = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes + * ['actions'] = (list) A comma-separated list of actions that should be cancelled. Leave empty to cancel all. + * ['parent_node'] = (string) Cancel tasks with specified parent node + * ['parent_task'] = (string) Cancel tasks with specified parent task id (node_id:task_number). Set to -1 to cancel all. + * ['detailed'] = (bool) Return detailed task information (default: false) + * ['wait_for_completion'] = (bool) Wait for the matching tasks to complete (default: false) + * ['group_by'] = (enum) Group tasks by nodes or parent/child relationships + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function tasksList($params = array()) + { + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var Get $endpoint */ + $endpoint = $endpointBuilder('Tasks\TasksList'); + $endpoint->setParams($params); + + return $this->performRequest($endpoint); + } + + /** + * $params['node_id'] = (list) A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes + * ['actions'] = (list) A comma-separated list of actions that should be cancelled. Leave empty to cancel all. + * ['parent_node'] = (string) Cancel tasks with specified parent node + * ['parent_task'] = (string) Cancel tasks with specified parent task id (node_id:task_number). Set to -1 to cancel all. + * + * @param $params array Associative array of parameters + * + * @return array + */ + public function cancel($params = array()) + { + $id = $this->extractArgument($params, 'id'); + + /** @var callback $endpointBuilder */ + $endpointBuilder = $this->endpoints; + + /** @var Cancel $endpoint */ + $endpoint = $endpointBuilder('Tasks\Cancel'); + $endpoint->setTaskId($id) + ->setParams($params); + + return $this->performRequest($endpoint); + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/ArrayToJSONSerializer.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/ArrayToJSONSerializer.php new file mode 100644 index 0000000..1290b6b --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/ArrayToJSONSerializer.php @@ -0,0 +1,49 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class ArrayToJSONSerializer implements SerializerInterface +{ + /** + * Serialize assoc array into JSON string + * + * @param string|array $data Assoc array to encode into JSON + * + * @return string + */ + public function serialize($data) + { + if (is_string($data) === true) { + return $data; + } else { + $data = json_encode($data, JSON_PRESERVE_ZERO_FRACTION); + if ($data === '[]') { + return '{}'; + } else { + return $data; + } + } + } + + /** + * Deserialize JSON into an assoc array + * + * @param string $data JSON encoded string + * @param array $headers Response Headers + * + * @return array + */ + public function deserialize($data, $headers) + { + return json_decode($data, true); + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/EverythingToJSONSerializer.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/EverythingToJSONSerializer.php new file mode 100644 index 0000000..b93b1bb --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/EverythingToJSONSerializer.php @@ -0,0 +1,45 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class EverythingToJSONSerializer implements SerializerInterface +{ + /** + * Serialize assoc array into JSON string + * + * @param string|array $data Assoc array to encode into JSON + * + * @return string + */ + public function serialize($data) + { + $data = json_encode($data, JSON_PRESERVE_ZERO_FRACTION); + if ($data === '[]') { + return '{}'; + } else { + return $data; + } + } + + /** + * Deserialize JSON into an assoc array + * + * @param string $data JSON encoded string + * @param array $headers Response headers + * + * @return array + */ + public function deserialize($data, $headers) + { + return json_decode($data, true); + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SerializerInterface.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SerializerInterface.php new file mode 100644 index 0000000..a237963 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SerializerInterface.php @@ -0,0 +1,34 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +interface SerializerInterface +{ + /** + * Serialize a complex data-structure into a json encoded string + * + * @param mixed The data to encode + * + * @return string + */ + public function serialize($data); + + /** + * Deserialize json encoded string into an associative array + * + * @param string $data JSON encoded string + * @param array $headers Response Headers + * + * @return array + */ + public function deserialize($data, $headers); +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SmartSerializer.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SmartSerializer.php new file mode 100644 index 0000000..3b5181d --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SmartSerializer.php @@ -0,0 +1,99 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class SmartSerializer implements SerializerInterface +{ + private $PHP_VERSION; + + public function __construct() + { + $this->PHP_VERSION = phpversion(); + } + + /** + * Serialize assoc array into JSON string + * + * @param string|array $data Assoc array to encode into JSON + * + * @return string + */ + public function serialize($data) + { + if (is_string($data) === true) { + return $data; + } else { + if (version_compare($this->PHP_VERSION, '5.6.6', '<') || ! defined('JSON_PRESERVE_ZERO_FRACTION')) { + $data = json_encode($data); + } else { + $data = json_encode($data, JSON_PRESERVE_ZERO_FRACTION); + } + if ($data === '[]') { + return '{}'; + } else { + return $data; + } + } + } + + /** + * Deserialize by introspecting content_type. Tries to deserialize JSON, + * otherwise returns string + * + * @param string $data JSON encoded string + * @param array $headers Response Headers + * + * @throws JsonErrorException + * @return array + */ + public function deserialize($data, $headers) + { + if (isset($headers['content_type']) === true) { + if (strpos($headers['content_type'], 'json') !== false) { + return $this->decode($data); + } else { + //Not json, return as string + return $data; + } + } else { + //No content headers, assume json + return $this->decode($data); + } + } + + /** + * @todo For 2.0, remove the E_NOTICE check before raising the exception. + * + * @param $data + * + * @return array + * @throws JsonErrorException + */ + private function decode($data) + { + if ($data === null || strlen($data) === 0) { + return ""; + } + + $result = @json_decode($data, true); + + // Throw exception only if E_NOTICE is on to maintain backwards-compatibility on systems that silently ignore E_NOTICEs. + if (json_last_error() !== JSON_ERROR_NONE && (error_reporting() & E_NOTICE) === E_NOTICE) { + $e = new JsonErrorException(json_last_error(), $data, $result); + throw $e; + } + + return $result; + } +} diff --git a/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Transport.php b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Transport.php new file mode 100644 index 0000000..07323f7 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/src/Elasticsearch/Transport.php @@ -0,0 +1,172 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ +class Transport +{ + /** + * @var AbstractConnectionPool + */ + public $connectionPool; + + /** + * @var LoggerInterface + */ + private $log; + + /** @var int */ + public $retryAttempts = 0; + + /** @var Connection */ + public $lastConnection; + + /** @var int */ + public $retries; + + /** + * Transport class is responsible for dispatching requests to the + * underlying cluster connections + * + * @param $retries + * @param bool $sniffOnStart + * @param ConnectionPool\AbstractConnectionPool $connectionPool + * @param \Psr\Log\LoggerInterface $log Monolog logger object + */ + public function __construct($retries, $sniffOnStart = false, AbstractConnectionPool $connectionPool, LoggerInterface $log) + { + $this->log = $log; + $this->connectionPool = $connectionPool; + $this->retries = $retries; + + if ($sniffOnStart === true) { + $this->log->notice('Sniff on Start.'); + $this->connectionPool->scheduleCheck(); + } + } + + /** + * Returns a single connection from the connection pool + * Potentially performs a sniffing step before returning + * + * @return ConnectionInterface Connection + */ + + public function getConnection() + { + return $this->connectionPool->nextConnection(); + } + + /** + * Perform a request to the Cluster + * + * @param string $method HTTP method to use + * @param string $uri HTTP URI to send request to + * @param null $params Optional query parameters + * @param null $body Optional query body + * @param array $options + * + * @throws Common\Exceptions\NoNodesAvailableException|\Exception + * @return FutureArrayInterface + */ + public function performRequest($method, $uri, $params = null, $body = null, $options = []) + { + try { + $connection = $this->getConnection(); + } catch (Exceptions\NoNodesAvailableException $exception) { + $this->log->critical('No alive nodes found in cluster'); + throw $exception; + } + + $response = array(); + $caughtException = null; + $this->lastConnection = $connection; + + $future = $connection->performRequest( + $method, + $uri, + $params, + $body, + $options, + $this + ); + + $future->promise()->then( + //onSuccess + function ($response) { + $this->retryAttempts = 0; + // Note, this could be a 4xx or 5xx error + }, + //onFailure + function ($response) { + //some kind of real faiure here, like a timeout + $this->connectionPool->scheduleCheck(); + // log stuff + }); + + return $future; + } + + /** + * @param FutureArrayInterface $result Response of a request (promise) + * @param array $options Options for transport + * + * @return callable|array + */ + public function resultOrFuture($result, $options = []) + { + $response = null; + $async = isset($options['client']['future']) ? $options['client']['future'] : null; + if (is_null($async) || $async === false) { + do { + $result = $result->wait(); + } while ($result instanceof FutureArrayInterface); + + return $result; + } elseif ($async === true || $async === 'lazy') { + return $result; + } + } + + /** + * @param $request + * + * @return bool + */ + public function shouldRetry($request) + { + if ($this->retryAttempts < $this->retries) { + $this->retryAttempts += 1; + + return true; + } + + return false; + } + + /** + * Returns the last used connection so that it may be inspected. Mainly + * for debugging/testing purposes. + * + * @return Connection + */ + public function getLastConnection() + { + return $this->lastConnection; + } +} diff --git a/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ClientTest.php b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ClientTest.php new file mode 100644 index 0000000..da4cc8c --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ClientTest.php @@ -0,0 +1,420 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elasticsearch.org + */ +class ClientTest extends \PHPUnit_Framework_TestCase +{ + public function tearDown() + { + m::close(); + } + + /** + * @expectedException \Elasticsearch\Common\Exceptions\InvalidArgumentException + */ + public function testConstructorIllegalPort() + { + $client = Elasticsearch\ClientBuilder::create()->setHosts(['localhost:abc'])->build(); + } + + public function testCustomQueryParams() + { + $params = array(); + + $client = Elasticsearch\ClientBuilder::create()->setHosts([$_SERVER['ES_TEST_HOST']])->build(); + + $getParams = array( + 'index' => 'test', + 'type' => 'test', + 'id' => 1, + 'parent' => 'abc', + 'custom' => array('customToken' => 'abc', 'otherToken' => 123), + 'client' => ['ignore' => 400] + ); + $exists = $client->exists($getParams); + } + + public function testFromConfig() + { + $params = [ + 'hosts' => [ + 'localhost:9200' + ], + 'retries' => 2, + 'handler' => ClientBuilder::multiHandler() + ]; + $client = ClientBuilder::fromConfig($params); + } + + /** + * @expectedException \Elasticsearch\Common\Exceptions\RuntimeException + */ + public function testFromConfigBadParam() + { + $params = [ + 'hosts' => [ + 'localhost:9200' + ], + 'retries' => 2, + 'imNotReal' => 5 + ]; + $client = ClientBuilder::fromConfig($params); + } + + public function testFromConfigBadParamQuiet() + { + $params = [ + 'hosts' => [ + 'localhost:9200' + ], + 'retries' => 2, + 'imNotReal' => 5 + ]; + $client = ClientBuilder::fromConfig($params, true); + } + + public function testNullDelete() + { + $client = ClientBuilder::create()->build(); + + try { + $client->delete([ + 'index' => null, + 'type' => 'test', + 'id' => 'test' + ]); + $this->fail("InvalidArgumentException was not thrown"); + } catch (Elasticsearch\Common\Exceptions\InvalidArgumentException $e) { + // all good + } + + try { + $client->delete([ + 'index' => 'test', + 'type' => null, + 'id' => 'test' + ]); + $this->fail("InvalidArgumentException was not thrown"); + } catch (Elasticsearch\Common\Exceptions\InvalidArgumentException $e) { + // all good + } + + try { + $client->delete([ + 'index' => 'test', + 'type' => 'test', + 'id' => null + ]); + $this->fail("InvalidArgumentException was not thrown"); + } catch (Elasticsearch\Common\Exceptions\InvalidArgumentException $e) { + // all good + } + } + + public function testEmptyStringDelete() + { + $client = ClientBuilder::create()->build(); + + try { + $client->delete([ + 'index' => '', + 'type' => 'test', + 'id' => 'test' + ]); + $this->fail("InvalidArgumentException was not thrown"); + } catch (Elasticsearch\Common\Exceptions\InvalidArgumentException $e) { + // all good + } + + try { + $client->delete([ + 'index' => 'test', + 'type' => '', + 'id' => 'test' + ]); + $this->fail("InvalidArgumentException was not thrown"); + } catch (Elasticsearch\Common\Exceptions\InvalidArgumentException $e) { + // all good + } + + try { + $client->delete([ + 'index' => 'test', + 'type' => 'test', + 'id' => '' + ]); + $this->fail("InvalidArgumentException was not thrown"); + } catch (Elasticsearch\Common\Exceptions\InvalidArgumentException $e) { + // all good + } + } + + public function testArrayOfEmptyStringDelete() + { + $client = ClientBuilder::create()->build(); + + try { + $client->delete([ + 'index' => ['', '', ''], + 'type' => 'test', + 'id' => 'test' + ]); + $this->fail("InvalidArgumentException was not thrown"); + } catch (Elasticsearch\Common\Exceptions\InvalidArgumentException $e) { + // all good + } + + try { + $client->delete([ + 'index' => 'test', + 'type' => ['', '', ''], + 'id' => 'test' + ]); + $this->fail("InvalidArgumentException was not thrown"); + } catch (Elasticsearch\Common\Exceptions\InvalidArgumentException $e) { + // all good + } + } + + public function testArrayOfNullDelete() + { + $client = ClientBuilder::create()->build(); + + try { + $client->delete([ + 'index' => [null, null, null], + 'type' => 'test', + 'id' => 'test' + ]); + $this->fail("InvalidArgumentException was not thrown"); + } catch (Elasticsearch\Common\Exceptions\InvalidArgumentException $e) { + // all good + } + + try { + $client->delete([ + 'index' => 'test', + 'type' => [null, null, null], + 'id' => 'test' + ]); + $this->fail("InvalidArgumentException was not thrown"); + } catch (Elasticsearch\Common\Exceptions\InvalidArgumentException $e) { + // all good + } + } + + public function testMaxRetriesException() + { + $client = Elasticsearch\ClientBuilder::create() + ->setHosts(["localhost:1"]) + ->setRetries(0) + ->build(); + + $searchParams = array( + 'index' => 'test', + 'type' => 'test', + 'body' => [ + 'query' => [ + 'match_all' => [] + ] + ] + ); + + $client = Elasticsearch\ClientBuilder::create() + ->setHosts(["localhost:1"]) + ->setRetries(0) + ->build(); + + try { + $client->search($searchParams); + $this->fail("Should have thrown CouldNotConnectToHost"); + } catch (Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost $e) { + // All good + $previous = $e->getPrevious(); + $this->assertInstanceOf('Elasticsearch\Common\Exceptions\MaxRetriesException', $previous); + } catch (\Exception $e) { + throw $e; + } + + + $client = Elasticsearch\ClientBuilder::create() + ->setHosts(["localhost:1"]) + ->setRetries(0) + ->build(); + + try { + $client->search($searchParams); + $this->fail("Should have thrown TransportException"); + } catch (Elasticsearch\Common\Exceptions\TransportException $e) { + // All good + $previous = $e->getPrevious(); + $this->assertInstanceOf('Elasticsearch\Common\Exceptions\MaxRetriesException', $previous); + } catch (\Exception $e) { + throw $e; + } + } + + public function testInlineHosts() + { + $client = Elasticsearch\ClientBuilder::create()->setHosts([ + 'localhost:9200' + ])->build(); + $host = $client->transport->getConnection(); + $this->assertEquals("localhost:9200", $host->getHost()); + $this->assertEquals("http", $host->getTransportSchema()); + + + $client = Elasticsearch\ClientBuilder::create()->setHosts([ + 'http://localhost:9200' + ])->build(); + $host = $client->transport->getConnection(); + $this->assertEquals("localhost:9200", $host->getHost()); + $this->assertEquals("http", $host->getTransportSchema()); + + $client = Elasticsearch\ClientBuilder::create()->setHosts([ + 'http://foo.com:9200' + ])->build(); + $host = $client->transport->getConnection(); + $this->assertEquals("foo.com:9200", $host->getHost()); + $this->assertEquals("http", $host->getTransportSchema()); + + $client = Elasticsearch\ClientBuilder::create()->setHosts([ + 'https://foo.com:9200' + ])->build(); + $host = $client->transport->getConnection(); + $this->assertEquals("foo.com:9200", $host->getHost()); + $this->assertEquals("https", $host->getTransportSchema()); + + + $client = Elasticsearch\ClientBuilder::create()->setHosts([ + 'https://user:pass@foo.com:9200' + ])->build(); + $host = $client->transport->getConnection(); + $this->assertEquals("foo.com:9200", $host->getHost()); + $this->assertEquals("https", $host->getTransportSchema()); + $this->assertEquals("user:pass", $host->getUserPass()); + } + + public function testExtendedHosts() + { + $client = Elasticsearch\ClientBuilder::create()->setHosts([ + [ + 'host' => 'localhost', + 'port' => 9200, + 'scheme' => 'http' + ] + ])->build(); + $host = $client->transport->getConnection(); + $this->assertEquals("localhost:9200", $host->getHost()); + $this->assertEquals("http", $host->getTransportSchema()); + + + $client = Elasticsearch\ClientBuilder::create()->setHosts([ + [ + 'host' => 'foo.com', + 'port' => 9200, + 'scheme' => 'http' + ] + ])->build(); + $host = $client->transport->getConnection(); + $this->assertEquals("foo.com:9200", $host->getHost()); + $this->assertEquals("http", $host->getTransportSchema()); + + + $client = Elasticsearch\ClientBuilder::create()->setHosts([ + [ + 'host' => 'foo.com', + 'port' => 9200, + 'scheme' => 'https' + ] + ])->build(); + $host = $client->transport->getConnection(); + $this->assertEquals("foo.com:9200", $host->getHost()); + $this->assertEquals("https", $host->getTransportSchema()); + + + $client = Elasticsearch\ClientBuilder::create()->setHosts([ + [ + 'host' => 'foo.com', + 'scheme' => 'http' + ] + ])->build(); + $host = $client->transport->getConnection(); + $this->assertEquals("foo.com:9200", $host->getHost()); + $this->assertEquals("http", $host->getTransportSchema()); + + + $client = Elasticsearch\ClientBuilder::create()->setHosts([ + [ + 'host' => 'foo.com' + ] + ])->build(); + $host = $client->transport->getConnection(); + $this->assertEquals("foo.com:9200", $host->getHost()); + $this->assertEquals("http", $host->getTransportSchema()); + + + $client = Elasticsearch\ClientBuilder::create()->setHosts([ + [ + 'host' => 'foo.com', + 'port' => 9500, + 'scheme' => 'https' + ] + ])->build(); + $host = $client->transport->getConnection(); + $this->assertEquals("foo.com:9500", $host->getHost()); + $this->assertEquals("https", $host->getTransportSchema()); + + + try { + $client = Elasticsearch\ClientBuilder::create()->setHosts([ + [ + 'port' => 9200, + 'scheme' => 'http' + ] + ])->build(); + $this->fail("Expected RuntimeException from missing host, none thrown"); + } catch (Elasticsearch\Common\Exceptions\RuntimeException $e) { + // good + } + + // Underscore host, questionably legal, but inline method would break + $client = Elasticsearch\ClientBuilder::create()->setHosts([ + [ + 'host' => 'the_foo.com' + ] + ])->build(); + $host = $client->transport->getConnection(); + $this->assertEquals("the_foo.com:9200", $host->getHost()); + $this->assertEquals("http", $host->getTransportSchema()); + + + // Special characters in user/pass, would break inline + $client = Elasticsearch\ClientBuilder::create()->setHosts([ + [ + 'host' => 'foo.com', + 'user' => 'user', + 'pass' => 'abc#$@?%!abc' + ] + ])->build(); + $host = $client->transport->getConnection(); + $this->assertEquals("foo.com:9200", $host->getHost()); + $this->assertEquals("http", $host->getTransportSchema()); + $this->assertEquals("user:abc#$@?%!abc", $host->getUserPass()); + } +} diff --git a/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/Selectors/RoundRobinSelectorTest.php b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/Selectors/RoundRobinSelectorTest.php new file mode 100644 index 0000000..7e5de4f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/Selectors/RoundRobinSelectorTest.php @@ -0,0 +1,82 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elasticsearch.org + */ +class RoundRobinSelectorTest extends \PHPUnit_Framework_TestCase +{ + /** + * Add Ten connections, select 15 to verify round robin + * + * @covers \Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector::select + * + * @return void + */ + public function testTenConnections() + { + $roundRobin = new Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector(); + + $mockConnections = array(); + foreach (range(0, 10) as $index) { + $mockConnections[$index] = $this->getMockBuilder('\Elasticsearch\Connections\CurlMultiConnection') + ->disableOriginalConstructor() + ->getMock(); + } + + foreach (range(0, 15) as $index) { + $retConnection = $roundRobin->select($mockConnections); + + $nextIndex = ($index % 10) + 1; + $this->assertEquals($mockConnections[$nextIndex], $retConnection); + } + } + + /** + * Add Ten connections, select five, remove thre, test another 10 to check + * that the round-robining works after removing connections + * + * @covers \Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector::select + * + * @return void + */ + public function testAddTenConnectionsestFiveTRemoveThree() + { + $roundRobin = new Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector(); + + $mockConnections = array(); + foreach (range(0, 10) as $index) { + $mockConnections[$index] = $this->getMockBuilder('\Elasticsearch\Connections\CurlMultiConnection') + ->disableOriginalConstructor() + ->getMock(); + } + + foreach (range(0, 4) as $index) { + $retConnection = $roundRobin->select($mockConnections); + + $nextIndex = ($index % (count($mockConnections)-1)) + 1; + $this->assertEquals($mockConnections[$nextIndex], $retConnection); + } + + unset($mockConnections[8]); + unset($mockConnections[9]); + unset($mockConnections[10]); + + foreach (range(5, 15) as $index) { + $retConnection = $roundRobin->select($mockConnections); + + $nextIndex = ($index % (count($mockConnections)-1)) + 1; + $this->assertEquals($mockConnections[$nextIndex], $retConnection); + } + } +} diff --git a/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/Selectors/StickyRoundRobinSelectorTest.php b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/Selectors/StickyRoundRobinSelectorTest.php new file mode 100644 index 0000000..3d93ec7 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/Selectors/StickyRoundRobinSelectorTest.php @@ -0,0 +1,65 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elasticsearch.org + */ +class StickyRoundRobinSelectorTest extends \PHPUnit_Framework_TestCase +{ + public function tearDown() + { + m::close(); + } + + public function testTenConnections() + { + $roundRobin = new Elasticsearch\ConnectionPool\Selectors\StickyRoundRobinSelector(); + + $mockConnections = array(); + $mockConnections[] = m::mock('\Elasticsearch\Connections\GuzzleConnection') + ->shouldReceive('isAlive')->times(16)->andReturn(true)->getMock(); + + foreach (range(0, 9) as $index) { + $mockConnections[] = m::mock('\Elasticsearch\Connections\GuzzleConnection'); + } + + foreach (range(0, 15) as $index) { + $retConnection = $roundRobin->select($mockConnections); + + $this->assertEquals($mockConnections[0], $retConnection); + } + } + + public function testTenConnectionsFirstDies() + { + $roundRobin = new Elasticsearch\ConnectionPool\Selectors\StickyRoundRobinSelector(); + + $mockConnections = array(); + $mockConnections[] = m::mock('\Elasticsearch\Connections\GuzzleConnection') + ->shouldReceive('isAlive')->once()->andReturn(false)->getMock(); + + $mockConnections[] = m::mock('\Elasticsearch\Connections\GuzzleConnection') + ->shouldReceive('isAlive')->times(15)->andReturn(true)->getMock(); + + foreach (range(0, 8) as $index) { + $mockConnections[] = m::mock('\Elasticsearch\Connections\GuzzleConnection'); + } + + foreach (range(0, 15) as $index) { + $retConnection = $roundRobin->select($mockConnections); + + $this->assertEquals($mockConnections[1], $retConnection); + } + } +} diff --git a/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/SniffingConnectionPoolIntegrationTest.php b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/SniffingConnectionPoolIntegrationTest.php new file mode 100644 index 0000000..e6f3de9 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/SniffingConnectionPoolIntegrationTest.php @@ -0,0 +1,25 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elasticsearch.org + */ +class SniffingConnectionPoolIntegrationTest extends \PHPUnit_Framework_TestCase +{ + public function testSniff() + { + $client = ClientBuilder::create() + ->setHosts([$_SERVER['ES_TEST_HOST']]) + ->setConnectionPool('\Elasticsearch\ConnectionPool\SniffingConnectionPool', ['sniffingInterval' => -10]) + ->build(); + + $client->ping(); + } +} diff --git a/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/SniffingConnectionPoolTest.php b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/SniffingConnectionPoolTest.php new file mode 100644 index 0000000..5d38c87 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/SniffingConnectionPoolTest.php @@ -0,0 +1,426 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elasticsearch.org + */ +class SniffingConnectionPoolTest extends \PHPUnit_Framework_TestCase +{ + public function tearDown() + { + m::close(); + } + + public function testAddOneHostThenGetConnection() + { + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping') + ->andReturn(true) + ->getMock() + ->shouldReceive('isAlive') + ->andReturn(true) + ->getMock(); + + $connections = array($mockConnection); + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select') + ->andReturn($connections[0]) + ->getMock(); + + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory'); + + $connectionPoolParams = array('randomizeHosts' => false); + $connectionPool = new SniffingConnectionPool($connections, $selector, $connectionFactory, $connectionPoolParams); + + $retConnection = $connectionPool->nextConnection(); + + $this->assertEquals($mockConnection, $retConnection); + } + + public function testAddOneHostAndTriggerSniff() + { + $clusterState = json_decode('{"ok":true,"cluster_name":"elasticsearch_zach","nodes":{"Bl2ihSr7TcuUHxhu1GA_YQ":{"name":"Vesta","transport_address":"inet[/192.168.1.119:9300]","hostname":"zach-ThinkPad-W530","version":"0.90.5","http_address":"inet[/192.168.1.119:9200]"}}}', true); + + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping')->andReturn(true)->getMock() + ->shouldReceive('isAlive')->andReturn(true)->getMock() + ->shouldReceive('getTransportSchema')->once()->andReturn('http')->getMock() + ->shouldReceive('sniff')->once()->andReturn($clusterState)->getMock(); + + $connections = array($mockConnection); + $mockNewConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('isAlive')->andReturn(true)->getMock(); + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select')->twice() + ->andReturn($mockNewConnection) + ->getMock(); + + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory') + ->shouldReceive('create')->with(array('host' => '192.168.1.119', 'port' => 9200))->andReturn($mockNewConnection)->getMock(); + + $connectionPoolParams = array( + 'randomizeHosts' => false, + 'sniffingInterval' => -1 + ); + $connectionPool = new SniffingConnectionPool($connections, $selector, $connectionFactory, $connectionPoolParams); + + $retConnection = $connectionPool->nextConnection(); + + $this->assertEquals($mockNewConnection, $retConnection); + } + + public function testAddOneHostAndForceNext() + { + $clusterState = json_decode('{"ok":true,"cluster_name":"elasticsearch_zach","nodes":{"Bl2ihSr7TcuUHxhu1GA_YQ":{"name":"Vesta","transport_address":"inet[/192.168.1.119:9300]","hostname":"zach-ThinkPad-W530","version":"0.90.5","http_address":"inet[/192.168.1.119:9200]"}}}', true); + + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping')->andReturn(true)->getMock() + ->shouldReceive('isAlive')->andReturn(true)->getMock() + ->shouldReceive('getTransportSchema')->once()->andReturn('http')->getMock() + ->shouldReceive('sniff')->once()->andReturn($clusterState)->getMock(); + + $connections = array($mockConnection); + $mockNewConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('isAlive')->andReturn(true)->getMock(); + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select')->once()->andReturn($mockConnection)->getMock() + ->shouldReceive('select')->once()->andReturn($mockNewConnection)->getMock(); + + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory') + ->shouldReceive('create')->with(array('host' => '192.168.1.119', 'port' => 9200))->andReturn($mockNewConnection)->getMock(); + + $connectionPoolParams = array( + 'randomizeHosts' => false + ); + $connectionPool = new SniffingConnectionPool($connections, $selector, $connectionFactory, $connectionPoolParams); + + $retConnection = $connectionPool->nextConnection(true); + + $this->assertEquals($mockNewConnection, $retConnection); + } + + public function testAddTenNodesThenGetConnection() + { + $connections = array(); + + foreach (range(1, 10) as $index) { + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping') + ->andReturn(true) + ->getMock() + ->shouldReceive('isAlive') + ->andReturn(true) + ->getMock(); + + $connections[] = $mockConnection; + } + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select') + ->andReturn($connections[0]) + ->getMock(); + + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory'); + + $connectionPoolParams = array('randomizeHosts' => false); + $connectionPool = new SniffingConnectionPool($connections, $selector, $connectionFactory, $connectionPoolParams); + + $retConnection = $connectionPool->nextConnection(); + + $this->assertEquals($connections[0], $retConnection); + } + + public function testAddTenNodesTimeoutAllButLast() + { + $connections = array(); + + foreach (range(1, 9) as $index) { + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping') + ->andReturn(false) + ->getMock() + ->shouldReceive('isAlive') + ->andReturn(false) + ->getMock(); + + $connections[] = $mockConnection; + } + + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping') + ->andReturn(true) + ->getMock() + ->shouldReceive('isAlive') + ->andReturn(true) + ->getMock(); + + $connections[] = $mockConnection; + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select') + ->andReturnValues($connections) + ->getMock(); + + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory'); + + $connectionPoolParams = array('randomizeHosts' => false); + $connectionPool = new SniffingConnectionPool($connections, $selector, $connectionFactory, $connectionPoolParams); + + $retConnection = $connectionPool->nextConnection(); + + $this->assertEquals($connections[9], $retConnection); + } + + /** + * @expectedException Elasticsearch\Common\Exceptions\NoNodesAvailableException + */ + public function testAddTenNodesAllTimeout() + { + $connections = array(); + + foreach (range(1, 10) as $index) { + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping') + ->andReturn(false) + ->getMock() + ->shouldReceive('isAlive') + ->andReturn(false) + ->getMock(); + + $connections[] = $mockConnection; + } + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select') + ->andReturnValues($connections) + ->getMock(); + + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory'); + + $connectionPoolParams = array('randomizeHosts' => false); + $connectionPool = new SniffingConnectionPool($connections, $selector, $connectionFactory, $connectionPoolParams); + + $retConnection = $connectionPool->nextConnection(); + } + + public function testAddOneHostSniffTwo() + { + $clusterState = json_decode('{"ok":true,"cluster_name":"elasticsearch_zach","nodes":{"node1":{"name":"Vesta","transport_address":"inet[/192.168.1.119:9300]","hostname":"zach-ThinkPad-W530","version":"0.90.5","http_address":"inet[/192.168.1.119:9200]"}, "node2":{"name":"Vesta","transport_address":"inet[/192.168.1.119:9301]","hostname":"zach-ThinkPad-W530","version":"0.90.5","http_address":"inet[/192.168.1.119:9201]"}}}', true); + + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping')->andReturn(true)->getMock() + ->shouldReceive('isAlive')->andReturn(true)->getMock() + ->shouldReceive('getTransportSchema')->twice()->andReturn('http')->getMock() + ->shouldReceive('sniff')->twice()->andReturn($clusterState)->getMock(); + + $connections = array($mockConnection); + + $newConnections = array(); + $newConnections[] = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('isAlive')->andReturn(true)->getMock(); + + $newConnections[] = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('isAlive')->andReturn(true)->getMock(); + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select') + ->andReturnValues(array( //selects provided node first, then the new cluster list + $mockConnection, + $newConnections[0], + $newConnections[1] + )) + ->getMock(); + + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory') + ->shouldReceive('create')->with(array('host' => '192.168.1.119', 'port' => 9200))->andReturn($newConnections[0])->getMock() + ->shouldReceive('create')->with(array('host' => '192.168.1.119', 'port' => 9201))->andReturn($newConnections[1])->getMock(); + + $connectionPoolParams = array( + 'randomizeHosts' => false, + 'sniffingInterval' => -1 + ); + $connectionPool = new SniffingConnectionPool($connections, $selector, $connectionFactory, $connectionPoolParams); + + $retConnection = $connectionPool->nextConnection(); + $this->assertEquals($newConnections[0], $retConnection); + + $retConnection = $connectionPool->nextConnection(); + $this->assertEquals($newConnections[1], $retConnection); + } + + /** + * @expectedException Elasticsearch\Common\Exceptions\NoNodesAvailableException + */ + public function testAddSeed_SniffTwo_TimeoutTwo() + { + $clusterState = json_decode('{"ok":true,"cluster_name":"elasticsearch_zach","nodes":{"node1":{"name":"Vesta","transport_address":"inet[/192.168.1.119:9300]","hostname":"zach-ThinkPad-W530","version":"0.90.5","http_address":"inet[/192.168.1.119:9200]"}, "node2":{"name":"Vesta","transport_address":"inet[/192.168.1.119:9301]","hostname":"zach-ThinkPad-W530","version":"0.90.5","http_address":"inet[/192.168.1.119:9201]"}}}', true); + + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping')->andReturn(true)->getMock() + ->shouldReceive('isAlive')->andReturn(true)->getMock() + ->shouldReceive('getTransportSchema')->once()->andReturn('http')->getMock() + ->shouldReceive('sniff')->once()->andReturn($clusterState)->getMock(); + + $connections = array($mockConnection); + + $newConnections = array(); + $newConnections[] = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('isAlive')->andReturn(false)->getMock() + ->shouldReceive('ping')->andReturn(false)->getMock(); + + $newConnections[] = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('isAlive')->andReturn(false)->getMock() + ->shouldReceive('ping')->andReturn(false)->getMock(); + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select') + ->andReturnValues(array( //selects provided node first, then the new cluster list + $mockConnection, + $newConnections[0], + $newConnections[1] + )) + ->getMock(); + + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory') + ->shouldReceive('create')->with(array('host' => '192.168.1.119', 'port' => 9200))->andReturn($newConnections[0])->getMock() + ->shouldReceive('create')->with(array('host' => '192.168.1.119', 'port' => 9201))->andReturn($newConnections[1])->getMock(); + + $connectionPoolParams = array( + 'randomizeHosts' => false, + 'sniffingInterval' => -1 + ); + $connectionPool = new SniffingConnectionPool($connections, $selector, $connectionFactory, $connectionPoolParams); + + $retConnection = $connectionPool->nextConnection(); + $this->assertEquals($mockConnection, $retConnection); + } + + public function testTen_TimeoutNine_SniffTenth_AddTwoAlive() + { + $clusterState = json_decode('{"ok":true,"cluster_name":"elasticsearch_zach","nodes":{"node1":{"name":"Vesta","transport_address":"inet[/192.168.1.119:9300]","hostname":"zach-ThinkPad-W530","version":"0.90.5","http_address":"inet[/192.168.1.119:9200]"}, "node2":{"name":"Vesta","transport_address":"inet[/192.168.1.119:9301]","hostname":"zach-ThinkPad-W530","version":"0.90.5","http_address":"inet[/192.168.1.119:9201]"}}}', true); + + $connections = array(); + + foreach (range(1, 10) as $index) { + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping')->andReturn(false)->getMock() + ->shouldReceive('isAlive')->andReturn(true)->getMock() + ->shouldReceive('sniff')->andThrow('Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException')->getMock(); + + $connections[] = $mockConnection; + } + + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping')->andReturn(true)->getMock() + ->shouldReceive('isAlive')->andReturn(true)->getMock() + ->shouldReceive('sniff')->andReturn($clusterState)->getMock() + ->shouldReceive('getTransportSchema')->twice()->andReturn('http')->getMock(); + + $connections[] = $mockConnection; + + $newConnections = $connections; + $newConnections[] = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('isAlive')->andReturn(true)->getMock() + ->shouldReceive('ping')->andReturn(true)->getMock(); + + $newConnections[] = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('isAlive')->andReturn(true)->getMock() + ->shouldReceive('ping')->andReturn(true)->getMock(); + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select') + ->andReturnValues($newConnections) + ->getMock(); + + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory') + ->shouldReceive('create')->with(array('host' => '192.168.1.119', 'port' => 9200))->andReturn($newConnections[10])->getMock() + ->shouldReceive('create')->with(array('host' => '192.168.1.119', 'port' => 9201))->andReturn($newConnections[11])->getMock(); + + $connectionPoolParams = array( + 'randomizeHosts' => false, + 'sniffingInterval' => -1 + ); + $connectionPool = new SniffingConnectionPool($connections, $selector, $connectionFactory, $connectionPoolParams); + + $retConnection = $connectionPool->nextConnection(); + $this->assertEquals($newConnections[11], $retConnection); + + $retConnection = $connectionPool->nextConnection(); + $this->assertEquals($newConnections[12], $retConnection); + } + + /** + * @expectedException Elasticsearch\Common\Exceptions\NoNodesAvailableException + */ + public function testTen_TimeoutNine_SniffTenth_AddTwoDead_TimeoutEveryone() + { + $clusterState = json_decode('{"ok":true,"cluster_name":"elasticsearch_zach","nodes":{"node1":{"name":"Vesta","transport_address":"inet[/192.168.1.119:9300]","hostname":"zach-ThinkPad-W530","version":"0.90.5","http_address":"inet[/192.168.1.119:9200]"}, "node2":{"name":"Vesta","transport_address":"inet[/192.168.1.119:9301]","hostname":"zach-ThinkPad-W530","version":"0.90.5","http_address":"inet[/192.168.1.119:9201]"}}}', true); + + $connections = array(); + + foreach (range(1, 10) as $index) { + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping')->andReturn(false)->getMock() + ->shouldReceive('isAlive')->andReturn(true)->getMock() + ->shouldReceive('sniff')->andThrow('Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException')->getMock(); + + $connections[] = $mockConnection; + } + + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping')->andReturn(true)->getMock() + ->shouldReceive('isAlive')->andReturn(true)->getMock() + ->shouldReceive('sniff')->andReturn($clusterState)->getMock() + ->shouldReceive('getTransportSchema')->once()->andReturn('http')->getMock() + ->shouldReceive('sniff')->andThrow('Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException')->getMock(); + + $connections[] = $mockConnection; + + $newConnections = $connections; + $newConnections[] = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('isAlive')->andReturn(false)->getMock() + ->shouldReceive('ping')->andReturn(false)->getMock() + ->shouldReceive('sniff')->andThrow('Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException')->getMock(); + + $newConnections[] = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('isAlive')->andReturn(false)->getMock() + ->shouldReceive('ping')->andReturn(false)->getMock() + ->shouldReceive('sniff')->andThrow('Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException')->getMock(); + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select') + ->andReturnValues($newConnections) + ->getMock(); + + $RRConnections = $newConnections; + //array_push($connections); + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory') + ->shouldReceive('create')->with(array('host' => '192.168.1.119', 'port' => 9200))->andReturn($newConnections[10])->getMock() + ->shouldReceive('create')->with(array('host' => '192.168.1.119', 'port' => 9201))->andReturn($newConnections[11])->getMock(); + + $connectionPoolParams = array( + 'randomizeHosts' => false, + 'sniffingInterval' => -1 + ); + $connectionPool = new SniffingConnectionPool($connections, $selector, $connectionFactory, $connectionPoolParams); + + $retConnection = $connectionPool->nextConnection(); + $this->assertEquals($newConnections[11], $retConnection); + + $retConnection = $connectionPool->nextConnection(); + $this->assertEquals($newConnections[12], $retConnection); + } +} diff --git a/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/StaticConnectionPoolTest.php b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/StaticConnectionPoolTest.php new file mode 100644 index 0000000..d8466db --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/ConnectionPool/StaticConnectionPoolTest.php @@ -0,0 +1,231 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elasticsearch.org + */ +class StaticConnectionPoolTest extends \PHPUnit_Framework_TestCase +{ + public function tearDown() + { + m::close(); + } + + public function testAddOneHostThenGetConnection() + { + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping') + ->andReturn(true) + ->getMock() + ->shouldReceive('isAlive') + ->andReturn(true) + ->getMock() + ->shouldReceive('markDead')->once()->getMock(); + + $connections = array($mockConnection); + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select') + ->andReturn($connections[0]) + ->getMock(); + + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory'); + + $randomizeHosts = false; + $connectionPool = new Elasticsearch\ConnectionPool\StaticConnectionPool($connections, $selector, $connectionFactory, $randomizeHosts); + + $retConnection = $connectionPool->nextConnection(); + + $this->assertEquals($mockConnection, $retConnection); + } + + public function testAddMultipleHostsThenGetFirst() + { + $connections = array(); + + foreach (range(1, 10) as $index) { + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping') + ->andReturn(true) + ->getMock() + ->shouldReceive('isAlive') + ->andReturn(true) + ->getMock() + ->shouldReceive('markDead')->once()->getMock(); + + $connections[] = $mockConnection; + } + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select') + ->andReturn($connections[0]) + ->getMock(); + + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory'); + + $randomizeHosts = false; + $connectionPool = new Elasticsearch\ConnectionPool\StaticConnectionPool($connections, $selector, $connectionFactory, $randomizeHosts); + + $retConnection = $connectionPool->nextConnection(); + + $this->assertEquals($connections[0], $retConnection); + } + + /** + * @expectedException Elasticsearch\Common\Exceptions\NoNodesAvailableException + */ + public function testAllHostsFailPing() + { + $connections = array(); + + foreach (range(1, 10) as $index) { + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping') + ->andReturn(false) + ->getMock() + ->shouldReceive('isAlive') + ->andReturn(false) + ->getMock() + ->shouldReceive('markDead')->once()->getMock() + ->shouldReceive('getPingFailures')->andReturn(0)->once()->getMock() + ->shouldReceive('getLastPing')->andReturn(time())->once()->getMock(); + + $connections[] = $mockConnection; + } + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select') + ->andReturnValues($connections) + ->getMock(); + + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory'); + + $randomizeHosts = false; + $connectionPool = new Elasticsearch\ConnectionPool\StaticConnectionPool($connections, $selector, $connectionFactory, $randomizeHosts); + + $connectionPool->nextConnection(); + } + + public function testAllExceptLastHostFailPingRevivesInSkip() + { + $connections = array(); + + foreach (range(1, 9) as $index) { + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping') + ->andReturn(false) + ->getMock() + ->shouldReceive('isAlive') + ->andReturn(false) + ->getMock() + ->shouldReceive('markDead')->once()->getMock() + ->shouldReceive('getPingFailures')->andReturn(0)->once()->getMock() + ->shouldReceive('getLastPing')->andReturn(time())->once()->getMock(); + + $connections[] = $mockConnection; + } + + $goodConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping')->once() + ->andReturn(true) + ->getMock() + ->shouldReceive('isAlive')->once() + ->andReturn(false) + ->getMock() + ->shouldReceive('markDead')->once()->getMock() + ->shouldReceive('getPingFailures')->andReturn(0)->once()->getMock() + ->shouldReceive('getLastPing')->andReturn(time())->once()->getMock(); + + $connections[] = $goodConnection; + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select') + ->andReturnValues($connections) + ->getMock(); + + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory'); + + $randomizeHosts = false; + $connectionPool = new Elasticsearch\ConnectionPool\StaticConnectionPool($connections, $selector, $connectionFactory, $randomizeHosts); + + $ret = $connectionPool->nextConnection(); + $this->assertEquals($goodConnection, $ret); + } + + public function testAllExceptLastHostFailPingRevivesPreSkip() + { + $connections = array(); + + foreach (range(1, 9) as $index) { + $mockConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping') + ->andReturn(false) + ->getMock() + ->shouldReceive('isAlive') + ->andReturn(false) + ->getMock() + ->shouldReceive('markDead')->once()->getMock() + ->shouldReceive('getPingFailures')->andReturn(0)->once()->getMock() + ->shouldReceive('getLastPing')->andReturn(time())->once()->getMock(); + + $connections[] = $mockConnection; + } + + $goodConnection = m::mock('\Elasticsearch\Connections\Connection') + ->shouldReceive('ping')->once() + ->andReturn(true) + ->getMock() + ->shouldReceive('isAlive')->once() + ->andReturn(false) + ->getMock() + ->shouldReceive('markDead')->once()->getMock() + ->shouldReceive('getPingFailures')->andReturn(0)->once()->getMock() + ->shouldReceive('getLastPing')->andReturn(time()-10000)->once()->getMock(); + + $connections[] = $goodConnection; + + $selector = m::mock('\Elasticsearch\ConnectionPool\Selectors\RoundRobinSelector') + ->shouldReceive('select') + ->andReturnValues($connections) + ->getMock(); + + $connectionFactory = m::mock('\Elasticsearch\Connections\ConnectionFactory'); + + $randomizeHosts = false; + $connectionPool = new Elasticsearch\ConnectionPool\StaticConnectionPool($connections, $selector, $connectionFactory, $randomizeHosts); + + $ret = $connectionPool->nextConnection(); + $this->assertEquals($goodConnection, $ret); + } + + public function testCustomConnectionPoolIT() + { + $clientBuilder = \Elasticsearch\ClientBuilder::create(); + $clientBuilder->setHosts(['localhost:1']); + $client = $clientBuilder + ->setRetries(0) + ->setConnectionPool('\Elasticsearch\ConnectionPool\StaticConnectionPool', []) + ->build(); + + try { + $client->search([]); + $this->fail("Should have thrown NoNodesAvailableException"); + } catch (Elasticsearch\Common\Exceptions\NoNodesAvailableException $e) { + // All good + } catch (\Exception $e) { + throw $e; + } + } +} diff --git a/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Endpoints/AbstractEndpointTest.php b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Endpoints/AbstractEndpointTest.php new file mode 100644 index 0000000..51701e3 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Endpoints/AbstractEndpointTest.php @@ -0,0 +1,36 @@ + 10]], + [['invalid' => 10, 'invalid2' => 'another']], + ]; + } + + /** + * @dataProvider invalidParameters + * @expectedException Elasticsearch\Common\Exceptions\UnexpectedValueException + */ + public function testInvalidParamsCauseErrorsWhenProvidedToSetParams(array $params) + { + $this->endpoint->expects($this->once()) + ->method('getParamWhitelist') + ->willReturn(['one', 'two']); + + $this->endpoint->setParams($params); + } + + protected function setUp() + { + $this->endpoint = $this->getMockForAbstractClass(AbstractEndpoint::class); + } +} diff --git a/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Helper/Iterators/SearchResponseIteratorTest.php b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Helper/Iterators/SearchResponseIteratorTest.php new file mode 100644 index 0000000..2570dd8 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Helper/Iterators/SearchResponseIteratorTest.php @@ -0,0 +1,167 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://Elasticsearch.org + */ +class SearchResponseIteratorTest extends \PHPUnit_Framework_TestCase +{ + + public function tearDown() + { + m::close(); + } + + public function testWithNoResults() + { + $search_params = array( + 'scroll' => '5m', + 'index' => 'twitter', + 'size' => 1000, + 'body' => array( + 'query' => array( + 'match_all' => new \StdClass + ) + ) + ); + + $mock_client = m::mock('\Elasticsearch\Client'); + + $mock_client->shouldReceive('search') + ->once() + ->ordered() + ->with($search_params) + ->andReturn(array('_scroll_id' => 'scroll_id_01')); + + $mock_client->shouldReceive('scroll') + ->never(); + + $mock_client->shouldReceive('clearScroll') + ->once() + ->ordered() + ->withAnyArgs(); + + + $responses = new SearchResponseIterator($mock_client, $search_params); + + $this->assertCount(0, $responses); + } + + public function testWithHits() + { + $search_params = array( + 'scroll' => '5m', + 'index' => 'twitter', + 'size' => 1000, + 'body' => array( + 'query' => array( + 'match_all' => new \StdClass + ) + ) + ); + + $mock_client = m::mock('\Elasticsearch\Client'); + + $mock_client->shouldReceive('search') + ->once() + ->ordered() + ->with($search_params) + ->andReturn([ + '_scroll_id' => 'scroll_id_01', + 'hits' => [ + 'hits' => [ + [ + 'foo' => 'bar' + ] + ] + ] + ]); + + $mock_client->shouldReceive('scroll') + ->once() + ->ordered() + ->with( + [ + 'scroll_id' => 'scroll_id_01', + 'scroll' => '5m' + ] + ) + ->andReturn( + [ + '_scroll_id' => 'scroll_id_02', + 'hits' => [ + 'hits' => [ + [ + 'foo' => 'bar' + ] + ] + ] + ]); + + $mock_client->shouldReceive('scroll') + ->once() + ->ordered() + ->with( + [ + 'scroll_id' => 'scroll_id_02', + 'scroll' => '5m' + ] + ) + ->andReturn( + [ + '_scroll_id' => 'scroll_id_03', + 'hits' => [ + 'hits' => [ + [ + 'foo' => 'bar' + ] + ] + ] + ] + ); + + $mock_client->shouldReceive('scroll') + ->once() + ->ordered() + ->with( + [ + 'scroll_id' => 'scroll_id_03', + 'scroll' => '5m' + ] + ) + ->andReturn( + [ + '_scroll_id' => 'scroll_id_04', + 'hits' => [ + 'hits' => [] + ] + ] + ); + + $mock_client->shouldReceive('scroll') + ->never() + ->with( + [ + 'scroll_id' => 'scroll_id_04', + 'scroll' => '5m' + ] + ); + + $mock_client->shouldReceive('clearScroll') + ->once() + ->ordered() + ->withAnyArgs(); + + $responses = new SearchResponseIterator($mock_client, $search_params); + + $this->assertCount(4, $responses); + } +} diff --git a/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/RegisteredNamespaceTest.php b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/RegisteredNamespaceTest.php new file mode 100644 index 0000000..f40430f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/RegisteredNamespaceTest.php @@ -0,0 +1,65 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elasticsearch.org + */ +class RegisteredNamespaceTest extends \PHPUnit_Framework_TestCase +{ + public function tearDown() + { + m::close(); + } + + public function testRegisteringNamespace() + { + $builder = new FooNamespaceBuilder(); + $client = ClientBuilder::create()->registerNamespace($builder)->build(); + $this->assertEquals("123", $client->foo()->fooMethod()); + } + + /** + * @expectedException \Elasticsearch\Common\Exceptions\BadMethodCallException + */ + public function testNonExistingNamespace() + { + $builder = new FooNamespaceBuilder(); + $client = ClientBuilder::create()->registerNamespace($builder)->build(); + $this->assertEquals("123", $client->bar()->fooMethod()); + } +} + +class FooNamespaceBuilder implements Elasticsearch\Namespaces\NamespaceBuilderInterface +{ + public function getName() + { + return "foo"; + } + + public function getObject(Transport $transport, SerializerInterface $serializer) + { + return new FooNamespace(); + } +} + +class FooNamespace +{ + public function fooMethod() + { + return "123"; + } +} \ No newline at end of file diff --git a/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Serializers/ArrayToJSONSerializerTest.php b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Serializers/ArrayToJSONSerializerTest.php new file mode 100644 index 0000000..0010f86 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Serializers/ArrayToJSONSerializerTest.php @@ -0,0 +1,51 @@ + 'field'); + + $ret = $serializer->serialize($body); + + $body = json_encode($body, JSON_PRESERVE_ZERO_FRACTION); + $this->assertEquals($body, $ret); + } + + public function testSerializeString() + { + $serializer = new ArrayToJSONSerializer(); + $body = 'abc'; + + $ret = $serializer->serialize($body); + + $this->assertEquals($body, $ret); + } + + public function testDeserializeJSON() + { + $serializer = new ArrayToJSONSerializer(); + $body = '{"field":"value"}'; + + $ret = $serializer->deserialize($body, array()); + + $body = json_decode($body, true); + $this->assertEquals($body, $ret); + } +} diff --git a/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Serializers/EverythingToJSONSerializerTest.php b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Serializers/EverythingToJSONSerializerTest.php new file mode 100644 index 0000000..d0c6a91 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/Serializers/EverythingToJSONSerializerTest.php @@ -0,0 +1,52 @@ + 'field'); + + $ret = $serializer->serialize($body); + + $body = json_encode($body, JSON_PRESERVE_ZERO_FRACTION); + $this->assertEquals($body, $ret); + } + + public function testSerializeString() + { + $serializer = new EverythingToJSONSerializer(); + $body = 'abc'; + + $ret = $serializer->serialize($body); + + $body = '"abc"'; + $this->assertEquals($body, $ret); + } + + public function testDeserializeJSON() + { + $serializer = new EverythingToJSONSerializer(); + $body = '{"field":"value"}'; + + $ret = $serializer->deserialize($body, array()); + + $body = json_decode($body, true); + $this->assertEquals($body, $ret); + } +} diff --git a/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/YamlRunnerTest.php b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/YamlRunnerTest.php new file mode 100644 index 0000000..132b562 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/tests/Elasticsearch/Tests/YamlRunnerTest.php @@ -0,0 +1,975 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elasticsearch.org + */ +class YamlRunnerTest extends \PHPUnit_Framework_TestCase +{ + /** @var Parser Yaml parser for reading integrations tests */ + private $yaml; + + /** @var Elasticsearch\Client client used by elasticsearch */ + private $client; + + /** @var string Es version */ + private static $esVersion; + + /** @var array A list of supported features */ + private static $supportedFeatures = [ + 'stash_in_path', 'warnings' + ]; + + /** @var array A mapping for endpoint when there is a reserved keywords for the method / namespace name */ + private static $endpointMapping = [ + 'tasks' => [ + 'list' => ['tasksList', 'tasks'], + ], + ]; + + /** @var array A list of skipped test with their reasons */ + private static $skippedTest = [ + 'cat.nodeattrs/10_basic.yaml' => 'Using java regex fails in PHP', + 'cat.repositories/10_basic.yaml' => 'Using java regex fails in PHP', + 'indices.shrink/10_basic.yaml' => 'Shrink tests seem to require multiple nodes', + 'indices.rollover/10_basic.yaml' => 'Rollover test seems buggy atm' + ]; + + /** @var array A list of files to skip completely, due to fatal parsing errors */ + private static $skippedFiles = [ + 'indices.create/10_basic.yaml' => 'Temporary: Yaml parser doesnt support "inline" empty keys', + 'indices.put_mapping/10_basic.yaml' => 'Temporary: Yaml parser doesnt support "inline" empty keys', + ]; + + /** + * Return the elasticsearch host + * + * @return string + */ + public static function getHost() + { + if (isset($_SERVER['ES_TEST_HOST']) === true) { + return $_SERVER['ES_TEST_HOST']; + } + + echo 'Environment variable for elasticsearch test cluster (ES_TEST_HOST) not defined. Exiting yaml test'; + exit; + } + + + public static function setUpBeforeClass() + { + $host = static::getHost(); + echo "Test Host: $host\n"; + + $ch = curl_init($host); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + + $response = curl_exec($ch); + curl_close($ch); + + $response = json_decode($response, true); + static::$esVersion = $response['version']['number']; + echo "ES Version: ".static::$esVersion."\n"; + } + + public function setUp() + { + $this->clean(); + $builder = Elasticsearch\ClientBuilder::create()->setHosts([self::getHost()]); + if (version_compare(phpversion(), '5.6.6', '<') || ! defined('JSON_PRESERVE_ZERO_FRACTION')) { + $builder->allowBadJSONSerialization(); + } + $this->client = $builder->build(); + } + + /** + * @dataProvider yamlProvider + * @group sync + */ + public function testIntegration($testProcedure, $skip, $setupProcedure, $fileName) + { + if ($skip) { + static::markTestIncomplete($testProcedure); + } + + if (array_key_exists($fileName, static::$skippedTest)) { + static::markTestSkipped(static::$skippedTest[$fileName]); + } + + if (null !== $setupProcedure) { + $this->processProcedure(current($setupProcedure), 'setup'); + $this->waitForYellow(); + } + + $this->processProcedure(current($testProcedure), key($testProcedure)); + } + + /** + * @dataProvider yamlProvider + * @group async + */ + public function testAsyncIntegration($testProcedure, $skip, $setupProcedure, $fileName) + { + if ($skip) { + static::markTestIncomplete($testProcedure); + } + + if (array_key_exists($fileName, static::$skippedTest)) { + static::markTestSkipped(static::$skippedTest[$fileName]); + } + + if (null !== $setupProcedure) { + $this->processProcedure(current($setupProcedure), 'setup'); + $this->waitForYellow(); + } + + $this->processProcedure(current($testProcedure), key($testProcedure), true); + } + + /** + * Process a procedure + * + * @param $procedure + * @param $name + * @param bool $async + */ + public function processProcedure($procedure, $name, $async = false) + { + $lastOperationResult = null; + $context = []; + + foreach ($procedure as $operation) { + $lastOperationResult = $this->processOperation($operation, $lastOperationResult, $context, $name, $async); + } + } + + /** + * Process an operation + * + * @param $operation + * @param $lastOperationResult + * @param $testName + * @param array $context + * @param bool $async + * + * @return mixed + */ + public function processOperation($operation, $lastOperationResult, &$context, $testName, $async = false) + { + $operationName = array_keys((array)$operation)[0]; + + if ('do' === $operationName) { + return $this->operationDo($operation->{$operationName}, $lastOperationResult, $context, $testName, $async); + } + + if ('is_false' === $operationName) { + return $this->operationIsFalse($operation->{$operationName}, $lastOperationResult, $context, $testName); + } + + if ('is_true' === $operationName) { + return $this->operationIsTrue($operation->{$operationName}, $lastOperationResult, $context, $testName); + } + + if ('match' === $operationName) { + return $this->operationMatch($operation->{$operationName}, $lastOperationResult, $context, $testName); + } + + if ('gte' === $operationName) { + return $this->operationGreaterThanOrEqual($operation->{$operationName}, $lastOperationResult, $context, $testName); + } + + if ('gt' === $operationName) { + return $this->operationGreaterThan($operation->{$operationName}, $lastOperationResult, $context, $testName); + } + + if ('length' === $operationName) { + return $this->operationLength($operation->{$operationName}, $lastOperationResult, $context, $testName); + } + + if ('set' === $operationName) { + return $this->operationSet($operation->{$operationName}, $lastOperationResult, $context, $testName); + } + + if ('skip' === $operationName) { + return $this->operationSkip($operation->{$operationName}, $lastOperationResult, $testName); + } + + self::markTestIncomplete(sprintf('Operation %s not supported for test "%s"', $operationName, $testName)); + } + + /** + * Do something on the client + * + * @param $operation + * @param $lastOperationResult + * @param array $context + * @param $testName + * @param bool $async + * + * @throws \Exception + * + * @return mixed + */ + public function operationDo($operation, $lastOperationResult, &$context, $testName, $async = false) + { + $expectedError = null; + $expectedWarnings = null; + + // Check if a error must be caught + if ('catch' === key($operation)) { + $expectedError = current($operation); + next($operation); + } + + // Check if a warning must be caught + if ('warnings' === key($operation)) { + $expectedWarnings = current($operation); + next($operation); + } + + $endpointInfo = explode('.', key($operation)); + $endpointParams = $this->replaceWithContext(current($operation), $context); + $caller = $this->client; + $namespace = null; + $method = null; + + if (count($endpointInfo) === 1) { + $method = Inflector::camelize($endpointInfo[0]); + } + + if (count($endpointInfo) === 2) { + $namespace = $endpointInfo[0]; + $method = Inflector::camelize($endpointInfo[1]); + } + + if (is_object($endpointParams) === true && property_exists($endpointParams, 'ignore')) { + $ignore = $endpointParams->ignore; + unset($endpointParams->ignore); + + $endpointParams->client['ignore'] = $ignore; + } + + if ($async) { + $endpointParams->client['future'] = true; + } + + list($method, $namespace) = $this->mapEndpoint($method, $namespace); + + if (null !== $namespace) { + $caller = $caller->$namespace(); + } + + if (null === $method) { + self::markTestIncomplete(sprintf('Invalid do operation for test "%s"', $testName)); + } + + if (!method_exists($caller, $method)) { + self::markTestIncomplete(sprintf('Method "%s" not implement in "%s"', $method, get_class($caller))); + } + + // TODO remove this after cat testing situation resolved + if ($caller instanceof Elasticsearch\Namespaces\CatNamespace) { + $endpointParams->format = 'text'; + } + + // Exist* methods have to be manually 'unwrapped' into true/false for async + if (strpos($method, "exist") !== false && $async === true) { + return $this->executeAsyncExistRequest($caller, $method, $endpointParams, $expectedError, $expectedWarnings, $testName); + } + + return $this->executeRequest($caller, $method, $endpointParams, $expectedError, $expectedWarnings, $testName); + } + + /** + * Obtain the response from the server + * + * @param $caller + * @param $method + * @param $endpointParams + * @param $expectedError + * @param $testName + * + * @throws \Exception + * + * @return array|mixed + */ + public function executeRequest($caller, $method, $endpointParams, $expectedError, $expectedWarnings, $testName) + { + try { + $response = $caller->$method($endpointParams); + + while ($response instanceof FutureArrayInterface) { + $response = $response->wait(); + } + + $this->checkForWarnings($expectedWarnings); + + return $response; + } catch (\Exception $exception) { + if (null !== $expectedError) { + return $this->assertException($exception, $expectedError, $testName); + } + + $msg = $exception->getMessage() + ."\nException in ".get_class($caller)." with [$method].\n Context:\n" + .var_export($endpointParams, true); + throw new \Exception($msg, 0, $exception); + } + } + + /** + * Obtain the response when it is an Exists* method. These are converted into + * true/false responses + * + * @param $caller + * @param $method + * @param $endpointParams + * @param $expectedError + * @param $testName + * + * @throws \Exception + * + * @return bool + */ + public function executeAsyncExistRequest($caller, $method, $endpointParams, $expectedError, $expectedWarnings, $testName) + { + try { + + $response = $caller->$method($endpointParams); + + while ($response instanceof FutureArrayInterface) { + $response = $response->wait(); + } + + $this->checkForWarnings($expectedWarnings); + + if ($response['status'] === 200) { + return true; + } else { + return false; + } + } catch (Missing404Exception $exception) { + return false; + } catch (RoutingMissingException $exception) { + return false; + } catch (\Exception $exception) { + if (null !== $expectedError) { + return $this->assertException($exception, $expectedError, $testName); + } + + throw $exception; + } + } + + public function checkForWarnings($expectedWarnings) { + $last = $this->client->transport->getLastConnection()->getLastRequestInfo(); + + + // We have some warnings to check + if ($expectedWarnings !== null) { + if (isset($last['response']['headers']['Warning']) === true) { + foreach ($last['response']['headers']['Warning'] as $warning) { + //$position = array_search($warning, $expectedWarnings); + $position = false; + foreach ($expectedWarnings as $index => $value) { + if (stristr($warning, $value) !== false) { + $position = $index; + break; + } + } + if ($position !== false) { + // found the warning + unset($expectedWarnings[$position]); + } else { + // didn't find, throw error + //throw new \Exception("Expected to find warning [$warning] but did not."); + } + } + if (count($expectedWarnings) > 0) { + print_r($last['response']); + throw new \Exception("Expected to find more warnings: ". print_r($expectedWarnings, true)); + } + } + } + + // Check to make sure we're adding headers + static::assertArrayHasKey('Content-type', $last['request']['headers'], print_r($last['request']['headers'], true)); + static::assertEquals('application/json', $last['request']['headers']['Content-type'][0], print_r($last['request']['headers'], true)); + static::assertArrayHasKey('Accept', $last['request']['headers'], print_r($last['request']['headers'], true)); + static::assertEquals('application/json', $last['request']['headers']['Accept'][0], print_r($last['request']['headers'], true)); + + } + + /** + * Check if a field in the last operation is false + * + * @param $operation + * @param $lastOperationResult + * @param $testName + */ + public function operationIsFalse($operation, $lastOperationResult, &$context, $testName) + { + $value = (bool)$this->resolveValue($lastOperationResult, $operation, $context); + $msg = "Failed to assert that a value is false in test \"$testName\"\n" + ."$operation was [".print_r($value, true)."]" + .var_export($lastOperationResult, true); + static::assertFalse($value, $msg); + + return $lastOperationResult; + } + + /** + * Check if a field in the last operation is true + * + * @param $operation + * @param $lastOperationResult + * @param $testName + */ + public function operationIsTrue($operation, $lastOperationResult, &$context, $testName) + { + $value = $this->resolveValue($lastOperationResult, $operation, $context); + + $msg = "Failed to assert that a value is true in test \"$testName\"\n" + ."$operation was [".print_r($value, true)."]" + .var_export($lastOperationResult, true); + static::assertNotEquals(0, $value, $msg); + static::assertNotFalse($value, $msg); + static::assertNotNull($value, $msg); + static::assertNotEquals('', $msg); + + return $lastOperationResult; + } + + /** + * Check if a field in the last operation match an expected value + * + * @param $operation + * @param $lastOperationResult + * @param $testName + */ + public function operationMatch($operation, $lastOperationResult, &$context, $testName) + { + $key = key($operation); + + if ($key === '$body') { + $match = $lastOperationResult; + } else { + $match = $this->resolveValue($lastOperationResult, $key, $context); + } + + $expected = $this->replaceWithContext(current($operation), $context); + $msg = "Failed to match in test \"$testName\". Expected [" + .var_export($expected, true)."] does not match [".var_export($match, true)."]\n".var_export($lastOperationResult, true); + + if ($expected instanceof \stdClass) { + // Avoid stdClass / array mismatch + $expected = json_decode(json_encode($expected), true); + $match = json_decode(json_encode($match), true); + + static::assertEquals($expected, $match, $msg); + } elseif (is_string($expected) && preg_match('#^/.+?/$#s', $expected)) { + static::assertRegExp($this->formatRegex($expected), $match, $msg); + } else { + static::assertEquals($expected, $match, $msg); + } + + return $lastOperationResult; + } + + /** + * Check if a field in the last operation is greater than or equal a value + * + * @param $operation + * @param $lastOperationResult + * @param $testName + */ + public function operationGreaterThanOrEqual($operation, $lastOperationResult, &$context, $testName) + { + $value = $this->resolveValue($lastOperationResult, key($operation), $context); + $expected = current($operation); + + static::assertGreaterThanOrEqual($expected, $value, 'Failed to gte in test ' . $testName); + + return $lastOperationResult; + } + + /** + * Check if a field in the last operation is greater than a value + * + * @param $operation + * @param $lastOperationResult + * @param $testName + */ + public function operationGreaterThan($operation, $lastOperationResult, &$context, $testName) + { + $value = $this->resolveValue($lastOperationResult, key($operation), $context); + $expected = current($operation); + + static::assertGreaterThan($expected, $value, 'Failed to gt in test ' . $testName); + + return $lastOperationResult; + } + + /** + * Check if a field in the last operation has length of a value + * + * @param $operation + * @param $lastOperationResult + * @param $testName + */ + public function operationLength($operation, $lastOperationResult, &$context, $testName) + { + $value = $this->resolveValue($lastOperationResult, key($operation), $context); + $expected = current($operation); + + static::assertCount($expected, $value, 'Failed to gte in test ' . $testName); + + return $lastOperationResult; + } + + /** + * Set a variable into context from last operation + * + * @param $operation + * @param $lastOperationResult + * @param $context + * @param $testName + */ + public function operationSet($operation, $lastOperationResult, &$context, $testName) + { + $key = key($operation); + $value = $this->resolveValue($lastOperationResult, $key, $context); + $variable = current($operation); + + $context['$' . $variable] = $value; + + return $lastOperationResult; + } + + /** + * Skip an operation depending on Elasticsearch Version + * + * @param $operation + * @param $lastOperationResult + * @param $testName + */ + public function operationSkip($operation, $lastOperationResult, $testName) + { + if (is_object($operation) !== true ) { + return $lastOperationResult; + } + + if (property_exists($operation, 'features') && !in_array($operation->features, static::$supportedFeatures, true)) { + static::markTestSkipped(sprintf('Feature(s) %s not supported in test "%s"', json_encode($operation->features), $testName)); + } + + if (property_exists($operation, 'version')) { + $version = $operation->version; + $version = str_replace(" ", "", $version); + $version = explode("-", $version); + + if (isset($version[0]) && $version[0] == 'all') { + static::markTestSkipped(sprintf('Skip test "%s", as all versions should be skipped (%s)', $testName, $operation->reason)); + } + + if (!isset($version[0]) || $version[0] === "") { + $version[0] = ~PHP_INT_MAX; + } + + if (!isset($version[1]) || $version[1] === "" ) { + $version[1] = PHP_INT_MAX; + } + + if (version_compare(static::$esVersion, $version[0], '>=') && version_compare(static::$esVersion, $version[1], '<=')) { + static::markTestSkipped(sprintf('Skip test "%s", as version %s should be skipped (%s)', $testName, static::$esVersion, $operation->reason)); + } + } + + return $lastOperationResult; + } + + /** + * Assert an expected error + * + * @param \Exception $exception + * @param $expectedError + * @param $testName + * + * @return array + */ + private function assertException(\Exception $exception, $expectedError, $testName) + { + if (is_string($expectedError) && preg_match('#^/.+?/$#', $expectedError)) { + static::assertRegExp($expectedError, $exception->getMessage(), 'Failed to catch error in test ' . $testName); + } elseif ($exception instanceof Missing404Exception && $expectedError === 'missing') { + static::assertTrue(true); + } elseif ($exception instanceof Conflict409Exception && $expectedError === 'conflict') { + static::assertTrue(true); + } elseif ($exception instanceof Forbidden403Exception && $expectedError === 'forbidden') { + static::assertTrue(true); + } elseif ($exception instanceof RequestTimeout408Exception && $expectedError === 'request_timeout') { + static::assertTrue(true); + } elseif ($exception instanceof BadRequest400Exception && $expectedError === 'request') { + static::assertTrue(true); + } elseif ($exception instanceof ServerErrorResponseException && $expectedError === 'request') { + static::assertTrue(true); + } elseif ($exception instanceof \RuntimeException && $expectedError === 'param') { + static::assertTrue(true); + } else { + static::assertContains($expectedError, $exception->getMessage()); + } + + if ($exception->getPrevious() !== null) { + return json_decode($exception->getPrevious()->getMessage(), true); + } + + return json_decode($exception->getMessage(), true); + } + + /** + * Provider list of document to test + * + * @return array + */ + public function yamlProvider() + { + $this->yaml = new Yaml(); + $path = __DIR__ . '/../../../util/elasticsearch/rest-api-spec/src/main/resources/rest-api-spec/test'; + $files = []; + + $finder = new Finder(); + $finder->in($path); + $finder->files(); + $finder->name('*.yaml'); + + $filter = isset($_SERVER['TEST_CASE']) ? $_SERVER['TEST_CASE'] : null; + + /** @var SplFileInfo $file */ + foreach ($finder as $file) { + $files = array_merge($files, $this->splitDocument($file, $path, $filter)); + } + + return $files; + } + + /** + * Return the real namespace / method couple for elasticsearch php + * + * @param string $method + * @param string|null $namespace + * + * @return array + */ + private function mapEndpoint($method, $namespace = null) + { + if (null === $namespace && array_key_exists($method, static::$endpointMapping)) { + return static::$endpointMapping[$method]; + } + + if (null !== $namespace && array_key_exists($namespace, static::$endpointMapping) && array_key_exists($method, static::$endpointMapping[$namespace])) { + return static::$endpointMapping[$namespace][$method]; + } + + return [$method, $namespace]; + } + + /** + * Replace contextual variable into a bunch of data + * + * @param $data + * @param $context + * + * @return mixed + */ + private function replaceWithContext($data, $context) + { + if (empty($context)) { + return $data; + } + + if (is_string($data)) { + if (array_key_exists($data, $context)) { + return $context[$data]; + } + } + + if (!is_array($data) && !$data instanceof \stdClass) { + return $data; + } + + foreach ($data as $key => &$value) { + $value = $this->replaceWithContext($value, $context); + } + + return $data; + } + + /** + * Resolve a value into an array given a specific field + * + * @param $result + * @param $field + * + * @return mixed + */ + private function resolveValue($result, $field, &$context) + { + if (empty($field)) { + return $result; + } + + foreach ($context as $key => $value) { + $field = preg_replace('/('.preg_quote($key, '/').')/', $value, $field); + } + + $operationSplit = explode('.', $field); + $value = $result; + + do { + $key = array_shift($operationSplit); + + if (substr($key, -1) === '\\') { + $key = substr($key, 0, -1) . '.' . array_shift($operationSplit); + } + + if (!is_array($value)) { + return $value; + } + + if (!array_key_exists($key, $value)) { + return false; + } + + $value = $value[$key]; + } while (count($operationSplit) > 0); + + return $value; + } + + /** + * Format a regex for PHP + * + * @param $regex + * + * @return string + */ + private function formatRegex($regex) + { + $regex = trim($regex); + $regex = substr($regex, 1, -1); + $regex = str_replace('/', '\\/', $regex); + $regex = '/' . $regex . '/mx'; + + return $regex; + } + + /** + * Split file content into multiple document + * + * @param SplFileInfo $file + * @param string $path; + * + * @return array + */ + private function splitDocument($file, $path, $filter = null) + { + $fileContent = file_get_contents($file); + $documents = explode("---\n", $fileContent); + $documents = array_filter($documents, function ($item) { + return trim($item) !== ''; + }); + + $documentsParsed = []; + $setup = null; + $setupSkip = false; + $fileName = str_replace($path . '/', '', $file); + + if (array_key_exists($fileName, static::$skippedFiles)) { + echo "Skipping: $fileName. ".static::$skippedFiles[$fileName]."\n"; + return []; + } + + if (null !== $filter && !preg_match('/'.preg_quote($filter, '/').'/', $fileName)) { + return []; + } + $skip = false; + $documentParsed = null; + foreach ($documents as $documentString) { + try { + if (!$setupSkip) { + $documentParsed = $this->yaml->parse($documentString, false, false, true); + $skip = false; + } + } catch (ParseException $exception) { + $documentParsed = sprintf( + "[ParseException]Cannot run this test as it cannot be parsed (%s) in file %s", + $exception->getMessage(), + $fileName + ); + + if (preg_match("#\nsetup:#mx", $documentString)) { + $setupSkip = true; + } + + $skip = true; + } catch (\Exception $exception) { + $documentParsed = sprintf( + "[Exception] Cannot run this test as it generated an exception (%s) in file %s", + $exception->getMessage(), + $fileName + ); + + if (preg_match("#\nsetup:#mx", $documentString)) { + $setupSkip = true; + } + + $skip = true; + } + + if (!$skip && key($documentParsed) === 'setup') { + $setup = $documentParsed; + $setupSkip = $skip; + } else { + $documentsParsed[] = [$documentParsed, $skip || $setupSkip, $setup, $fileName]; + } + } + + return $documentsParsed; + } + + /** + * Clean the cluster + */ + private function clean() + { + $host = static::getHost(); + $ch = curl_init($host."/*"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + + $response = curl_exec($ch); + curl_close($ch); + + $ch = curl_init($host."/_template/*"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + + $response = curl_exec($ch); + curl_close($ch); + + $ch = curl_init($host."/_analyzer/*"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + + $response = curl_exec($ch); + curl_close($ch); + + $ch = curl_init($host."/_snapshot/_all"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + + $response = curl_exec($ch); + curl_close($ch); + if ($response != "{}") { + $response = json_decode($response, true); + foreach ($response as $repo => $settings) { + if ($settings['type'] == 'fs') { + $ch = curl_init($host."/_snapshot/$repo/_all?ignore_unavailable"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + + $snapshots = json_decode(curl_exec($ch), true); + curl_close($ch); + foreach ($snapshots['snapshots'] as $snapshot) { + $snapshotName = $snapshot['snapshot']; + $ch = curl_init($host."/_snapshot/$repo/$snapshotName"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + + $response = curl_exec($ch); + curl_close($ch); + } + $ch = curl_init($host."/_snapshot/$repo"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + + $response = curl_exec($ch); + curl_close($ch); + + } + } + } + + // TODO ewwww... + shell_exec('rm -rf /tmp/test_repo_create_1_loc'); + shell_exec('rm -rf /tmp/test_repo_restore_1_loc'); + shell_exec('rm -rf /tmp/test_cat_repo_1_loc'); + shell_exec('rm -rf /tmp/test_cat_repo_2_loc'); + shell_exec('rm -rf /tmp/test_cat_snapshots_1_loc'); + + $this->waitForYellow(); + } + + /** + * Wait for cluster to be in a "YELLOW" state + */ + private function waitForYellow() + { + $host = static::getHost(); + $ch = curl_init("$host/_cluster/health?wait_for_status=yellow&timeout=50s"); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + + $response = json_decode(curl_exec($ch), true); + + $counter = 0; + while ($response['status'] === 'red') { + sleep(0.5); + $response = json_decode(curl_exec($ch), true); + ++$counter; + + if ($counter > 10) { + $this->log("Aborting test due to failure in clearing cluster.\n"); + $this->log(print_r($response, true)); + exit; + } + } + curl_close($ch); + } +} diff --git a/vendor/elasticsearch/elasticsearch/tests/bootstrap.php b/vendor/elasticsearch/elasticsearch/tests/bootstrap.php new file mode 100644 index 0000000..dca8a29 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/tests/bootstrap.php @@ -0,0 +1,16 @@ +/dev/null + +which java +java -version + + +echo "Downloading Elasticsearch v${ES_VERSION}-SNAPSHOT..." + +ES_URL=$(curl -sS "https://esvm-props.kibana.rocks/builds" | jq -r ".branches[\"$ES_VERSION\"].zip") + +curl -L -o elasticsearch-latest-SNAPSHOT.zip $ES_URL +unzip "elasticsearch-latest-SNAPSHOT.zip" + +echo "Adding repo to config..." +find . -name "elasticsearch.yml" | while read TXT ; do echo 'repositories.url.allowed_urls: ["http://*"]' >> $TXT ; done +find . -name "elasticsearch.yml" | while read TXT ; do echo 'path.repo: ["/tmp"]' >> $TXT ; done + +echo "Starting Elasticsearch v${ES_VERSION}" + +./elasticsearch-*/bin/elasticsearch -d + + +sleep 3 diff --git a/vendor/elasticsearch/elasticsearch/travis/generate_docs.sh b/vendor/elasticsearch/elasticsearch/travis/generate_docs.sh new file mode 100644 index 0000000..90f39e9 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/travis/generate_docs.sh @@ -0,0 +1 @@ +php vendor/sami/sami/sami.php update util/DocsConfig.php \ No newline at end of file diff --git a/vendor/elasticsearch/elasticsearch/util/EnsureClusterAlive.php b/vendor/elasticsearch/elasticsearch/util/EnsureClusterAlive.php new file mode 100644 index 0000000..e3a4b3a --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/util/EnsureClusterAlive.php @@ -0,0 +1,31 @@ + [$_SERVER['ES_TEST_HOST']] +]); + +$count = 0; +while (!$client->ping()) { + $count += 1; + + if ($count > 15) { + throw new \Exception("Live cluster could not be found in 15s!"); + } + sleep(1); +} + diff --git a/vendor/elasticsearch/elasticsearch/util/RestSpecRunner.php b/vendor/elasticsearch/elasticsearch/util/RestSpecRunner.php new file mode 100644 index 0000000..e8f4f23 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/util/RestSpecRunner.php @@ -0,0 +1,29 @@ +workingCopy(dirname(__DIR__) . '/util/elasticsearch'); + +echo "Update elasticsearch submodule\n"; +$git->fetchAll(array('verbose' => true)); + +$hash = $_SERVER['TEST_BUILD_REF']; +echo "Checkout yaml tests (hash: $hash)\n"; +$git->checkout($hash, array('force' => true, 'quiet' => true)); diff --git a/vendor/elasticsearch/elasticsearch/util/SpecParser.php b/vendor/elasticsearch/elasticsearch/util/SpecParser.php new file mode 100755 index 0000000..19dfd93 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/util/SpecParser.php @@ -0,0 +1,215 @@ +#!/usr/bin/env php +loadTemplate('endpoint.twig'); + +$counter = 0; + + +function getApiPath() +{ + $path = dirname(__FILE__).'/elasticsearch/rest-api-spec/api'; + if (file_exists($path) !== true) { + $path = dirname(__FILE__).'/elasticsearch/rest-api-spec/src/main/resources/rest-api-spec/api'; + } + return $path; +} + + +if ($handle = opendir(getApiPath())) { + while (false !== ($entry = readdir($handle))) { + if ($entry != "." && $entry != "..") { + generateTemplate($entry, $template); + } + } + closedir($handle); +} + +function processURLPaths($data) +{ + $final = array(); + + $containsType = false; + $containsIndex = false; + foreach ($data['url']['paths'] as $path) { + $params = array(); + preg_match_all('/{(.*?)}/', $path, $params); + $params = $params[1]; + $count = count($params); + $parsedPath = str_replace('}', '', $path); + $parsedPath = str_replace('{', '$', $parsedPath); + + if (array_search('index', $params) !== false) { + $containsIndex = true; + } + + if (array_search('type', $params) !== false) { + $containsType = true; + } + + $duplicate = false; + foreach ($final as $existing) { + if ($existing['params'] === $params) { + $duplicate = true; + } + } + + if ($duplicate !== true) { + $final[] = array( + 'path' => $path, + 'parsedPath' => $parsedPath, + 'params' => $params, + 'count' => $count + ); + } + } + + /* + foreach ($final as &$existing) { + if ($containsIndex === true && array_search('index', $existing['params']) === false && array_search('type', $existing['params']) !== false) { + $existing['parsedPath'] = '/_all'.$existing['parsedPath']; + } + } + */ + + usort($final, function ($a, $b) { + if ($a['count'] == $b['count']) { + return 0; + } + + return ($a['count'] > $b['count']) ? -1 : 1; + }); + + return $final; +} + +function getDefaultPath($path) +{ + if ($path['count'] === 0) { + return $path['path']; + } else { + $final = str_replace('}', '', $path['path']); + $final = str_replace('{', '$', $final); + + return $final; + } +} + +function forbid($key, $value) +{ + $forbid = array( + 'GET' => array( + '/_nodes/hotthreads', + '/_nodes/{node_id}/hotthreads', + '/_nodes/{metric}' + ), + 'HEAD' => array(), + 'PUT' => array( + '/{index}/{type}/_mapping' + ), + 'POST' => array( + '/_all/{type}/_bulk', + '/_all/{type}/_mget' + ), + 'DELETE' => array( + '/{index}/{type}', + '/{index}/{type}/_mapping' + ), + 'QS' => array( + 'operation_threading', + 'field_data' + ) + ); + + if (isset($forbid['key']) && $forbid['key'] === $value) { + return true; + } else { + return false; + } +} + +function generateTemplate($path, $template) +{ + $ignoreList = array( + 'index.json', 'bulk.json' + ); + + if (array_search($path, $ignoreList) !== false) { + return; + } + + $path = getApiPath().DIRECTORY_SEPARATOR.$path; + $json = file_get_contents($path); + $data = json_decode($json, true); + + reset($data); + $namespace = key($data); + $data = $data[$namespace]; + $namespace = explode('.', $namespace); + + $underscoreNamespace = array( + 'get', + 'put', + 'post', + 'delete', + 'exists', + 'update', + 'create' + ); + + $exceptions = array( + 'delete_by_query', + 'update_by_query', + ); + + if (strpos($namespace[count($namespace)-1], '_')) { + $temp = explode('_', $namespace[count($namespace)-1]); + + if (array_search($temp[0], $underscoreNamespace) !== false && array_search($namespace[count($namespace)-1], $exceptions) === false) { + $namespace[count($namespace)-1] = $temp[1]; + $namespace[] = $temp[0]; + } else { + $namespace[count($namespace)-1] = str_replace('_', '', $namespace[count($namespace)-1]); + } + } + + $data['url']['processed'] = processURLPaths($data); + $data['url']['default'] = getDefaultPath($data['url']['processed'][count($data['url']['processed'])-1]); + if (count($data['methods']) > 1) { + if (in_array("GET", $data['methods'])) { + $data['methods'] = "GET"; + } else { + $data['methods'] = $data['methods'][0]; + } + } + $renderVars = array( + 'json' => $json, + 'data' => $data, + 'namespace' => $namespace, + 'className' => ucfirst($namespace[count($namespace)-1]), + ); + + $ret = $template->render($renderVars); + + $dir = __DIR__.'/output/'.implode('/', array_map('ucfirst', array_splice($namespace, 0, count($namespace)-1))); + + if (substr($dir, -1) !== '/') { + $dir .= '/'; + } + if (!file_exists($dir)) { + echo 'making dir: '.$dir."\n\n"; + $oldumask = umask(0); + mkdir($dir, 0777, true); + umask($oldumask); + } + + echo $dir."\n\n"; + $path = $dir.$renderVars['className'].'.php'; + echo $path."\n\n"; + + file_put_contents($path, $ret); +} diff --git a/vendor/elasticsearch/elasticsearch/util/docsConfig.php b/vendor/elasticsearch/elasticsearch/util/docsConfig.php new file mode 100644 index 0000000..c0e05c3 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/util/docsConfig.php @@ -0,0 +1,30 @@ +files() + ->name('*Namespace.php') + ->name("Client.php") + ->name("ClientBuilder.php") + ->notName("AbstractNamespace.php") + ->in(__DIR__.'/../src/'); + +return new Sami($iterator, array( + 'theme' => 'asciidoc', + 'template_dirs' => array(__DIR__.'/docstheme/'), + 'title' => 'Elasticsearch-php', + 'build_dir' => __DIR__.'/../docs/build', + 'cache_dir' => __DIR__.'/cache/', +)); \ No newline at end of file diff --git a/vendor/elasticsearch/elasticsearch/util/docstheme/layout/base.twig b/vendor/elasticsearch/elasticsearch/util/docstheme/layout/base.twig new file mode 100644 index 0000000..70610e1 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/util/docstheme/layout/base.twig @@ -0,0 +1,13 @@ +{% from "macros.twig" import replace_backslash, sanitize %} + +{% block title %} +{% set parts = title|split("\\") %} +{% set path = parts|slice(0, -1)|join("\\") %} +{% set name = parts|slice(-1, 1)|join("\\") %} + +[[{{ sanitize(replace_backslash(title)) }}]] +=== {{ title }} + +{% endblock %} +{% block content %} +{% endblock %} diff --git a/vendor/elasticsearch/elasticsearch/util/docstheme/macros.twig b/vendor/elasticsearch/elasticsearch/util/docstheme/macros.twig new file mode 100644 index 0000000..9ea1834 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/util/docstheme/macros.twig @@ -0,0 +1,138 @@ +{% macro class_item(class, short = false, indent = "") -%} +{{ indent }}{{ _self.class_name(class, short) }}{% if class.shortdesc %} -- {{ class.shortdesc }}{% endif %} +{%- endmacro -%} + +{% macro class_name(class, short = false, suffix = "") -%} +{% spaceless -%} +{% if class.projectclass or class.phpclass %} +{% if class.projectclass %} +<<{{_self.replace_backslash( (short ? class.shortname : class)~suffix) }},{{ class }}>> +{% else %} +http://php.net/class.{{ class }}[{{ short ? class.shortname : class }}{{ suffix }}] +{% endif %}) +{% else %} +`{{ class }}` +{% endif %} +{%- endspaceless %} +{%- endmacro -%} + +{% macro class_type(class, sentence = false) -%} +{% spaceless %} +{% if class.interface %} +{% if sentence %}an {% endif %} +interface +{%- else -%} +{% if sentence %}{% if class.abstract %}an{% else %}a{% endif %} {% endif %} +{% if class.abstract %}abstract {% endif %} +class +{%- endif -%} +{% endspaceless %} +{%- endmacro -%} + +{% macro hint(hint) -%} +{% spaceless %} +{% if hint.class -%} +{{ _self.class_name(hint.name) }} +{%- elseif hint.name -%} +{{ hint.name }} +{%- endif %} +{% endspaceless %} +{%- endmacro -%} + +{% macro markdown_path(path) -%} +{% spaceless %} +{{- path|replace({".html": ".md"}) -}} +{% endspaceless %} +{%- endmacro -%} + +{% macro replace_backslash(path) -%} + {% spaceless %} + {{- path|replace({"\\": "_"}) -}} + {% endspaceless %} +{%- endmacro -%} + +{% macro back_to_forward(path) -%} + {% spaceless %} + {{- path|replace({"\\": "/"}) -}} + {% endspaceless %} +{%- endmacro -%} + +{% macro sanitize(path) -%} + {% spaceless %} + {{- path|replace({"$": "", "::": "", "__": "-"}) -}} + {% endspaceless %} +{%- endmacro -%} + +{% macro get_namespace(class) -%} + {% spaceless %} + {% if class.shortname == 'Client' %} + $client + {% else %} + $client->{{ class.shortname|lower|replace({"namespace": ""}) }}() + {% endif %} + {% endspaceless %} +{%- endmacro -%} + + + +{% macro param_list(member) -%} + {% spaceless %} + {% for parameter in member.parameters %}${{ parameter.name }},{% endfor %} + {% endspaceless %} +{%- endmacro -%} + +{% macro member_signature(type, member) -%} +* It is a **{{ _self.member_visibility(member) }}{% if member.abstract is defined and member.abstract %} abstract{% endif %}** {{ type }}. +{% if member.final %} +* It is a **finalized** {{ type }}. +{% endif %} +{% if "property" == type %} +{% if member.hint|length > 1 %} +* It can be one of the following types: +{%- for hint in member.hint %} + + * {{ _self.hint(hint) }} +{%- endfor -%} +{%- elseif member.hint|length > 0 %} +* It is a(n) {{ _self.hint(member.hint[0]) }} value. +{%- else %} +* Its type is not specified. +{% endif %} +{% elseif "method" == type %} +{% if member.parameters %} +* It accepts the following parameter(s): +{% for parameter in member.parameters %} + * `${{ parameter.name }}`{% if parameter.hint %} ({% for hint in parameter.hint %}{{ _self.hint(hint) }}{% if not loop.last %}|{% endif %}{% endfor %}){% endif %}{% if parameter.shortdesc %} -- {{ parameter.shortdesc }}{% endif %} + +{% endfor %} +{% endif %} +{% if (member.hint|length > 1) or member.hintDesc %} +{% if member.hintDesc %} +* _Returns:_ {{ member.hintDesc }} +{% else %} +* It can return one of the following values: +{% endif %} +{% for hint in member.hint %} + * {{ _self.hint(hint) }} +{% endfor -%} +{% elseif member.hint|length > 0 %} +* It returns a(n) {{ _self.hint(member.hint[0]) }} value. +{% else %} +* It does not return anything. +{% endif %} +{% if member.exceptions %} +* It throws one of the following exceptions: +{% for exception in member.exceptions %} + * {{ _self.class_name(exception[0]) }}{% if exception[1] is defined and exception[1] is not empty %} -- {{ exception[1] }}{% endif %} + +{% endfor %} +{% endif %} +{% endif %} +{%- endmacro -%} + +{% macro member_visibility(member) -%} +{% spaceless %} +{% if member.private %}private{% elseif member.protected %}protected{% else %}public{% endif %} +{% if member.static %} static{% endif %} +{% endspaceless %} +{%- endmacro -%} diff --git a/vendor/elasticsearch/elasticsearch/util/docstheme/manifest.yml b/vendor/elasticsearch/elasticsearch/util/docstheme/manifest.yml new file mode 100644 index 0000000..c1b212d --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/util/docstheme/manifest.yml @@ -0,0 +1,9 @@ +name: asciidoc + +global: + "pages/namespaces.twig": "namespaces.asciidoc" + "pages/interfaces.twig": "interfaces.asciidoc" + "pages/classes.twig": "classes.asciidoc" + +class: + "pages/class.twig": "%s.asciidoc" diff --git a/vendor/elasticsearch/elasticsearch/util/docstheme/pages/class.twig b/vendor/elasticsearch/elasticsearch/util/docstheme/pages/class.twig new file mode 100644 index 0000000..633ca12 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/util/docstheme/pages/class.twig @@ -0,0 +1,82 @@ +{% extends "layout/base.twig" %} + +{% from "macros.twig" import class_item, class_name, class_type, member_signature, replace_backslash, sanitize, param_list, get_namespace %} + +{% block title %} +{% set title = class %} +{{ parent() }} +{% endblock %} + +{% block content %} +{% if class.shortdesc %} + +{{ class.shortdesc|raw }} +{% endif %} +{% if class.longdesc %} + +*Description* + + +{{ class.longdesc|raw }} +{% endif %} + +{% if class.methods %} + +*Methods* + +The {{ class_type(class) }} defines the following methods: + +{% for method in class.methods %} +{% if method.name != "__construct" %} +* <<{{ sanitize(replace_backslash(method)~"_"~method.name) }},`{{ method.name }}()`>> +{% endif %} +{% endfor %} + +{% for method in class.methods %} +{% if method.name != "__construct" %} + + +{% if class.shortname != "ClientBuilder" %} +[[{{ sanitize(replace_backslash(method)~"_"~method.name) }}]] +.`{{ method.name }}()` +**** +[source,php] +---- +/* +{% if method.shortdesc %} +{{ method.shortdesc|raw }} + ['body'] = (array) Request body +{% endif %} +*/ + +$params = [ + // ... +]; + +$client = ClientBuilder::create()->build(); +$response = {{ get_namespace(class) }}->{{ method.name }}({{ param_list(method)|trim(',') }}); +---- +**** +{% else %} +[[{{ sanitize(replace_backslash(method)~"_"~method.name) }}]] +.`{{ method.name }}()` +**** +[source,php] +---- +/* +{% if method.shortdesc %} + {{ method.shortdesc|raw }} + ['body'] = (array) Request body +{% endif %} +*/ + +---- +**** +{% endif %} +{% endif %} + +{% endfor %} +{% endif %} + +{% endblock %} + diff --git a/vendor/elasticsearch/elasticsearch/util/docstheme/pages/classes.twig b/vendor/elasticsearch/elasticsearch/util/docstheme/pages/classes.twig new file mode 100644 index 0000000..f8c6521 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/util/docstheme/pages/classes.twig @@ -0,0 +1,27 @@ +{% extends "layout/base.twig" %} + +{% from "macros.twig" import class_item, replace_backslash, back_to_forward, sanitize %} + +{% block title %} +[[{{ sanitize(replace_backslash("ElasticsearchPHP_Endpoints")) }}]] +== {{ "Reference - Endpoints" }} +{% endblock %} + +{% block content %} + +This is a complete list of namespaces and their associated endpoints. + +NOTE: This is auto-generated documentation + +{% for class in classes if not class.interface %} +* <<{{ replace_backslash(class) }}, {{ class }}>> +{% else %} +* There are no endpoints available. +{% endfor %} +{% for class in classes if not class.interface %} +include::{{ back_to_forward(class) }}.asciidoc[] +{% else %} +{% endfor %} +{% endblock %} + + diff --git a/vendor/elasticsearch/elasticsearch/util/docstheme/pages/interfaces.twig b/vendor/elasticsearch/elasticsearch/util/docstheme/pages/interfaces.twig new file mode 100644 index 0000000..dfa5a94 --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/util/docstheme/pages/interfaces.twig @@ -0,0 +1,23 @@ +{% extends "layout/base.twig" %} + +{% from "macros.twig" import class_item, replace_backslash, back_to_forward, sanitize %} + +{% block title %} +[[{{ sanitize(replace_backslash("ElasticsearchPHP_Interfaces")) }}]] +== {{ "Reference - Interfaces" }} +{% endblock %} + +{% block content %} + +This is a complete list of available interfaces: + +{% for interface in classes if interface.interface %} +* <<{{ replace_backslash(interface) }}, {{ interface }}>> +{% else %} +* There are no interfaces available. +{% endfor %} +{% for interface in classes if interface.interface %} +include::{{ back_to_forward(interface) }}.asciidoc[] +{% else %} +{% endfor %} +{% endblock %} diff --git a/vendor/elasticsearch/elasticsearch/util/docstheme/pages/namespaces.twig b/vendor/elasticsearch/elasticsearch/util/docstheme/pages/namespaces.twig new file mode 100644 index 0000000..8b82c2f --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/util/docstheme/pages/namespaces.twig @@ -0,0 +1,23 @@ +{% extends "layout/base.twig" %} + +{% from "macros.twig" import markdown_path, replace_backslash, back_to_forward, sanitize %} + +{% block title %} +[[{{ sanitize(replace_backslash("ElasticsearchPHP_Namespaces")) }}]] +== {{ "Reference - Namespaces" }} +{% endblock %} + +{% block content %} + +This is a complete list of available namespaces: + +{% for namespace in namespaces %} +* <<{{ replace_backslash(namespace) }}, {{ namespace }}>> +{% else %} +* There are no namespaces available. +{% endfor %} +{% for namespace in namespaces %} +include::{{ back_to_forward(namespace) }}.asciidoc[] +{% else %} +{% endfor %} +{% endblock %} diff --git a/vendor/elasticsearch/elasticsearch/util/templates/endpoint.twig b/vendor/elasticsearch/elasticsearch/util/templates/endpoint.twig new file mode 100644 index 0000000..5a704af --- /dev/null +++ b/vendor/elasticsearch/elasticsearch/util/templates/endpoint.twig @@ -0,0 +1,150 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache2 + * @link http://elastic.co + */ + +class {{ className|title }} extends AbstractEndpoint +{ +{% for part, info in data.url.parts %} +{% if part != 'index' and part != 'type' and part != 'id' %} + // {{info.description }} + private ${{part}}; +{% endif %} +{% endfor %} +{% if data.body is not null %} + /** + * @param array $body + * + * @throws \Elasticsearch\Common\Exceptions\InvalidArgumentException + * @return $this + */ + public function setBody($body) + { + if (isset($body) !== true) { + return $this; + } + + $this->body = $body; + return $this; + } + + + +{% endif %} +{% for part, info in data.url.parts %} +{% if part != 'index' and part != 'type' and part != 'id' %} + /** + * @param ${{part}} + * + * @return $this + */ + public function set{{part|title|replace({'_': ""})}}(${{part}}) + { + if (isset(${{part}}) !== true) { + return $this; + } +{% if info.type == 'list' %} + if (is_array(${{part}}) === true) { + ${{part}} = implode(",", ${{part}}); + } +{% endif %} + $this->{{part}} = ${{part}}; + return $this; + } + +{% endif %} +{% endfor %} +{% set exception = '' %} + /** +{% for part, info in data.url.parts %}{% if info.required is not null %}{% set exception = ' * @throws \\Elasticsearch\\Common\\Exceptions\\BadMethodCallException + ' %}{% endif %}{% endfor %} + {% autoescape false %}{{ exception }}{% endautoescape %} + * @return string + */ + protected function getURI() + { +{% for part, info in data.url.parts %} +{% if info.required == true %} + if (isset($this->{{ part }}) !== true) { + throw new Exceptions\RuntimeException( + '{{ part }} is required for {{ className }}' + ); + } +{% endif %} +{% endfor %} +{% for part, info in data.url.parts %} + ${{ part }} = $this->{{ part }}; +{% endfor %} + $uri = "{{ data.url.default }}"; +{% set loopCounter = 0 %} +{% for part, info in data.url.processed %} +{% if info.count > 0 %} +{% set counter = 0 %} +{% if loopCounter != 0 %} + else{% endif %}if ({% for param in info.params %}{% if counter == 0 %}isset(${{ param }}) === true{% else %} && isset(${{ param }}) === true{% endif %}{% set counter = counter + 1 %}{% endfor %}) + { + $uri = "{{ info.parsedPath }}"; + } +{% set loopCounter = 1 %} +{% endif %} +{% endfor %} + + return $uri; + } + + + /** + * @return string[] + */ + protected function getParamWhitelist() + { + return array( +{% for param, options in data.url.params %} + '{{ param }}', +{% endfor %} + ); + } + + +{% if data.body.required == true %} + /** + * @return array + * @throws \Elasticsearch\Common\Exceptions\RuntimeException + */ + protected function getBody() + { + if (isset($this->body) !== true) { + throw new Exceptions\RuntimeException('Body is required for {{ className|title }}'); + } + return $this->body; + } + + +{% endif %} + /** + * @return string + */ + protected function getMethod() + { +{% if data.methods|length > 1 %} + //TODO Fix Me! + return '{{ data.methods|join(',') }}'; +{% else %} + return '{{ data.methods[0] }}'; +{% endif %} + } +} \ No newline at end of file diff --git a/vendor/guzzlehttp/ringphp/.gitignore b/vendor/guzzlehttp/ringphp/.gitignore new file mode 100644 index 0000000..290a945 --- /dev/null +++ b/vendor/guzzlehttp/ringphp/.gitignore @@ -0,0 +1,4 @@ +vendor +build/artifacts/ +composer.lock +docs/_build/ diff --git a/vendor/guzzlehttp/ringphp/.travis.yml b/vendor/guzzlehttp/ringphp/.travis.yml new file mode 100644 index 0000000..e43fbdd --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/CHANGELOG.md b/vendor/guzzlehttp/ringphp/CHANGELOG.md new file mode 100644 index 0000000..d399d82 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/LICENSE b/vendor/guzzlehttp/ringphp/LICENSE new file mode 100644 index 0000000..71d3b78 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/Makefile b/vendor/guzzlehttp/ringphp/Makefile new file mode 100644 index 0000000..21c812e --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/README.rst b/vendor/guzzlehttp/ringphp/README.rst new file mode 100644 index 0000000..10374e8 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/composer.json b/vendor/guzzlehttp/ringphp/composer.json new file mode 100644 index 0000000..22002ef --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/docs/Makefile b/vendor/guzzlehttp/ringphp/docs/Makefile new file mode 100644 index 0000000..51270aa --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/docs/client_handlers.rst b/vendor/guzzlehttp/ringphp/docs/client_handlers.rst new file mode 100644 index 0000000..3151f00 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/docs/client_middleware.rst b/vendor/guzzlehttp/ringphp/docs/client_middleware.rst new file mode 100644 index 0000000..5a2c1a8 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/docs/conf.py b/vendor/guzzlehttp/ringphp/docs/conf.py new file mode 100644 index 0000000..c6404aa --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/docs/futures.rst b/vendor/guzzlehttp/ringphp/docs/futures.rst new file mode 100644 index 0000000..af29cb3 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/docs/index.rst b/vendor/guzzlehttp/ringphp/docs/index.rst new file mode 100644 index 0000000..4bbce63 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/docs/requirements.txt b/vendor/guzzlehttp/ringphp/docs/requirements.txt new file mode 100644 index 0000000..483a4e9 --- /dev/null +++ b/vendor/guzzlehttp/ringphp/docs/requirements.txt @@ -0,0 +1 @@ +sphinx_rtd_theme diff --git a/vendor/guzzlehttp/ringphp/docs/spec.rst b/vendor/guzzlehttp/ringphp/docs/spec.rst new file mode 100644 index 0000000..bc91078 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/docs/testing.rst b/vendor/guzzlehttp/ringphp/docs/testing.rst new file mode 100644 index 0000000..9df2562 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/phpunit.xml.dist b/vendor/guzzlehttp/ringphp/phpunit.xml.dist new file mode 100644 index 0000000..1d19290 --- /dev/null +++ b/vendor/guzzlehttp/ringphp/phpunit.xml.dist @@ -0,0 +1,14 @@ + + + + + tests + + + + + src + + + diff --git a/vendor/guzzlehttp/ringphp/src/Client/ClientUtils.php b/vendor/guzzlehttp/ringphp/src/Client/ClientUtils.php new file mode 100644 index 0000000..2acf92e --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php b/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php new file mode 100644 index 0000000..e00aa4e --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php b/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php new file mode 100644 index 0000000..b45f6c3 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/src/Client/Middleware.php b/vendor/guzzlehttp/ringphp/src/Client/Middleware.php new file mode 100644 index 0000000..6fa7318 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php b/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php new file mode 100644 index 0000000..4bacec1 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/src/Core.php b/vendor/guzzlehttp/ringphp/src/Core.php new file mode 100644 index 0000000..dd7d1a0 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/src/Exception/CancelledException.php b/vendor/guzzlehttp/ringphp/src/Exception/CancelledException.php new file mode 100644 index 0000000..95b353a --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php b/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php new file mode 100644 index 0000000..0a90c93 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php b/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php new file mode 100644 index 0000000..0d25af7 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/src/Future/FutureArray.php b/vendor/guzzlehttp/ringphp/src/Future/FutureArray.php new file mode 100644 index 0000000..3d64c96 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php b/vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php new file mode 100644 index 0000000..58f5f73 --- /dev/null +++ b/vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php @@ -0,0 +1,11 @@ +_value = $this->wait(); + } +} diff --git a/vendor/guzzlehttp/ringphp/tests/Client/CurlFactoryTest.php b/vendor/guzzlehttp/ringphp/tests/Client/CurlFactoryTest.php new file mode 100644 index 0000000..ebde187 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/tests/Client/CurlHandlerTest.php b/vendor/guzzlehttp/ringphp/tests/Client/CurlHandlerTest.php new file mode 100644 index 0000000..ba03b8c --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/tests/Client/CurlMultiHandlerTest.php b/vendor/guzzlehttp/ringphp/tests/Client/CurlMultiHandlerTest.php new file mode 100644 index 0000000..9228f1c --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/tests/Client/MiddlewareTest.php b/vendor/guzzlehttp/ringphp/tests/Client/MiddlewareTest.php new file mode 100644 index 0000000..a47bb30 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/tests/Client/MockHandlerTest.php b/vendor/guzzlehttp/ringphp/tests/Client/MockHandlerTest.php new file mode 100644 index 0000000..26bcd6c --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/tests/Client/Server.php b/vendor/guzzlehttp/ringphp/tests/Client/Server.php new file mode 100644 index 0000000..14665a5 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/tests/Client/StreamHandlerTest.php b/vendor/guzzlehttp/ringphp/tests/Client/StreamHandlerTest.php new file mode 100644 index 0000000..3cb9a8e --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/tests/Client/server.js b/vendor/guzzlehttp/ringphp/tests/Client/server.js new file mode 100644 index 0000000..6a03e33 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/tests/CoreTest.php b/vendor/guzzlehttp/ringphp/tests/CoreTest.php new file mode 100644 index 0000000..49522f2 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureArrayTest.php b/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureArrayTest.php new file mode 100644 index 0000000..82d7efb --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureValueTest.php b/vendor/guzzlehttp/ringphp/tests/Future/CompletedFutureValueTest.php new file mode 100644 index 0000000..6ded40d --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/tests/Future/FutureArrayTest.php b/vendor/guzzlehttp/ringphp/tests/Future/FutureArrayTest.php new file mode 100644 index 0000000..0e09f5a --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/tests/Future/FutureValueTest.php b/vendor/guzzlehttp/ringphp/tests/Future/FutureValueTest.php new file mode 100644 index 0000000..d59c543 --- /dev/null +++ b/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/vendor/guzzlehttp/ringphp/tests/bootstrap.php b/vendor/guzzlehttp/ringphp/tests/bootstrap.php new file mode 100644 index 0000000..017610f --- /dev/null +++ b/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/vendor/guzzlehttp/streams/Makefile b/vendor/guzzlehttp/streams/Makefile new file mode 100644 index 0000000..f4d4284 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/README.rst b/vendor/guzzlehttp/streams/README.rst new file mode 100644 index 0000000..baff63b --- /dev/null +++ b/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/vendor/guzzlehttp/streams/composer.json b/vendor/guzzlehttp/streams/composer.json new file mode 100644 index 0000000..6d70343 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/phpunit.xml.dist b/vendor/guzzlehttp/streams/phpunit.xml.dist new file mode 100644 index 0000000..6e758c1 --- /dev/null +++ b/vendor/guzzlehttp/streams/phpunit.xml.dist @@ -0,0 +1,17 @@ + + + + + tests + + + + + src + + src/functions.php + + + + diff --git a/vendor/guzzlehttp/streams/src/AppendStream.php b/vendor/guzzlehttp/streams/src/AppendStream.php new file mode 100644 index 0000000..94bda71 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/src/AsyncReadStream.php b/vendor/guzzlehttp/streams/src/AsyncReadStream.php new file mode 100644 index 0000000..25ad960 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/src/BufferStream.php b/vendor/guzzlehttp/streams/src/BufferStream.php new file mode 100644 index 0000000..0fffbd6 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/src/CachingStream.php b/vendor/guzzlehttp/streams/src/CachingStream.php new file mode 100644 index 0000000..60bb905 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/src/DroppingStream.php b/vendor/guzzlehttp/streams/src/DroppingStream.php new file mode 100644 index 0000000..56ee80c --- /dev/null +++ b/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/vendor/guzzlehttp/streams/src/Exception/CannotAttachException.php b/vendor/guzzlehttp/streams/src/Exception/CannotAttachException.php new file mode 100644 index 0000000..e631b9f --- /dev/null +++ b/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/vendor/guzzlehttp/streams/src/FnStream.php b/vendor/guzzlehttp/streams/src/FnStream.php new file mode 100644 index 0000000..6b5872d --- /dev/null +++ b/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/vendor/guzzlehttp/streams/src/GuzzleStreamWrapper.php b/vendor/guzzlehttp/streams/src/GuzzleStreamWrapper.php new file mode 100644 index 0000000..4d049a6 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/src/InflateStream.php b/vendor/guzzlehttp/streams/src/InflateStream.php new file mode 100644 index 0000000..978af21 --- /dev/null +++ b/vendor/guzzlehttp/streams/src/InflateStream.php @@ -0,0 +1,27 @@ +stream = new Stream($resource); + } +} diff --git a/vendor/guzzlehttp/streams/src/LazyOpenStream.php b/vendor/guzzlehttp/streams/src/LazyOpenStream.php new file mode 100644 index 0000000..6242ee7 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/src/LimitStream.php b/vendor/guzzlehttp/streams/src/LimitStream.php new file mode 100644 index 0000000..e9fad98 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/src/MetadataStreamInterface.php b/vendor/guzzlehttp/streams/src/MetadataStreamInterface.php new file mode 100644 index 0000000..c1433ad --- /dev/null +++ b/vendor/guzzlehttp/streams/src/MetadataStreamInterface.php @@ -0,0 +1,11 @@ +stream->attach($stream); + } +} diff --git a/vendor/guzzlehttp/streams/src/NullStream.php b/vendor/guzzlehttp/streams/src/NullStream.php new file mode 100644 index 0000000..41ee776 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/src/Stream.php b/vendor/guzzlehttp/streams/src/Stream.php new file mode 100644 index 0000000..7adbc5e --- /dev/null +++ b/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/vendor/guzzlehttp/streams/src/StreamDecoratorTrait.php b/vendor/guzzlehttp/streams/src/StreamDecoratorTrait.php new file mode 100644 index 0000000..39c19c5 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/src/StreamInterface.php b/vendor/guzzlehttp/streams/src/StreamInterface.php new file mode 100644 index 0000000..fd19c6f --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/AppendStreamTest.php b/vendor/guzzlehttp/streams/tests/AppendStreamTest.php new file mode 100644 index 0000000..78798d9 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/AsyncReadStreamTest.php b/vendor/guzzlehttp/streams/tests/AsyncReadStreamTest.php new file mode 100644 index 0000000..8c78995 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/BufferStreamTest.php b/vendor/guzzlehttp/streams/tests/BufferStreamTest.php new file mode 100644 index 0000000..f9bfea2 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/CachingStreamTest.php b/vendor/guzzlehttp/streams/tests/CachingStreamTest.php new file mode 100644 index 0000000..ea969b3 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/DroppingStreamTest.php b/vendor/guzzlehttp/streams/tests/DroppingStreamTest.php new file mode 100644 index 0000000..bb2cb22 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/Exception/SeekExceptionTest.php b/vendor/guzzlehttp/streams/tests/Exception/SeekExceptionTest.php new file mode 100644 index 0000000..fd8cd1a --- /dev/null +++ b/vendor/guzzlehttp/streams/tests/Exception/SeekExceptionTest.php @@ -0,0 +1,16 @@ +assertSame($s, $e->getStream()); + $this->assertContains('10', $e->getMessage()); + } +} diff --git a/vendor/guzzlehttp/streams/tests/FnStreamTest.php b/vendor/guzzlehttp/streams/tests/FnStreamTest.php new file mode 100644 index 0000000..6cc336b --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/GuzzleStreamWrapperTest.php b/vendor/guzzlehttp/streams/tests/GuzzleStreamWrapperTest.php new file mode 100644 index 0000000..33c3ecc --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/InflateStreamTest.php b/vendor/guzzlehttp/streams/tests/InflateStreamTest.php new file mode 100644 index 0000000..ead9356 --- /dev/null +++ b/vendor/guzzlehttp/streams/tests/InflateStreamTest.php @@ -0,0 +1,16 @@ +assertEquals('test', (string) $b); + } +} diff --git a/vendor/guzzlehttp/streams/tests/LazyOpenStreamTest.php b/vendor/guzzlehttp/streams/tests/LazyOpenStreamTest.php new file mode 100644 index 0000000..79e0078 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/LimitStreamTest.php b/vendor/guzzlehttp/streams/tests/LimitStreamTest.php new file mode 100644 index 0000000..efb1dc5 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/NoSeekStreamTest.php b/vendor/guzzlehttp/streams/tests/NoSeekStreamTest.php new file mode 100644 index 0000000..21b7c6d --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/NullStreamTest.php b/vendor/guzzlehttp/streams/tests/NullStreamTest.php new file mode 100644 index 0000000..8e41431 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/PumpStreamTest.php b/vendor/guzzlehttp/streams/tests/PumpStreamTest.php new file mode 100644 index 0000000..2d20ce9 --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/StreamDecoratorTraitTest.php b/vendor/guzzlehttp/streams/tests/StreamDecoratorTraitTest.php new file mode 100644 index 0000000..2ba79ad --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/StreamTest.php b/vendor/guzzlehttp/streams/tests/StreamTest.php new file mode 100644 index 0000000..2985bfb --- /dev/null +++ b/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/vendor/guzzlehttp/streams/tests/UtilsTest.php b/vendor/guzzlehttp/streams/tests/UtilsTest.php new file mode 100644 index 0000000..6e3e3b2 --- /dev/null +++ b/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/vendor/psr/log/.gitignore b/vendor/psr/log/.gitignore new file mode 100644 index 0000000..22d0d82 --- /dev/null +++ b/vendor/psr/log/.gitignore @@ -0,0 +1 @@ +vendor diff --git a/vendor/psr/log/LICENSE b/vendor/psr/log/LICENSE new file mode 100644 index 0000000..474c952 --- /dev/null +++ b/vendor/psr/log/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 PHP Framework Interoperability Group + +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/vendor/psr/log/Psr/Log/AbstractLogger.php b/vendor/psr/log/Psr/Log/AbstractLogger.php new file mode 100644 index 0000000..90e721a --- /dev/null +++ b/vendor/psr/log/Psr/Log/AbstractLogger.php @@ -0,0 +1,128 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000..67f852d --- /dev/null +++ b/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/vendor/psr/log/Psr/Log/LoggerInterface.php b/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000..5ea7243 --- /dev/null +++ b/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,123 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/vendor/psr/log/Psr/Log/NullLogger.php b/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 0000000..d8cd682 --- /dev/null +++ b/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,28 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php new file mode 100644 index 0000000..a0391a5 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php @@ -0,0 +1,140 @@ + ". + * + * Example ->error('Foo') would yield "error Foo". + * + * @return string[] + */ + abstract public function getLogs(); + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + /** + * @expectedException \Psr\Log\InvalidArgumentException + */ + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + if (method_exists($this, 'createPartialMock')) { + $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); + } else { + $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + } + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + + $expected = array('warning DUMMY'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest), + 'object' => new \DateTime, + 'resource' => fopen('php://memory', 'r'), + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } +} + +class DummyTest +{ + public function __toString() + { + } +} diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md new file mode 100644 index 0000000..574bc1c --- /dev/null +++ b/vendor/psr/log/README.md @@ -0,0 +1,45 @@ +PSR Log +======= + +This repository holds all interfaces/classes/traits related to +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). + +Note that this is not a logger of its own. It is merely an interface that +describes a logger. See the specification for more details. + +Usage +----- + +If you need a logger, you can use the interface like this: + +```php +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json new file mode 100644 index 0000000..87934d7 --- /dev/null +++ b/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/react/promise/.gitignore b/vendor/react/promise/.gitignore new file mode 100644 index 0000000..5241c60 --- /dev/null +++ b/vendor/react/promise/.gitignore @@ -0,0 +1,5 @@ +composer.lock +composer.phar +phpunit.xml +build/ +vendor/ diff --git a/vendor/react/promise/.travis.yml b/vendor/react/promise/.travis.yml new file mode 100644 index 0000000..5d0c6ab --- /dev/null +++ b/vendor/react/promise/.travis.yml @@ -0,0 +1,22 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - nightly + - hhvm + +before_install: + - composer self-update + +install: + - composer install + +script: + - ./vendor/bin/phpunit -v --coverage-text --coverage-clover=./build/logs/clover.xml + +after_script: + - if [ -f ./build/logs/clover.xml ]; then travis_retry composer require satooshi/php-coveralls --no-interaction --update-with-dependencies; fi + - if [ -f ./build/logs/clover.xml ]; then php vendor/bin/coveralls -v; fi diff --git a/vendor/react/promise/CHANGELOG.md b/vendor/react/promise/CHANGELOG.md new file mode 100644 index 0000000..484e542 --- /dev/null +++ b/vendor/react/promise/CHANGELOG.md @@ -0,0 +1,96 @@ +CHANGELOG for 2.x +================= + +* 2.5.1 (2017-03-25) + + * Fix circular references when resolving with a promise which follows + itself (#94). + +* 2.5.0 (2016-12-22) + + * Revert automatic cancellation of pending collection promises once the + output promise resolves. This was introduced in 42d86b7 (PR #36, released + in [v2.3.0](https://github.com/reactphp/promise/releases/tag/v2.3.0)) and + was both unintended and backward incompatible. + + If you need automatic cancellation, you can use something like: + + ```php + function allAndCancel(array $promises) + { + return \React\Promise\all($promises) + ->always(function() use ($promises) { + foreach ($promises as $promise) { + if ($promise instanceof \React\Promise\CancellablePromiseInterface) { + $promise->cancel(); + } + } + }); + } + ``` + * `all()` and `map()` functions now preserve the order of the array (#77). + * Fix circular references when resolving a promise with itself (#71). + +* 2.4.1 (2016-05-03) + + * Fix `some()` not cancelling pending promises when too much input promises + reject (16ff799). + +* 2.4.0 (2016-03-31) + + * Support foreign thenables in `resolve()`. + Any object that provides a `then()` method is now assimilated to a trusted + promise that follows the state of this thenable (#52). + * Fix `some()` and `any()` for input arrays containing not enough items + (#34). + +* 2.3.0 (2016-03-24) + + * Allow cancellation of promises returned by functions working on promise + collections (#36). + * Handle `\Throwable` in the same way as `\Exception` (#51 by @joshdifabio). + +* 2.2.2 (2016-02-26) + + * Fix cancellation handlers called multiple times (#47 by @clue). + +* 2.2.1 (2015-07-03) + + * Fix stack error when resolving a promise in its own fulfillment or + rejection handlers. + +* 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 is 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 `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: `Deferred::resolve()` and `Deferred::reject()` no longer return + a promise. diff --git a/vendor/react/promise/LICENSE b/vendor/react/promise/LICENSE new file mode 100644 index 0000000..5919d20 --- /dev/null +++ b/vendor/react/promise/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012-2016 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/vendor/react/promise/README.md b/vendor/react/promise/README.md new file mode 100644 index 0000000..9c0558c --- /dev/null +++ b/vendor/react/promise/README.md @@ -0,0 +1,840 @@ +React/Promise +============= + +A lightweight implementation of +[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP. + +[![Build Status](https://travis-ci.org/reactphp/promise.svg?branch=master)](http://travis-ci.org/reactphp/promise) +[![Coverage Status](https://coveralls.io/repos/github/reactphp/promise/badge.svg?branch=master)](https://coveralls.io/github/reactphp/promise?branch=master) + +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. + +All functions working on promise collections (like `all()`, `race()`, `some()` +etc.) support cancellation. This means, if you call `cancel()` on the returned +promise, all promises in the collection are cancelled. If the collection itself +is a promise which resolves to an array, this promise is also cancelled. + +#### 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 thenable (any object that provides a `then()` method), +a trusted promise that follows the state of the thenable is returned. + +If `$promiseOrValue` is a promise, it will be returned as is. + +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. + +The returned promise will also reject with a `React\Promise\Exception\LengthException` +if `$promisesOrValues` contains 0 items. + +#### 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. + +The returned promise will also reject with a `React\Promise\Exception\LengthException` +if `$promisesOrValues` contains less items than `$howMany`. + +#### 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); + }) + ->otherwise(function (\Exception $x) { + // Propagate the rejection + throw $x; + }) + ->otherwise(function (\Exception $x) { + // Can also propagate by returning another rejection + return React\Promise\reject( + new \Exception($x->getMessage() + 1) + ); + }) + ->otherwise(function ($x) { + echo 'Reject ' . $x->getMessage(); // 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 new \Exception($x + 1); + }) + ->otherwise(function (\Exception $x) { + // Handle the rejection, and don't propagate. + // This is like catch without a rethrow + return $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, the ApiErrorException will be thrown +getJsonResult() + ->done( + // Consume transformed object + function ($jsonResultObject) { + // Do something with $jsonResultObject + } + ); + +// Here we provide a rejection handler which will either throw while debugging +// or log the exception +getJsonResult() + ->done( + function ($jsonResultObject) { + // Do something with $jsonResultObject + }, + function (ApiErrorException $exception) { + if (isDebug()) { + throw $exception; + } 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/vendor/react/promise/composer.json b/vendor/react/promise/composer.json new file mode 100644 index 0000000..2fc4809 --- /dev/null +++ b/vendor/react/promise/composer.json @@ -0,0 +1,29 @@ +{ + "name": "react/promise", + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "license": "MIT", + "authors": [ + {"name": "Jan Sorgalla", "email": "jsorgalla@gmail.com"} + ], + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "autoload": { + "psr-4": { + "React\\Promise\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "autoload-dev": { + "psr-4": { + "React\\Promise\\": "tests/fixtures" + } + }, + "keywords": [ + "promise", + "promises" + ] +} diff --git a/vendor/react/promise/phpunit.xml.dist b/vendor/react/promise/phpunit.xml.dist new file mode 100644 index 0000000..b9a689d --- /dev/null +++ b/vendor/react/promise/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + + ./tests/ + + + + + + ./src/ + + ./src/functions_include.php + + + + diff --git a/vendor/react/promise/src/CancellablePromiseInterface.php b/vendor/react/promise/src/CancellablePromiseInterface.php new file mode 100644 index 0000000..896db2d --- /dev/null +++ b/vendor/react/promise/src/CancellablePromiseInterface.php @@ -0,0 +1,11 @@ +started) { + return; + } + + $this->started = true; + $this->drain(); + } + + public function enqueue($cancellable) + { + if (!method_exists($cancellable, 'then') || !method_exists($cancellable, 'cancel')) { + return; + } + + $length = array_push($this->queue, $cancellable); + + if ($this->started && 1 === $length) { + $this->drain(); + } + } + + private function drain() + { + for ($i = key($this->queue); isset($this->queue[$i]); $i++) { + $cancellable = $this->queue[$i]; + + $exception = null; + + try { + $cancellable->cancel(); + } catch (\Throwable $exception) { + } catch (\Exception $exception) { + } + + unset($this->queue[$i]); + + if ($exception) { + throw $exception; + } + } + + $this->queue = []; + } +} diff --git a/vendor/react/promise/src/Deferred.php b/vendor/react/promise/src/Deferred.php new file mode 100644 index 0000000..f23980c --- /dev/null +++ b/vendor/react/promise/src/Deferred.php @@ -0,0 +1,60 @@ +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/vendor/react/promise/src/Exception/LengthException.php b/vendor/react/promise/src/Exception/LengthException.php new file mode 100644 index 0000000..775c48d --- /dev/null +++ b/vendor/react/promise/src/Exception/LengthException.php @@ -0,0 +1,7 @@ +value = $value; + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null === $onFulfilled) { + return $this; + } + + try { + return resolve($onFulfilled($this->value)); + } catch (\Throwable $exception) { + return new RejectedPromise($exception); + } 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 $this; + } + + 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 $this; + } + + public function cancel() + { + } +} diff --git a/vendor/react/promise/src/LazyPromise.php b/vendor/react/promise/src/LazyPromise.php new file mode 100644 index 0000000..7e3a3d3 --- /dev/null +++ b/vendor/react/promise/src/LazyPromise.php @@ -0,0 +1,63 @@ +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(); + } + + /** + * @internal + * @see Promise::settle() + */ + public function promise() + { + if (null === $this->promise) { + try { + $this->promise = resolve(call_user_func($this->factory)); + } catch (\Throwable $exception) { + $this->promise = new RejectedPromise($exception); + } catch (\Exception $exception) { + $this->promise = new RejectedPromise($exception); + } + } + + return $this->promise; + } +} diff --git a/vendor/react/promise/src/Promise.php b/vendor/react/promise/src/Promise.php new file mode 100644 index 0000000..0261eb3 --- /dev/null +++ b/vendor/react/promise/src/Promise.php @@ -0,0 +1,216 @@ +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 () { + 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 (ExtendedPromiseInterface $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; + } + + $canceller = $this->canceller; + $this->canceller = null; + + $this->call($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 (\Throwable $e) { + $notify($e); + } catch (\Exception $e) { + $notify($e); + } + }; + } else { + $progressHandler = $notify; + } + + $this->handlers[] = function (ExtendedPromiseInterface $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) + { + $promise = $this->unwrap($promise); + + $handlers = $this->handlers; + + $this->progressHandlers = $this->handlers = []; + $this->result = $promise; + + foreach ($handlers as $handler) { + $handler($promise); + } + } + + private function unwrap($promise) + { + $promise = $this->extract($promise); + + while ($promise instanceof self && null !== $promise->result) { + $promise = $this->extract($promise->result); + } + + return $promise; + } + + private function extract($promise) + { + if ($promise instanceof LazyPromise) { + $promise = $promise->promise(); + } + + if ($promise === $this) { + return new RejectedPromise( + new \LogicException('Cannot resolve a promise with itself.') + ); + } + + return $promise; + } + + 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 (\Throwable $e) { + $this->reject($e); + } catch (\Exception $e) { + $this->reject($e); + } + } +} diff --git a/vendor/react/promise/src/PromiseInterface.php b/vendor/react/promise/src/PromiseInterface.php new file mode 100644 index 0000000..d80d114 --- /dev/null +++ b/vendor/react/promise/src/PromiseInterface.php @@ -0,0 +1,11 @@ +reason = $reason; + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null === $onRejected) { + return $this; + } + + try { + return resolve($onRejected($this->reason)); + } catch (\Throwable $exception) { + return new RejectedPromise($exception); + } 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 $this; + } + + 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 $this; + } + + public function cancel() + { + } +} diff --git a/vendor/react/promise/src/UnhandledRejectionException.php b/vendor/react/promise/src/UnhandledRejectionException.php new file mode 100644 index 0000000..a44b7a1 --- /dev/null +++ b/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/vendor/react/promise/src/functions.php b/vendor/react/promise/src/functions.php new file mode 100644 index 0000000..70c0eb7 --- /dev/null +++ b/vendor/react/promise/src/functions.php @@ -0,0 +1,244 @@ +then($resolve, $reject, $notify); + }, $canceller); + } + + return new FulfilledPromise($promiseOrValue); +} + +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) +{ + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($promisesOrValues); + + return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $cancellationQueue) { + resolve($promisesOrValues) + ->done(function ($array) use ($cancellationQueue, $resolve, $reject, $notify) { + if (!is_array($array) || !$array) { + $resolve(); + return; + } + + foreach ($array as $promiseOrValue) { + $cancellationQueue->enqueue($promiseOrValue); + + resolve($promiseOrValue) + ->done($resolve, $reject, $notify); + } + }, $reject, $notify); + }, $cancellationQueue); +} + +function any($promisesOrValues) +{ + return some($promisesOrValues, 1) + ->then(function ($val) { + return array_shift($val); + }); +} + +function some($promisesOrValues, $howMany) +{ + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($promisesOrValues); + + return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $howMany, $cancellationQueue) { + resolve($promisesOrValues) + ->done(function ($array) use ($howMany, $cancellationQueue, $resolve, $reject, $notify) { + if (!is_array($array) || $howMany < 1) { + $resolve([]); + return; + } + + $len = count($array); + + if ($len < $howMany) { + throw new Exception\LengthException( + sprintf( + 'Input array must contain at least %d item%s but contains only %s item%s.', + $howMany, + 1 === $howMany ? '' : 's', + $len, + 1 === $len ? '' : 's' + ) + ); + } + + $toResolve = $howMany; + $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); + } + }; + + $cancellationQueue->enqueue($promiseOrValue); + + resolve($promiseOrValue) + ->done($fulfiller, $rejecter, $notify); + } + }, $reject, $notify); + }, $cancellationQueue); +} + +function map($promisesOrValues, callable $mapFunc) +{ + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($promisesOrValues); + + return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $mapFunc, $cancellationQueue) { + resolve($promisesOrValues) + ->done(function ($array) use ($mapFunc, $cancellationQueue, $resolve, $reject, $notify) { + if (!is_array($array) || !$array) { + $resolve([]); + return; + } + + $toResolve = count($array); + $values = []; + + foreach ($array as $i => $promiseOrValue) { + $cancellationQueue->enqueue($promiseOrValue); + $values[$i] = null; + + resolve($promiseOrValue) + ->then($mapFunc) + ->done( + function ($mapped) use ($i, &$values, &$toResolve, $resolve) { + $values[$i] = $mapped; + + if (0 === --$toResolve) { + $resolve($values); + } + }, + $reject, + $notify + ); + } + }, $reject, $notify); + }, $cancellationQueue); +} + +function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null) +{ + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($promisesOrValues); + + return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) { + resolve($promisesOrValues) + ->done(function ($array) use ($reduceFunc, $initialValue, $cancellationQueue, $resolve, $reject, $notify) { + 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, $cancellationQueue, $total, &$i) { + $cancellationQueue->enqueue($val); + + return $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); + }); + }); + }; + + $cancellationQueue->enqueue($initialValue); + + array_reduce($array, $wrappedReduceFunc, resolve($initialValue)) + ->done($resolve, $reject, $notify); + }, $reject, $notify); + }, $cancellationQueue); +} + +// 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/vendor/react/promise/src/functions_include.php b/vendor/react/promise/src/functions_include.php new file mode 100644 index 0000000..c71decb --- /dev/null +++ b/vendor/react/promise/src/functions_include.php @@ -0,0 +1,5 @@ +enqueue($p); + + $cancellationQueue(); + + $this->assertTrue($p->cancelCalled); + } + + /** @test */ + public function ignoresSimpleCancellable() + { + $p = new SimpleTestCancellable(); + + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($p); + + $cancellationQueue(); + + $this->assertFalse($p->cancelCalled); + } + + /** @test */ + public function callsCancelOnPromisesEnqueuedBeforeStart() + { + $d1 = $this->getCancellableDeferred(); + $d2 = $this->getCancellableDeferred(); + + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($d1->promise()); + $cancellationQueue->enqueue($d2->promise()); + + $cancellationQueue(); + } + + /** @test */ + public function callsCancelOnPromisesEnqueuedAfterStart() + { + $d1 = $this->getCancellableDeferred(); + $d2 = $this->getCancellableDeferred(); + + $cancellationQueue = new CancellationQueue(); + + $cancellationQueue(); + + $cancellationQueue->enqueue($d2->promise()); + $cancellationQueue->enqueue($d1->promise()); + } + + /** @test */ + public function doesNotCallCancelTwiceWhenStartedTwice() + { + $d = $this->getCancellableDeferred(); + + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($d->promise()); + + $cancellationQueue(); + $cancellationQueue(); + } + + /** @test */ + public function rethrowsExceptionsThrownFromCancel() + { + $this->setExpectedException('\Exception', 'test'); + + $mock = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock + ->expects($this->once()) + ->method('cancel') + ->will($this->throwException(new \Exception('test'))); + + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($mock); + + $cancellationQueue(); + } + + private function getCancellableDeferred() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke'); + + return new Deferred($mock); + } +} diff --git a/vendor/react/promise/tests/DeferredTest.php b/vendor/react/promise/tests/DeferredTest.php new file mode 100644 index 0000000..16212e9 --- /dev/null +++ b/vendor/react/promise/tests/DeferredTest.php @@ -0,0 +1,42 @@ + [$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/vendor/react/promise/tests/FulfilledPromiseTest.php b/vendor/react/promise/tests/FulfilledPromiseTest.php new file mode 100644 index 0000000..97fc8f6 --- /dev/null +++ b/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/vendor/react/promise/tests/FunctionAllTest.php b/vendor/react/promise/tests/FunctionAllTest.php new file mode 100644 index 0000000..74c1d7c --- /dev/null +++ b/vendor/react/promise/tests/FunctionAllTest.php @@ -0,0 +1,114 @@ +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); + } + + /** @test */ + public function shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([1, 2, 3])); + + $deferred = new Deferred(); + + all([resolve(1), $deferred->promise(), resolve(3)]) + ->then($mock); + + $deferred->resolve(2); + } +} diff --git a/vendor/react/promise/tests/FunctionAnyTest.php b/vendor/react/promise/tests/FunctionAnyTest.php new file mode 100644 index 0000000..140b551 --- /dev/null +++ b/vendor/react/promise/tests/FunctionAnyTest.php @@ -0,0 +1,204 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with( + $this->callback(function($exception){ + return $exception instanceof LengthException && + 'Input array must contain at least 1 item but contains only 0 items.' === $exception->getMessage(); + }) + ); + + any([]) + ->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldResolveToNullWithNonArrayInput() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + any(null) + ->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); + } + + /** @test */ + public function shouldRejectWhenInputPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + any(reject()) + ->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldCancelInputPromise() + { + $mock = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock + ->expects($this->once()) + ->method('cancel'); + + any($mock)->cancel(); + } + + /** @test */ + public function shouldCancelInputArrayPromises() + { + $mock1 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock1 + ->expects($this->once()) + ->method('cancel'); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->once()) + ->method('cancel'); + + any([$mock1, $mock2])->cancel(); + } + + /** @test */ + public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseFulfills() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + + $deferred = New Deferred($mock); + $deferred->resolve(); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->never()) + ->method('cancel'); + + some([$deferred->promise(), $mock2], 1)->cancel(); + } +} diff --git a/vendor/react/promise/tests/FunctionCheckTypehintTest.php b/vendor/react/promise/tests/FunctionCheckTypehintTest.php new file mode 100644 index 0000000..8449bc1 --- /dev/null +++ b/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/vendor/react/promise/tests/FunctionMapTest.php b/vendor/react/promise/tests/FunctionMapTest.php new file mode 100644 index 0000000..1ea560a --- /dev/null +++ b/vendor/react/promise/tests/FunctionMapTest.php @@ -0,0 +1,198 @@ +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 shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([2, 4, 6])); + + $deferred = new Deferred(); + + map( + [resolve(1), $deferred->promise(), resolve(3)], + $this->mapper() + )->then($mock); + + $deferred->resolve(2); + } + + /** @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); + } + + /** @test */ + public function shouldRejectWhenInputPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + map( + reject(), + $this->mapper() + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldCancelInputPromise() + { + $mock = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock + ->expects($this->once()) + ->method('cancel'); + + map( + $mock, + $this->mapper() + )->cancel(); + } + + /** @test */ + public function shouldCancelInputArrayPromises() + { + $mock1 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock1 + ->expects($this->once()) + ->method('cancel'); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->once()) + ->method('cancel'); + + map( + [$mock1, $mock2], + $this->mapper() + )->cancel(); + } +} diff --git a/vendor/react/promise/tests/FunctionRaceTest.php b/vendor/react/promise/tests/FunctionRaceTest.php new file mode 100644 index 0000000..83770ec --- /dev/null +++ b/vendor/react/promise/tests/FunctionRaceTest.php @@ -0,0 +1,211 @@ +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); + } + + /** @test */ + public function shouldRejectWhenInputPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + race( + reject() + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldCancelInputPromise() + { + $mock = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock + ->expects($this->once()) + ->method('cancel'); + + race($mock)->cancel(); + } + + /** @test */ + public function shouldCancelInputArrayPromises() + { + $mock1 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock1 + ->expects($this->once()) + ->method('cancel'); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->once()) + ->method('cancel'); + + race([$mock1, $mock2])->cancel(); + } + + /** @test */ + public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseFulfills() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + $deferred = New Deferred($mock); + $deferred->resolve(); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->never()) + ->method('cancel'); + + race([$deferred->promise(), $mock2])->cancel(); + } + + /** @test */ + public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + $deferred = New Deferred($mock); + $deferred->reject(); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->never()) + ->method('cancel'); + + race([$deferred->promise(), $mock2])->cancel(); + } +} diff --git a/vendor/react/promise/tests/FunctionReduceTest.php b/vendor/react/promise/tests/FunctionReduceTest.php new file mode 100644 index 0000000..8b43a87 --- /dev/null +++ b/vendor/react/promise/tests/FunctionReduceTest.php @@ -0,0 +1,347 @@ +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); + } + + /** @test */ + public function shouldRejectWhenInputPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + reduce( + reject(), + $this->plus(), + 1 + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldCancelInputPromise() + { + $mock = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock + ->expects($this->once()) + ->method('cancel'); + + reduce( + $mock, + $this->plus(), + 1 + )->cancel(); + } + + /** @test */ + public function shouldCancelInputArrayPromises() + { + $mock1 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock1 + ->expects($this->once()) + ->method('cancel'); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->once()) + ->method('cancel'); + + reduce( + [$mock1, $mock2], + $this->plus(), + 1 + )->cancel(); + } +} diff --git a/vendor/react/promise/tests/FunctionRejectTest.php b/vendor/react/promise/tests/FunctionRejectTest.php new file mode 100644 index 0000000..84b8ec6 --- /dev/null +++ b/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/vendor/react/promise/tests/FunctionResolveTest.php b/vendor/react/promise/tests/FunctionResolveTest.php new file mode 100644 index 0000000..53126bc --- /dev/null +++ b/vendor/react/promise/tests/FunctionResolveTest.php @@ -0,0 +1,171 @@ +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 shouldResolveAThenable() + { + $thenable = new SimpleFulfilledTestThenable(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo('foo')); + + resolve($thenable) + ->then( + $mock, + $this->expectCallableNever() + ); + } + + /** @test */ + public function shouldResolveACancellableThenable() + { + $thenable = new SimpleTestCancellableThenable(); + + $promise = resolve($thenable); + $promise->cancel(); + + $this->assertTrue($thenable->cancelCalled); + } + + /** @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 shouldSupportVeryDeepNestedPromises() + { + $deferreds = []; + + // @TODO Increase count once global-queue is merged + for ($i = 0; $i < 10; $i++) { + $deferreds[] = $d = new Deferred(); + $p = $d->promise(); + + $last = $p; + for ($j = 0; $j < 10; $j++) { + $last = $last->then(function($result) { + return $result; + }); + } + } + + $p = null; + foreach ($deferreds as $d) { + if ($p) { + $d->resolve($p); + } + + $p = $d->promise(); + } + + $deferreds[0]->resolve(true); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(true)); + + $deferreds[0]->promise()->then($mock); + } + + /** @test */ + public function returnsExtendePromiseForSimplePromise() + { + $promise = $this + ->getMockBuilder('React\Promise\PromiseInterface') + ->getMock(); + + $this->assertInstanceOf('React\Promise\ExtendedPromiseInterface', resolve($promise)); + } +} diff --git a/vendor/react/promise/tests/FunctionSomeTest.php b/vendor/react/promise/tests/FunctionSomeTest.php new file mode 100644 index 0000000..276b54b --- /dev/null +++ b/vendor/react/promise/tests/FunctionSomeTest.php @@ -0,0 +1,258 @@ +createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with( + $this->callback(function($exception){ + return $exception instanceof LengthException && + 'Input array must contain at least 1 item but contains only 0 items.' === $exception->getMessage(); + }) + ); + + some( + [], + 1 + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldRejectWithLengthExceptionWithInputArrayContainingNotEnoughItems() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with( + $this->callback(function($exception){ + return $exception instanceof LengthException && + 'Input array must contain at least 4 items but contains only 3 items.' === $exception->getMessage(); + }) + ); + + some( + [1, 2, 3], + 4 + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldResolveToEmptyArrayWithNonArrayInput() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo([])); + + some( + null, + 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); + } + + /** @test */ + public function shouldRejectWhenInputPromiseRejects() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($this->identicalTo(null)); + + some( + reject(), + 1 + )->then($this->expectCallableNever(), $mock); + } + + /** @test */ + public function shouldCancelInputPromise() + { + $mock = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock + ->expects($this->once()) + ->method('cancel'); + + some($mock, 1)->cancel(); + } + + /** @test */ + public function shouldCancelInputArrayPromises() + { + $mock1 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock1 + ->expects($this->once()) + ->method('cancel'); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->once()) + ->method('cancel'); + + some([$mock1, $mock2], 1)->cancel(); + } + + /** @test */ + public function shouldNotCancelOtherPendingInputArrayPromisesIfEnoughPromisesFulfill() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + $deferred = New Deferred($mock); + $deferred->resolve(); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->never()) + ->method('cancel'); + + some([$deferred->promise(), $mock2], 1); + } + + /** @test */ + public function shouldNotCancelOtherPendingInputArrayPromisesIfEnoughPromisesReject() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + $deferred = New Deferred($mock); + $deferred->reject(); + + $mock2 = $this + ->getMockBuilder('React\Promise\CancellablePromiseInterface') + ->getMock(); + $mock2 + ->expects($this->never()) + ->method('cancel'); + + some([$deferred->promise(), $mock2], 2); + } +} diff --git a/vendor/react/promise/tests/LazyPromiseTest.php b/vendor/react/promise/tests/LazyPromiseTest.php new file mode 100644 index 0000000..b630881 --- /dev/null +++ b/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/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php b/vendor/react/promise/tests/PromiseAdapter/CallbackPromiseAdapter.php new file mode 100644 index 0000000..bdedf46 --- /dev/null +++ b/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/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php b/vendor/react/promise/tests/PromiseAdapter/PromiseAdapterInterface.php new file mode 100644 index 0000000..9157cd4 --- /dev/null +++ b/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()); + } +} diff --git a/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php b/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php new file mode 100644 index 0000000..d722d75 --- /dev/null +++ b/vendor/react/promise/tests/PromiseTest/CancelTestTrait.php @@ -0,0 +1,231 @@ +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 cancelShouldNotTriggerCancellerWhenCancellingOneChildrenMultipleTimes() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableNever()); + + $child1 = $adapter->promise() + ->then() + ->then(); + + $child2 = $adapter->promise() + ->then(); + + $child1->cancel(); + $child1->cancel(); + } + + /** @test */ + public function cancelShouldTriggerCancellerOnlyOnceWhenCancellingMultipleTimes() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce()); + + $adapter->promise()->cancel(); + $adapter->promise()->cancel(); + } + + /** @test */ + public function cancelShouldAlwaysTriggerCancellerWhenCalledOnRootPromise() + { + $adapter = $this->getPromiseTestAdapter($this->expectCallableOnce()); + + $adapter->promise() + ->then() + ->then(); + + $adapter->promise() + ->then(); + + $adapter->promise()->cancel(); + } +} diff --git a/vendor/react/promise/tests/PromiseTest/FullTestTrait.php b/vendor/react/promise/tests/PromiseTest/FullTestTrait.php new file mode 100644 index 0000000..3ce45d6 --- /dev/null +++ b/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/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php b/vendor/react/promise/tests/PromiseTest/PromiseFulfilledTestTrait.php new file mode 100644 index 0000000..428230b --- /dev/null +++ b/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/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php b/vendor/react/promise/tests/PromiseTest/PromisePendingTestTrait.php new file mode 100644 index 0000000..a4f48ee --- /dev/null +++ b/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/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php b/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php new file mode 100644 index 0000000..98d1dcf --- /dev/null +++ b/vendor/react/promise/tests/PromiseTest/PromiseRejectedTestTrait.php @@ -0,0 +1,512 @@ +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 unhandledRejectionExceptionThrownByDoneHoldsRejectionValue() + { + $adapter = $this->getPromiseTestAdapter(); + + $expected = new \stdClass(); + + $adapter->reject($expected); + + try { + $adapter->promise()->done(); + } catch (UnhandledRejectionException $e) { + $this->assertSame($expected, $e->getReason()); + return; + } + + $this->fail(); + } + + /** @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/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php b/vendor/react/promise/tests/PromiseTest/PromiseSettledTestTrait.php new file mode 100644 index 0000000..e363b6d --- /dev/null +++ b/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/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php b/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php new file mode 100644 index 0000000..063f178 --- /dev/null +++ b/vendor/react/promise/tests/PromiseTest/RejectTestTrait.php @@ -0,0 +1,368 @@ +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(null, function ($value) use ($adapter) { + $adapter->reject(3); + + return Promise\reject($value); + }) + ->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/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php b/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php new file mode 100644 index 0000000..0736d35 --- /dev/null +++ b/vendor/react/promise/tests/PromiseTest/ResolveTestTrait.php @@ -0,0 +1,312 @@ +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(function ($value) use ($adapter) { + $adapter->resolve(3); + + return $value; + }) + ->then( + $mock, + $this->expectCallableNever() + ); + + $adapter->resolve(1); + $adapter->resolve(2); + } + + /** + * @test + */ + public function resolveShouldRejectWhenResolvedWithItself() + { + $adapter = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with(new \LogicException('Cannot resolve a promise with itself.')); + + $adapter->promise() + ->then( + $this->expectCallableNever(), + $mock + ); + + $adapter->resolve($adapter->promise()); + } + + /** + * @test + */ + public function resolveShouldRejectWhenResolvedWithAPromiseWhichFollowsItself() + { + $adapter1 = $this->getPromiseTestAdapter(); + $adapter2 = $this->getPromiseTestAdapter(); + + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with(new \LogicException('Cannot resolve a promise with itself.')); + + $promise1 = $adapter1->promise(); + + $promise2 = $adapter2->promise(); + + $promise2->then( + $this->expectCallableNever(), + $mock + ); + + $adapter1->resolve($promise2); + $adapter2->resolve($promise1); + } + + /** @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/vendor/react/promise/tests/RejectedPromiseTest.php b/vendor/react/promise/tests/RejectedPromiseTest.php new file mode 100644 index 0000000..c886b00 --- /dev/null +++ b/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/vendor/react/promise/tests/Stub/CallableStub.php b/vendor/react/promise/tests/Stub/CallableStub.php new file mode 100644 index 0000000..0120893 --- /dev/null +++ b/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 + ->getMockBuilder('React\\Promise\Stub\CallableStub') + ->getMock(); + } +} diff --git a/vendor/react/promise/tests/bootstrap.php b/vendor/react/promise/tests/bootstrap.php new file mode 100644 index 0000000..9b7f872 --- /dev/null +++ b/vendor/react/promise/tests/bootstrap.php @@ -0,0 +1,7 @@ +addPsr4('React\\Promise\\', __DIR__); diff --git a/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php b/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php new file mode 100644 index 0000000..ef4d530 --- /dev/null +++ b/vendor/react/promise/tests/fixtures/SimpleFulfilledTestPromise.php @@ -0,0 +1,21 @@ +cancelCalled = true; + } +} diff --git a/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php b/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php new file mode 100644 index 0000000..c0f1593 --- /dev/null +++ b/vendor/react/promise/tests/fixtures/SimpleTestCancellableThenable.php @@ -0,0 +1,18 @@ +cancelCalled = true; + } +} diff --git a/views/admin/admin/collections.php b/views/admin/admin/collections.php new file mode 100755 index 0000000..2413ecd --- /dev/null +++ b/views/admin/admin/collections.php @@ -0,0 +1,35 @@ + + + __('Solr Search | Collection Configuration'), +)); +?> + +
+ +partial('admin/partials/navigation.php', array( + 'tab' => 'collections' +)); +?> + + + +

The collections selected here will be excluded from indexing in +Solr.

+ + +
diff --git a/views/admin/admin/fields.php b/views/admin/admin/fields.php new file mode 100755 index 0000000..8e81caa --- /dev/null +++ b/views/admin/admin/fields.php @@ -0,0 +1,72 @@ + + + + + + __('Solr Search | Field Configuration'), +)); ?> + +
+ + partial('admin/partials/navigation.php', array( + 'tab' => 'fields' + )); ?> + +
+ +

+ + +
+ $group): ?> + +

+ +

+ +
+ + + + + + + + + + + + + + partial('admin/partials/field.php', array( + 'field' => $field + )); ?> + + + +
+
+ + + + + Load New Elements + + formSubmit('submit', __('Update Search Fields')); ?> + +
+ +
+
+ + diff --git a/views/admin/admin/partials/field.php b/views/admin/admin/partials/field.php new file mode 100755 index 0000000..6ee91b1 --- /dev/null +++ b/views/admin/admin/partials/field.php @@ -0,0 +1,45 @@ + + + + + + + + + getOriginalLabel(); ?> + + + + + + + + + + + $opt): ?>checked="checked" + type="checkbox" + /> + + + + diff --git a/views/admin/admin/partials/navigation.php b/views/admin/admin/partials/navigation.php new file mode 100755 index 0000000..5518318 --- /dev/null +++ b/views/admin/admin/partials/navigation.php @@ -0,0 +1,20 @@ + + + diff --git a/views/admin/admin/reindex.php b/views/admin/admin/reindex.php new file mode 100755 index 0000000..b4556e1 --- /dev/null +++ b/views/admin/admin/reindex.php @@ -0,0 +1,27 @@ + + + __('Solr Search | Index Items') +)); ?> + +partial('admin/partials/navigation.php', array( + 'tab' => 'reindex' +)); ?> + +
+

+ +

+ +
+ + diff --git a/views/admin/admin/results.php b/views/admin/admin/results.php new file mode 100755 index 0000000..c2f5871 --- /dev/null +++ b/views/admin/admin/results.php @@ -0,0 +1,26 @@ + + + __('Solr Search | Results Configuration') +)); ?> + +partial('admin/partials/navigation.php', array( + 'tab' => 'results' +)); ?> + +
+

+ + +
+ + diff --git a/views/admin/admin/server.php b/views/admin/admin/server.php new file mode 100755 index 0000000..3bb19b4 --- /dev/null +++ b/views/admin/admin/server.php @@ -0,0 +1,26 @@ + + + __('Solr Search | Server Configuration') +)); ?> + +partial('admin/partials/navigation.php', array( + 'tab' => 'server' +)); ?> + +
+

+ + +
+ + diff --git a/views/admin/javascripts/accordion.js b/views/admin/javascripts/accordion.js new file mode 100755 index 0000000..fb3fb0e --- /dev/null +++ b/views/admin/javascripts/accordion.js @@ -0,0 +1,18 @@ + +/** + * @package omeka + * @subpackage solr-search + * @copyright 2012 Rector and Board of Visitors, University of Virginia + * @license http://www.apache.org/licenses/LICENSE-2.0.html + */ + +jQuery(function($) { + + $('#facets-form').accordion({ + header: 'h3.fieldset', + autoHeight: false, + collapsible: true, + heightStyle: "content" + }); + +}); diff --git a/views/shared/common/pagination.php b/views/shared/common/pagination.php new file mode 100755 index 0000000..2f300bc --- /dev/null +++ b/views/shared/common/pagination.php @@ -0,0 +1,19 @@ + + +pageCount > 1): ?> + + + diff --git a/views/shared/css/fields.css b/views/shared/css/fields.css new file mode 100755 index 0000000..a3fa9e4 --- /dev/null +++ b/views/shared/css/fields.css @@ -0,0 +1,1171 @@ +/* line 109, sass/_mixins.scss */ +.clearfix { + *zoom: 1; +} +/* line 99, sass/_mixins.scss */ +.clearfix:before, .clearfix:after { + display: table; + content: ""; +} +/* line 104, sass/_mixins.scss */ +.clearfix:after { + clear: both; +} + +/* line 201, sass/_mixins.scss */ +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +/* line 382, sass/_mixins.scss */ +.input-block-level { + display: block; + width: 100%; + min-height: 28px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; +} + +/* line 740, sass/_mixins.scss */ +.clearfix { + *zoom: 1; +} +/* line 730, sass/_mixins.scss */ +.clearfix:before, .clearfix:after { + display: table; + content: ""; +} +/* line 735, sass/_mixins.scss */ +.clearfix:after { + clear: both; +} + +/* line 835, sass/_mixins.scss */ +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +/* line 1016, sass/_mixins.scss */ +.input-block-level { + display: block; + width: 100%; + min-height: 28px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; +} + +/* line 14, sass/_buttons.scss */ +.btn { + display: inline-block; + *display: inline; + *zoom: 1; + padding: 4px 10px 4px; + margin-bottom: 0; + font-size: 13px; + line-height: 18px; + *line-height: 20px; + color: #333; + text-align: center; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + vertical-align: middle; + cursor: pointer; + background-color: whitesmoke; + background-image: -moz-linear-gradient(top, #fff, #e6e6e6); + background-image: -ms-linear-gradient(top, #fff, #e6e6e6); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#e6e6e6)); + background-image: -webkit-linear-gradient(top, #fff, #e6e6e6); + background-image: -o-linear-gradient(top, #fff, #e6e6e6); + background-image: linear-gradient(top, #fff, #e6e6e6); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fcfcfc', endColorstr='#e3e3e3', GradientType=0); + border-color: #e6e6e6 #e6e6e6 #bfbfbf; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #e6e6e6; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); + border: 1px solid #ccc; + *border: 0; + border-bottom-color: #b3b3b3; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + *margin-left: .3em; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); +} +/* line 1182, sass/_mixins.scss */ +.btn:hover, .btn:active, .btn.active, .btn.disabled, .btn[disabled] { + background-color: #e6e6e6; + *background-color: #d9d9d9; +} +/* line 1188, sass/_mixins.scss */ +.btn:active, .btn.active { + background-color: #cccccc \9; +} +/* line 778, sass/_mixins.scss */ +.btn:first-child { + *margin-left: 0; +} + +/* line 38, sass/_buttons.scss */ +.btn:hover { + color: #333; + text-decoration: none; + background-color: #e6e6e6; + *background-color: #d9d9d9; + /* Buttons in IE7 don't get borders, so darken on hover */ + background-position: 0 -15px; + -webkit-transition: background-position 0.1s linear; + -moz-transition: background-position 0.1s linear; + -ms-transition: background-position 0.1s linear; + -o-transition: background-position 0.1s linear; + transition: background-position 0.1s linear; +} + +/* line 51, sass/_buttons.scss */ +.btn:focus { + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +/* line 56, sass/_buttons.scss */ +.btn.active, +.btn:active { + background-color: #e6e6e6; + background-color: #d9d9d9 \9; + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); +} + +/* line 67, sass/_buttons.scss */ +.btn.disabled, +.btn[disabled] { + cursor: default; + background-color: #e6e6e6; + background-image: none; + opacity: 0.65; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} + +/* line 81, sass/_buttons.scss */ +.btn-large { + padding: 9px 14px; + font-size: 15px; + line-height: normal; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} + +/* line 87, sass/_buttons.scss */ +.btn-large [class^="icon-"] { + margin-top: 1px; +} + +/* line 92, sass/_buttons.scss */ +.btn-small { + padding: 5px 9px; + font-size: 11px; + line-height: 16px; +} + +/* line 97, sass/_buttons.scss */ +.btn-small [class^="icon-"] { + margin-top: -1px; +} + +/* line 102, sass/_buttons.scss */ +.btn-mini { + padding: 2px 6px; + font-size: 11px; + line-height: 14px; +} + +/* line 114, sass/_buttons.scss */ +.btn-primary, +.btn-primary:hover, +.btn-warning, +.btn-warning:hover, +.btn-danger, +.btn-danger:hover, +.btn-success, +.btn-success:hover, +.btn-info, +.btn-info:hover, +.btn-inverse, +.btn-inverse:hover { + color: #fff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); +} + +/* line 130, sass/_buttons.scss */ +.btn-primary.active, +.btn-warning.active, +.btn-danger.active, +.btn-success.active, +.btn-info.active, +.btn-inverse.active { + color: rgba(255, 255, 255, 0.75); +} + +/* line 141, sass/_buttons.scss */ +.btn { + border-color: #ccc; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); +} + +/* line 146, sass/_buttons.scss */ +.btn-primary { + background-color: #0074cc; + background-image: -moz-linear-gradient(top, #08c, #0055cc); + background-image: -ms-linear-gradient(top, #08c, #0055cc); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#0055cc)); + background-image: -webkit-linear-gradient(top, #08c, #0055cc); + background-image: -o-linear-gradient(top, #08c, #0055cc); + background-image: linear-gradient(top, #08c, #0055cc); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0085c7', endColorstr='#0053c7', GradientType=0); + border-color: #0055cc #0055cc #003580; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #0055cc; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +/* line 1182, sass/_mixins.scss */ +.btn-primary:hover, .btn-primary:active, .btn-primary.active, .btn-primary.disabled, .btn-primary[disabled] { + background-color: #0055cc; + *background-color: #004ab3; +} +/* line 1188, sass/_mixins.scss */ +.btn-primary:active, .btn-primary.active { + background-color: #004099 \9; +} + +/* line 150, sass/_buttons.scss */ +.btn-warning { + background-color: #faa732; + background-image: -moz-linear-gradient(top, #fbb450, #f89406); + background-image: -ms-linear-gradient(top, #fbb450, #f89406); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); + background-image: -webkit-linear-gradient(top, #fbb450, #f89406); + background-image: -o-linear-gradient(top, #fbb450, #f89406); + background-image: linear-gradient(top, #fbb450, #f89406); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb24b', endColorstr='#f39106', GradientType=0); + border-color: #f89406 #f89406 #ad6704; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #f89406; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +/* line 1182, sass/_mixins.scss */ +.btn-warning:hover, .btn-warning:active, .btn-warning.active, .btn-warning.disabled, .btn-warning[disabled] { + background-color: #f89406; + *background-color: #df8505; +} +/* line 1188, sass/_mixins.scss */ +.btn-warning:active, .btn-warning.active { + background-color: #c67605 \9; +} + +/* line 154, sass/_buttons.scss */ +.btn-danger { + background-color: #da4f49; + background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f); + background-image: -ms-linear-gradient(top, #ee5f5b, #bd362f); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f)); + background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f); + background-image: -o-linear-gradient(top, #ee5f5b, #bd362f); + background-image: linear-gradient(top, #ee5f5b, #bd362f); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5a56', endColorstr='#b9352e', GradientType=0); + border-color: #bd362f #bd362f #802420; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #bd362f; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +/* line 1182, sass/_mixins.scss */ +.btn-danger:hover, .btn-danger:active, .btn-danger.active, .btn-danger.disabled, .btn-danger[disabled] { + background-color: #bd362f; + *background-color: #a9302a; +} +/* line 1188, sass/_mixins.scss */ +.btn-danger:active, .btn-danger.active { + background-color: #942a25 \9; +} + +/* line 158, sass/_buttons.scss */ +.btn-success { + background-color: #5bb75b; + background-image: -moz-linear-gradient(top, #62c462, #51a351); + background-image: -ms-linear-gradient(top, #62c462, #51a351); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351)); + background-image: -webkit-linear-gradient(top, #62c462, #51a351); + background-image: -o-linear-gradient(top, #62c462, #51a351); + background-image: linear-gradient(top, #62c462, #51a351); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5ec35e', endColorstr='#4fa04f', GradientType=0); + border-color: #51a351 #51a351 #387038; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #51a351; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +/* line 1182, sass/_mixins.scss */ +.btn-success:hover, .btn-success:active, .btn-success.active, .btn-success.disabled, .btn-success[disabled] { + background-color: #51a351; + *background-color: #499249; +} +/* line 1188, sass/_mixins.scss */ +.btn-success:active, .btn-success.active { + background-color: #408140 \9; +} + +/* line 162, sass/_buttons.scss */ +.btn-info { + background-color: #49afcd; + background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4); + background-image: -ms-linear-gradient(top, #5bc0de, #2f96b4); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4)); + background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4); + background-image: -o-linear-gradient(top, #5bc0de, #2f96b4); + background-image: linear-gradient(top, #5bc0de, #2f96b4); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#57bedd', endColorstr='#2e93b0', GradientType=0); + border-color: #2f96b4 #2f96b4 #1f6377; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #2f96b4; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +/* line 1182, sass/_mixins.scss */ +.btn-info:hover, .btn-info:active, .btn-info.active, .btn-info.disabled, .btn-info[disabled] { + background-color: #2f96b4; + *background-color: #2a85a0; +} +/* line 1188, sass/_mixins.scss */ +.btn-info:active, .btn-info.active { + background-color: #24748c \9; +} + +/* line 166, sass/_buttons.scss */ +.btn-inverse { + background-color: #414141; + background-image: -moz-linear-gradient(top, #555, #222); + background-image: -ms-linear-gradient(top, #555, #222); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#555), to(#222)); + background-image: -webkit-linear-gradient(top, #555, #222); + background-image: -o-linear-gradient(top, #555, #222); + background-image: linear-gradient(top, #555, #222); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#525252', endColorstr='#1f1f1f', GradientType=0); + border-color: #222 #222 black; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + *background-color: #222; + /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +/* line 1182, sass/_mixins.scss */ +.btn-inverse:hover, .btn-inverse:active, .btn-inverse.active, .btn-inverse.disabled, .btn-inverse[disabled] { + background-color: #222; + *background-color: #151515; +} +/* line 1188, sass/_mixins.scss */ +.btn-inverse:active, .btn-inverse.active { + background-color: #090909 \9; +} + +/* line 174, sass/_buttons.scss */ +button.btn, +input[type="submit"].btn { + *padding-top: 2px; + *padding-bottom: 2px; +} +/* line 178, sass/_buttons.scss */ +button.btn::-moz-focus-inner, +input[type="submit"].btn::-moz-focus-inner { + padding: 0; + border: 0; +} +/* line 186, sass/_buttons.scss */ +button.btn.btn-large, +input[type="submit"].btn.btn-large { + *padding-top: 7px; + *padding-bottom: 7px; +} +/* line 190, sass/_buttons.scss */ +button.btn.btn-small, +input[type="submit"].btn.btn-small { + *padding-top: 3px; + *padding-bottom: 3px; +} +/* line 194, sass/_buttons.scss */ +button.btn.btn-mini, +input[type="submit"].btn.btn-mini { + *padding-top: 1px; + *padding-bottom: 1px; +} + +/* line 17, sass/_sprites.scss */ +[class^="icon-"], +[class*=" icon-"] { + display: inline-block; + width: 14px; + height: 14px; + *margin-right: .3em; + line-height: 14px; + vertical-align: text-top; + background-image: url("./img/glyphicons-halflings.png"); + background-position: 14px 14px; + background-repeat: no-repeat; +} +/* line 786, sass/_mixins.scss */ +[class^="icon-"]:last-child, +[class*=" icon-"]:last-child { + *margin-left: 0; +} + +/* line 30, sass/_sprites.scss */ +.icon-white { + background-image: url("./img/glyphicons-halflings-white.png"); +} + +/* line 34, sass/_sprites.scss */ +.icon-glass { + background-position: 0 0; +} + +/* line 35, sass/_sprites.scss */ +.icon-music { + background-position: -24px 0; +} + +/* line 36, sass/_sprites.scss */ +.icon-search { + background-position: -48px 0; +} + +/* line 37, sass/_sprites.scss */ +.icon-envelope { + background-position: -72px 0; +} + +/* line 38, sass/_sprites.scss */ +.icon-heart { + background-position: -96px 0; +} + +/* line 39, sass/_sprites.scss */ +.icon-star { + background-position: -120px 0; +} + +/* line 40, sass/_sprites.scss */ +.icon-star-empty { + background-position: -144px 0; +} + +/* line 41, sass/_sprites.scss */ +.icon-user { + background-position: -168px 0; +} + +/* line 42, sass/_sprites.scss */ +.icon-film { + background-position: -192px 0; +} + +/* line 43, sass/_sprites.scss */ +.icon-th-large { + background-position: -216px 0; +} + +/* line 44, sass/_sprites.scss */ +.icon-th { + background-position: -240px 0; +} + +/* line 45, sass/_sprites.scss */ +.icon-th-list { + background-position: -264px 0; +} + +/* line 46, sass/_sprites.scss */ +.icon-ok { + background-position: -288px 0; +} + +/* line 47, sass/_sprites.scss */ +.icon-remove { + background-position: -312px 0; +} + +/* line 48, sass/_sprites.scss */ +.icon-zoom-in { + background-position: -336px 0; +} + +/* line 49, sass/_sprites.scss */ +.icon-zoom-out { + background-position: -360px 0; +} + +/* line 50, sass/_sprites.scss */ +.icon-off { + background-position: -384px 0; +} + +/* line 51, sass/_sprites.scss */ +.icon-signal { + background-position: -408px 0; +} + +/* line 52, sass/_sprites.scss */ +.icon-cog { + background-position: -432px 0; +} + +/* line 53, sass/_sprites.scss */ +.icon-trash { + background-position: -456px 0; +} + +/* line 55, sass/_sprites.scss */ +.icon-home { + background-position: 0 -24px; +} + +/* line 56, sass/_sprites.scss */ +.icon-file { + background-position: -24px -24px; +} + +/* line 57, sass/_sprites.scss */ +.icon-time { + background-position: -48px -24px; +} + +/* line 58, sass/_sprites.scss */ +.icon-road { + background-position: -72px -24px; +} + +/* line 59, sass/_sprites.scss */ +.icon-download-alt { + background-position: -96px -24px; +} + +/* line 60, sass/_sprites.scss */ +.icon-download { + background-position: -120px -24px; +} + +/* line 61, sass/_sprites.scss */ +.icon-upload { + background-position: -144px -24px; +} + +/* line 62, sass/_sprites.scss */ +.icon-inbox { + background-position: -168px -24px; +} + +/* line 63, sass/_sprites.scss */ +.icon-play-circle { + background-position: -192px -24px; +} + +/* line 64, sass/_sprites.scss */ +.icon-repeat { + background-position: -216px -24px; +} + +/* line 65, sass/_sprites.scss */ +.icon-refresh { + background-position: -240px -24px; +} + +/* line 66, sass/_sprites.scss */ +.icon-list-alt { + background-position: -264px -24px; +} + +/* line 67, sass/_sprites.scss */ +.icon-lock { + background-position: -287px -24px; +} + +/* line 68, sass/_sprites.scss */ +.icon-flag { + background-position: -312px -24px; +} + +/* line 69, sass/_sprites.scss */ +.icon-headphones { + background-position: -336px -24px; +} + +/* line 70, sass/_sprites.scss */ +.icon-volume-off { + background-position: -360px -24px; +} + +/* line 71, sass/_sprites.scss */ +.icon-volume-down { + background-position: -384px -24px; +} + +/* line 72, sass/_sprites.scss */ +.icon-volume-up { + background-position: -408px -24px; +} + +/* line 73, sass/_sprites.scss */ +.icon-qrcode { + background-position: -432px -24px; +} + +/* line 74, sass/_sprites.scss */ +.icon-barcode { + background-position: -456px -24px; +} + +/* line 76, sass/_sprites.scss */ +.icon-tag { + background-position: 0 -48px; +} + +/* line 77, sass/_sprites.scss */ +.icon-tags { + background-position: -25px -48px; +} + +/* line 78, sass/_sprites.scss */ +.icon-book { + background-position: -48px -48px; +} + +/* line 79, sass/_sprites.scss */ +.icon-bookmark { + background-position: -72px -48px; +} + +/* line 80, sass/_sprites.scss */ +.icon-print { + background-position: -96px -48px; +} + +/* line 81, sass/_sprites.scss */ +.icon-camera { + background-position: -120px -48px; +} + +/* line 82, sass/_sprites.scss */ +.icon-font { + background-position: -144px -48px; +} + +/* line 83, sass/_sprites.scss */ +.icon-bold { + background-position: -167px -48px; +} + +/* line 84, sass/_sprites.scss */ +.icon-italic { + background-position: -192px -48px; +} + +/* line 85, sass/_sprites.scss */ +.icon-text-height { + background-position: -216px -48px; +} + +/* line 86, sass/_sprites.scss */ +.icon-text-width { + background-position: -240px -48px; +} + +/* line 87, sass/_sprites.scss */ +.icon-align-left { + background-position: -264px -48px; +} + +/* line 88, sass/_sprites.scss */ +.icon-align-center { + background-position: -288px -48px; +} + +/* line 89, sass/_sprites.scss */ +.icon-align-right { + background-position: -312px -48px; +} + +/* line 90, sass/_sprites.scss */ +.icon-align-justify { + background-position: -336px -48px; +} + +/* line 91, sass/_sprites.scss */ +.icon-list { + background-position: -360px -48px; +} + +/* line 92, sass/_sprites.scss */ +.icon-indent-left { + background-position: -384px -48px; +} + +/* line 93, sass/_sprites.scss */ +.icon-indent-right { + background-position: -408px -48px; +} + +/* line 94, sass/_sprites.scss */ +.icon-facetime-video { + background-position: -432px -48px; +} + +/* line 95, sass/_sprites.scss */ +.icon-picture { + background-position: -456px -48px; +} + +/* line 97, sass/_sprites.scss */ +.icon-pencil { + background-position: 0 -72px; +} + +/* line 98, sass/_sprites.scss */ +.icon-map-marker { + background-position: -24px -72px; +} + +/* line 99, sass/_sprites.scss */ +.icon-adjust { + background-position: -48px -72px; +} + +/* line 100, sass/_sprites.scss */ +.icon-tint { + background-position: -72px -72px; +} + +/* line 101, sass/_sprites.scss */ +.icon-edit { + background-position: -96px -72px; +} + +/* line 102, sass/_sprites.scss */ +.icon-share { + background-position: -120px -72px; +} + +/* line 103, sass/_sprites.scss */ +.icon-check { + background-position: -144px -72px; +} + +/* line 104, sass/_sprites.scss */ +.icon-move { + background-position: -168px -72px; +} + +/* line 105, sass/_sprites.scss */ +.icon-step-backward { + background-position: -192px -72px; +} + +/* line 106, sass/_sprites.scss */ +.icon-fast-backward { + background-position: -216px -72px; +} + +/* line 107, sass/_sprites.scss */ +.icon-backward { + background-position: -240px -72px; +} + +/* line 108, sass/_sprites.scss */ +.icon-play { + background-position: -264px -72px; +} + +/* line 109, sass/_sprites.scss */ +.icon-pause { + background-position: -288px -72px; +} + +/* line 110, sass/_sprites.scss */ +.icon-stop { + background-position: -312px -72px; +} + +/* line 111, sass/_sprites.scss */ +.icon-forward { + background-position: -336px -72px; +} + +/* line 112, sass/_sprites.scss */ +.icon-fast-forward { + background-position: -360px -72px; +} + +/* line 113, sass/_sprites.scss */ +.icon-step-forward { + background-position: -384px -72px; +} + +/* line 114, sass/_sprites.scss */ +.icon-eject { + background-position: -408px -72px; +} + +/* line 115, sass/_sprites.scss */ +.icon-chevron-left { + background-position: -432px -72px; +} + +/* line 116, sass/_sprites.scss */ +.icon-chevron-right { + background-position: -456px -72px; +} + +/* line 118, sass/_sprites.scss */ +.icon-plus-sign { + background-position: 0 -96px; +} + +/* line 119, sass/_sprites.scss */ +.icon-minus-sign { + background-position: -24px -96px; +} + +/* line 120, sass/_sprites.scss */ +.icon-remove-sign { + background-position: -48px -96px; +} + +/* line 121, sass/_sprites.scss */ +.icon-ok-sign { + background-position: -72px -96px; +} + +/* line 122, sass/_sprites.scss */ +.icon-question-sign { + background-position: -96px -96px; +} + +/* line 123, sass/_sprites.scss */ +.icon-info-sign { + background-position: -120px -96px; +} + +/* line 124, sass/_sprites.scss */ +.icon-screenshot { + background-position: -144px -96px; +} + +/* line 125, sass/_sprites.scss */ +.icon-remove-circle { + background-position: -168px -96px; +} + +/* line 126, sass/_sprites.scss */ +.icon-ok-circle { + background-position: -192px -96px; +} + +/* line 127, sass/_sprites.scss */ +.icon-ban-circle { + background-position: -216px -96px; +} + +/* line 128, sass/_sprites.scss */ +.icon-arrow-left { + background-position: -240px -96px; +} + +/* line 129, sass/_sprites.scss */ +.icon-arrow-right { + background-position: -264px -96px; +} + +/* line 130, sass/_sprites.scss */ +.icon-arrow-up { + background-position: -289px -96px; +} + +/* line 131, sass/_sprites.scss */ +.icon-arrow-down { + background-position: -312px -96px; +} + +/* line 132, sass/_sprites.scss */ +.icon-share-alt { + background-position: -336px -96px; +} + +/* line 133, sass/_sprites.scss */ +.icon-resize-full { + background-position: -360px -96px; +} + +/* line 134, sass/_sprites.scss */ +.icon-resize-small { + background-position: -384px -96px; +} + +/* line 135, sass/_sprites.scss */ +.icon-plus { + background-position: -408px -96px; +} + +/* line 136, sass/_sprites.scss */ +.icon-minus { + background-position: -433px -96px; +} + +/* line 137, sass/_sprites.scss */ +.icon-asterisk { + background-position: -456px -96px; +} + +/* line 139, sass/_sprites.scss */ +.icon-exclamation-sign { + background-position: 0 -120px; +} + +/* line 140, sass/_sprites.scss */ +.icon-gift { + background-position: -24px -120px; +} + +/* line 141, sass/_sprites.scss */ +.icon-leaf { + background-position: -48px -120px; +} + +/* line 142, sass/_sprites.scss */ +.icon-fire { + background-position: -72px -120px; +} + +/* line 143, sass/_sprites.scss */ +.icon-eye-open { + background-position: -96px -120px; +} + +/* line 144, sass/_sprites.scss */ +.icon-eye-close { + background-position: -120px -120px; +} + +/* line 145, sass/_sprites.scss */ +.icon-warning-sign { + background-position: -144px -120px; +} + +/* line 146, sass/_sprites.scss */ +.icon-plane { + background-position: -168px -120px; +} + +/* line 147, sass/_sprites.scss */ +.icon-calendar { + background-position: -192px -120px; +} + +/* line 148, sass/_sprites.scss */ +.icon-random { + background-position: -216px -120px; +} + +/* line 149, sass/_sprites.scss */ +.icon-comment { + background-position: -240px -120px; +} + +/* line 150, sass/_sprites.scss */ +.icon-magnet { + background-position: -264px -120px; +} + +/* line 151, sass/_sprites.scss */ +.icon-chevron-up { + background-position: -288px -120px; +} + +/* line 152, sass/_sprites.scss */ +.icon-chevron-down { + background-position: -313px -119px; +} + +/* line 153, sass/_sprites.scss */ +.icon-retweet { + background-position: -336px -120px; +} + +/* line 154, sass/_sprites.scss */ +.icon-shopping-cart { + background-position: -360px -120px; +} + +/* line 155, sass/_sprites.scss */ +.icon-folder-close { + background-position: -384px -120px; +} + +/* line 156, sass/_sprites.scss */ +.icon-folder-open { + background-position: -408px -120px; +} + +/* line 157, sass/_sprites.scss */ +.icon-resize-vertical { + background-position: -432px -119px; +} + +/* line 158, sass/_sprites.scss */ +.icon-resize-horizontal { + background-position: -456px -118px; +} + +/* line 160, sass/_sprites.scss */ +.icon-hdd { + background-position: 0 -144px; +} + +/* line 161, sass/_sprites.scss */ +.icon-bullhorn { + background-position: -24px -144px; +} + +/* line 162, sass/_sprites.scss */ +.icon-bell { + background-position: -48px -144px; +} + +/* line 163, sass/_sprites.scss */ +.icon-certificate { + background-position: -72px -144px; +} + +/* line 164, sass/_sprites.scss */ +.icon-thumbs-up { + background-position: -96px -144px; +} + +/* line 165, sass/_sprites.scss */ +.icon-thumbs-down { + background-position: -120px -144px; +} + +/* line 166, sass/_sprites.scss */ +.icon-hand-right { + background-position: -144px -144px; +} + +/* line 167, sass/_sprites.scss */ +.icon-hand-left { + background-position: -168px -144px; +} + +/* line 168, sass/_sprites.scss */ +.icon-hand-up { + background-position: -192px -144px; +} + +/* line 169, sass/_sprites.scss */ +.icon-hand-down { + background-position: -216px -144px; +} + +/* line 170, sass/_sprites.scss */ +.icon-circle-arrow-right { + background-position: -240px -144px; +} + +/* line 171, sass/_sprites.scss */ +.icon-circle-arrow-left { + background-position: -264px -144px; +} + +/* line 172, sass/_sprites.scss */ +.icon-circle-arrow-up { + background-position: -288px -144px; +} + +/* line 173, sass/_sprites.scss */ +.icon-circle-arrow-down { + background-position: -312px -144px; +} + +/* line 174, sass/_sprites.scss */ +.icon-globe { + background-position: -336px -144px; +} + +/* line 175, sass/_sprites.scss */ +.icon-wrench { + background-position: -360px -144px; +} + +/* line 176, sass/_sprites.scss */ +.icon-tasks { + background-position: -384px -144px; +} + +/* line 177, sass/_sprites.scss */ +.icon-filter { + background-position: -408px -144px; +} + +/* line 178, sass/_sprites.scss */ +.icon-briefcase { + background-position: -432px -144px; +} + +/* line 179, sass/_sprites.scss */ +.icon-fullscreen { + background-position: -456px -144px; +} + +/* line 9, sass/fields.scss */ +#solr-fields td { + vertical-align: middle; +} +/* line 13, sass/fields.scss */ +#solr-fields input[type=text] { + margin: 0; + width: 100%; +} +/* line 18, sass/fields.scss */ +#solr-fields span.original-label { + font-weight: bold; +} + +/* line 24, sass/fields.scss */ +#submit { + margin-top: 2em !important; +} diff --git a/views/shared/css/img/glyphicons-halflings-white.png b/views/shared/css/img/glyphicons-halflings-white.png new file mode 100755 index 0000000000000000000000000000000000000000..3bf6484a29d8da269f9bc874b25493a45fae3bae GIT binary patch literal 8777 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 B7G?kd literal 0 HcmV?d00001 diff --git a/views/shared/css/img/glyphicons-halflings.png b/views/shared/css/img/glyphicons-halflings.png new file mode 100755 index 0000000000000000000000000000000000000000..79bc568c21395d5a5ceab62fadb04457094b2ac7 GIT binary patch literal 13826 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 literal 0 HcmV?d00001 diff --git a/views/shared/css/img/remove.gif b/views/shared/css/img/remove.gif new file mode 100755 index 0000000000000000000000000000000000000000..709272afe2d6cf656616120c99766d85378044aa GIT binary patch literal 111 zcmZ?wbhEHbG2WN3xd$aoaFWlt#V zUl&q7>+-`dNzF@UZ#tv+lZ62UbU*~i3&*};de LIa5`Xk--`Ox!Nb< literal 0 HcmV?d00001 diff --git a/views/shared/css/img/separator.gif b/views/shared/css/img/separator.gif new file mode 100755 index 0000000000000000000000000000000000000000..8df0bea71cb89a1c9399679a73a60f82fd6a2ffd GIT binary patch literal 112 zcmZ?wbhEHbk9nztZ>H2|;`Dgyuj literal 0 HcmV?d00001 diff --git a/views/shared/css/results.css b/views/shared/css/results.css new file mode 100755 index 0000000..b4e13df --- /dev/null +++ b/views/shared/css/results.css @@ -0,0 +1,2 @@ +๏ปฟ.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 10px 4px;margin-bottom:0;font-size:13px;line-height:18px;*line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #fff, #e6e6e6);background-image:-ms-linear-gradient(top, #fff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #fff, #e6e6e6);background-image:-o-linear-gradient(top, #fff, #e6e6e6);background-image:linear-gradient(top, #fff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fcfcfc', endColorstr='#e3e3e3', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);border:1px solid #ccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover{color:#333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-ms-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:9px 14px;font-size:15px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.btn-large [class^="icon-"]{margin-top:1px}.btn-small{padding:5px 9px;font-size:11px;line-height:16px}.btn-small [class^="icon-"]{margin-top:-1px}.btn-mini{padding:2px 6px;font-size:11px;line-height:14px}.btn-primary,.btn-primary:hover,.btn-warning,.btn-warning:hover,.btn-danger,.btn-danger:hover,.btn-success,.btn-success:hover,.btn-info,.btn-info:hover,.btn-inverse,.btn-inverse:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn{border-color:#ccc;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25)}.btn-primary{background-color:#0074cc;background-image:-moz-linear-gradient(top, #08c, #05c);background-image:-ms-linear-gradient(top, #08c, #05c);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#08c), to(#05c));background-image:-webkit-linear-gradient(top, #08c, #05c);background-image:-o-linear-gradient(top, #08c, #05c);background-image:linear-gradient(top, #08c, #05c);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0085c7', endColorstr='#0053c7', GradientType=0);border-color:#05c #05c #003580;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#05c;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{background-color:#05c;*background-color:#004ab3}.btn-primary:active,.btn-primary.active{background-color:#004099 \9}.btn-warning{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb24b', endColorstr='#f39106', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-ms-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(top, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5a56', endColorstr='#b9352e', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-ms-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(top, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5ec35e', endColorstr='#4fa04f', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-ms-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(top, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#57bedd', endColorstr='#2e93b0', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{background-color:#414141;background-image:-moz-linear-gradient(top, #555, #222);background-image:-ms-linear-gradient(top, #555, #222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#555), to(#222));background-image:-webkit-linear-gradient(top, #555, #222);background-image:-o-linear-gradient(top, #555, #222);background-image:linear-gradient(top, #555, #222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#525252', endColorstr='#1f1f1f', GradientType=0);border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);*background-color:#222;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#090909 \9}button.btn,input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}ul{margin-left:1em}#solr-search-form{overflow:hidden;box-sizing:border-box}#solr-search-form span.float-wrap{display:block;padding-right:10px;overflow:hidden}#solr-search-form span.float-wrap input[type="text"]{width:100%}#solr-search-form input[type="submit"]{float:right}#solr-applied-facets ul{margin:0;padding:0}#solr-applied-facets ul li{display:inline}#solr-applied-facets ul li+li:before{content:"ยท "}#solr-facets{width:20%;float:left}#solr-results{margin-left:22%}#solr-results div.result{margin:1em 0}#solr-results div.result a.result-title{font-weight:bold}#solr-results div.result ul.hl em{font-weight:bold} +/*# sourceMappingURL=results.css.map */ diff --git a/views/shared/css/results.css.map b/views/shared/css/results.css.map new file mode 100755 index 0000000..7d2101a --- /dev/null +++ b/views/shared/css/results.css.map @@ -0,0 +1,7 @@ +{ +"version": 3, +"mappings": "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", +"sources": ["sass/_mixins.scss","sass/_buttons.scss","sass/_variables.scss","sass/results.scss"], +"names": [], +"file": "results.css" +} \ No newline at end of file diff --git a/views/shared/css/sass/_buttons.scss b/views/shared/css/sass/_buttons.scss new file mode 100755 index 0000000..9407f9a --- /dev/null +++ b/views/shared/css/sass/_buttons.scss @@ -0,0 +1,199 @@ + +// BUTTON STYLES +// ------------- + +// Updates for SASS: +// - replace $ with $ +// - replace . with @include for mixins +// - pull complex shadow definitions into variables + +// Base styles +// -------------------------------------------------- + +// Core +.btn { + display: inline-block; + @include ie7-inline-block(); + padding: 4px 10px 4px; + margin-bottom: 0; // For input.btn + font-size: $baseFontSize; + line-height: $baseLineHeight; + *line-height: 20px; + color: $grayDark; + text-align: center; + text-shadow: 0 1px 1px rgba(255,255,255,.75); + vertical-align: middle; + cursor: pointer; + @include buttonBackground($btnBackground, $btnBackgroundHighlight); + border: 1px solid $btnBorder; + *border: 0; // Remove the border to prevent IE7's black border on input:focus + border-bottom-color: darken($btnBorder, 10%); + @include border-radius(4px); + @include ie7-restore-left-whitespace(); // Give IE7 some love + $shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05); + @include box-shadow($shadow); +} + +// Hover state +.btn:hover { + color: $grayDark; + text-decoration: none; + background-color: darken($white, 10%); + *background-color: darken($white, 15%); /* Buttons in IE7 don't get borders, so darken on hover */ + background-position: 0 -15px; + + // transition is only when going to hover, otherwise the background + // behind the gradient (there for IE<=9 fallback) gets mismatched + @include transition(background-position .1s linear); +} + +// Focus state for keyboard and accessibility +.btn:focus { + @include tab-focus(); +} + +// Active state +.btn.active, +.btn:active { + background-color: darken($white, 10%); + background-color: darken($white, 15%) \9; + background-image: none; + outline: 0; + $shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05); + @include box-shadow($shadow); +} + +// Disabled state +.btn.disabled, +.btn[disabled] { + cursor: default; + background-color: darken($white, 10%); + background-image: none; + @include opacity(65); + @include box-shadow(none); +} + + +// Button Sizes +// -------------------------------------------------- + +// Large +.btn-large { + padding: 9px 14px; + font-size: $baseFontSize + 2px; + line-height: normal; + @include border-radius(5px); +} +.btn-large [class^="icon-"] { + margin-top: 1px; +} + +// Small +.btn-small { + padding: 5px 9px; + font-size: $baseFontSize - 2px; + line-height: $baseLineHeight - 2px; +} +.btn-small [class^="icon-"] { + margin-top: -1px; +} + +// Mini +.btn-mini { + padding: 2px 6px; + font-size: $baseFontSize - 2px; + line-height: $baseLineHeight - 4px; +} + + +// Alternate buttons +// -------------------------------------------------- + +// Set text color +// ------------------------- +.btn-primary, +.btn-primary:hover, +.btn-warning, +.btn-warning:hover, +.btn-danger, +.btn-danger:hover, +.btn-success, +.btn-success:hover, +.btn-info, +.btn-info:hover, +.btn-inverse, +.btn-inverse:hover { + color: $white; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); +} +// Provide *some* extra contrast for those who can get it +.btn-primary.active, +.btn-warning.active, +.btn-danger.active, +.btn-success.active, +.btn-info.active, +.btn-inverse.active { + color: rgba(255,255,255,.75); +} + +// Set the backgrounds +// ------------------------- +.btn { + // reset here as of 2.0.3 due to Recess property order + border-color: #ccc; + border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25); +} +.btn-primary { + @include buttonBackground($btnPrimaryBackground, $btnPrimaryBackgroundHighlight); +} +// Warning appears are orange +.btn-warning { + @include buttonBackground($btnWarningBackground, $btnWarningBackgroundHighlight); +} +// Danger and error appear as red +.btn-danger { + @include buttonBackground($btnDangerBackground, $btnDangerBackgroundHighlight); +} +// Success appears as green +.btn-success { + @include buttonBackground($btnSuccessBackground, $btnSuccessBackgroundHighlight); +} +// Info appears as a neutral blue +.btn-info { + @include buttonBackground($btnInfoBackground, $btnInfoBackgroundHighlight); +} +// Inverse appears as dark gray +.btn-inverse { + @include buttonBackground($btnInverseBackground, $btnInverseBackgroundHighlight); +} + + +// Cross-browser Jank +// -------------------------------------------------- + +button.btn, +input[type="submit"].btn { + + // Firefox 3.6 only I believe + &::-moz-focus-inner { + padding: 0; + border: 0; + } + + // IE7 has some default padding on button controls + *padding-top: 2px; + *padding-bottom: 2px; + &.btn-large { + *padding-top: 7px; + *padding-bottom: 7px; + } + &.btn-small { + *padding-top: 3px; + *padding-bottom: 3px; + } + &.btn-mini { + *padding-top: 1px; + *padding-bottom: 1px; + } +} + diff --git a/views/shared/css/sass/_mixins.scss b/views/shared/css/sass/_mixins.scss new file mode 100755 index 0000000..c3ca172 --- /dev/null +++ b/views/shared/css/sass/_mixins.scss @@ -0,0 +1,1359 @@ +@mixin transition-background($intype, $in, $outtype, $out) { + transition: widtbh 2s, height 2s, transform 2s; + -moz-transition: width 2s, height 2s, -moz-transform 2s; + -webkit-transition: width 2s, height 2s, -webkit-transform 2s; + -o-transition: width 2s, height 2s,-o-transform 2s; +} + +@mixin box_shadow($top, $left, $blur, $color, $inset: false) { + @if $inset { + -webkit-box-shadow:inset $top $left $blur $color; + -moz-box-shadow:inset $top $left $blur $color; + box-shadow:inset $top $left $blur $color; + } @else { + -webkit-box-shadow: $top $left $blur $color; + -moz-box-shadow: $top $left $blur $color; + box-shadow: $top $left $blur $color; + } +} + +@mixin text-field { + display: inline-block; + outline: none; + text-decoration: none; + font: 14px/100% Arial, Helvetica, sans-serif; + padding: .5em; + text-shadow: 0 1px 1px rgba(0,0,0,.3); + @include rounded(); + @include box-shadow(0, 1px, 2px, rgba(0, 0, 0, 0.2)); +} + +@mixin button($color: $red, $text_color: $white) { + display: inline-block; + outline: none; + cursor: pointer; + text-align: center; + text-decoration: none; + font: 14px/100% Arial, Helvetica, sans-serif; + padding: .5em 2em .55em; + text-shadow: 0 1px 1px rgba(0,0,0,.3); + @include rounded(); + @include box-shadow(0, 1px, 2px, rgba(0, 0, 0, 0.2)); + + color: $text_color !important; + font-weight: bold; + border: solid 1px darken($color, 18%); + background: $color; + @include gradient(saturate($color, 15%), darken($color, 15%)); + + &:hover { + text-decoration: none; + background: saturate($color, 10%); + @include gradient(saturate($color, 5%), darken($color, 5%)); + } + + &:active { + position: relative; + top: 1px; + color: saturate($color, 15%); + @include gradient(saturate($color, 15%), lighten($color, 15%)); + } +} + +@mixin rounded($radius: 0.5em) { + -webkit-border-radius: $radius; + -moz-border-radius: $radius; + border-radius: $radius; +} + +@mixin gradient($from, $to) { + background: -webkit-gradient(linear, left top, left bottom, from($from), to($to)); + background: -moz-linear-gradient(top, $from, $to); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{$from}', endColorstr='#{$to}'); +} + + +// Mixins.less +// Snippets of reusable CSS to develop faster and keep code readable +// ----------------------------------------------------------------- + +// Updates for SASS: +// - use @mixin instead of class definitions +// - replace @ with $ +// - add default of 5px for @size and @square mixins +// - replace grid generation loops with for $i from 1 to $gridColumns +// - explicitly provide .clearfix, .hide-text and .input-block-level +// (line 304) for compatibility with LESS output +// - replace fadein with fade-in +// - pass grid-* mixin arguments to generators, because otherwise the +// generator mixins will use the default variables + +// UTILITY MIXINS +// -------------------------------------------------- + +// Clearfix +// -------- +// For clearing floats like a boss h5bp.com/q +@mixin clearfix { + *zoom: 1; + &:before, + &:after { + display: table; + content: ""; + } + &:after { + clear: both; + } +} + +.clearfix { + @include clearfix(); +} + +// Webkit-style focus +// ------------------ +@mixin tab-focus() { + // Default + outline: thin dotted #333; + // Webkit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +// Center-align a block level element +// ---------------------------------- +@mixin center-block() { + display: block; + margin-left: auto; + margin-right: auto; +} + +// IE7 inline-block +// ---------------- +@mixin ie7-inline-block() { + *display: inline; // IE7 inline-block hack + *zoom: 1; +} + +// IE7 likes to collapse whitespace on either side of the inline-block elements. +// Ems because we're attempting to match the width of a space character. Left +// version is for form buttons, which typically come after other elements, and +// right version is for icons, which come before. Applying both is ok, but it will +// mean that space between those elements will be .6em (~2 space characters) in IE7, +// instead of the 1 space in other browsers. +@mixin ie7-restore-left-whitespace() { + *margin-left: .3em; + + &:first-child { + *margin-left: 0; + } +} + +@mixin ie7-restore-right-whitespace() { + *margin-right: .3em; + + &:last-child { + *margin-left: 0; + } +} + +// Sizing shortcuts +// ------------------------- +@mixin size($height: 5px, $width: 5px) { + width: $width; + height: $height; +} +@mixin square($size: 5px) { + @include size($size, $size); +} + +// Placeholder text +// ------------------------- +@mixin placeholder($color: $placeholderText) { + :-moz-placeholder { + color: $color; + } + ::-webkit-input-placeholder { + color: $color; + } +} + +// Text overflow +// ------------------------- +// Requires inline-block or block for proper styling +@mixin text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// CSS image replacement +// ------------------------- +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 +@mixin hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.hide-text { + @include hide-text(); +} + +// FONTS +// -------------------------------------------------- + +@mixin font-family-serif { + font-family: $serifFontFamily; +} + +@mixin font-family-sans-serif { + font-family: $sansFontFamily; +} + +@mixin font-family-monospace { + font-family: $monoFontFamily; +} + +@mixin font-shorthand ($size: $baseFontSize, $weight: normal, $lineHeight: $baseLineHeight) { + font-size: $size; + font-weight: $weight; + line-height: $lineHeight; +} + +@mixin font-serif($size: $baseFontSize, $weight: normal, $lineHeight: $baseLineHeight) { + @include font-family-serif(); + @include font-shorthand($size, $weight, $lineHeight); +} +@mixin font-sans-serif($size: $baseFontSize, $weight: normal, $lineHeight: $baseLineHeight) { + @include font-family-sans-serif(); + @include font-shorthand($size, $weight, $lineHeight); +} +@mixin font-monospace($size: $baseFontSize, $weight: normal, $lineHeight: $baseLineHeight) { + @include font-family-monospace(); + @include font-shorthand($size, $weight, $lineHeight); +} + + +// FORMS +// -------------------------------------------------- + +// Block level inputs +@mixin input-block-level { + display: block; + width: 100%; + min-height: 28px; // Make inputs at least the height of their button counterpart + @include box-sizing(border-box); // Makes inputs behave like true block-level elements +} + + +// Mixin for form field states +@mixin formFieldState($textColor: #555, $borderColor: #ccc, $backgroundColor: #f5f5f5) { + // Set the text color + > label, + .help-block, + .help-inline { + color: $textColor; + } + // Style inputs accordingly + input, + select, + textarea { + color: $textColor; + border-color: $borderColor; + &:focus { + border-color: darken($borderColor, 10%); + @include box-shadow(0 0 6px lighten($borderColor, 20%)); + } + } + // Give a small background color for input-prepend/-append + .input-prepend .add-on, + .input-append .add-on { + color: $textColor; + background-color: $backgroundColor; + border-color: $textColor; + } +} + + + +// CSS3 PROPERTIES +// -------------------------------------------------- + +// Border Radius +@mixin border-radius($radius: 5px) { + -webkit-border-radius: $radius; + -moz-border-radius: $radius; + border-radius: $radius; +} + +// Drop shadows +@mixin box-shadow($shadow: 0 1px 3px rgba(0,0,0,.25)) { + -webkit-box-shadow: $shadow; + -moz-box-shadow: $shadow; + box-shadow: $shadow; +} + +// Transitions +@mixin transition($transition) { + -webkit-transition: $transition; + -moz-transition: $transition; + -ms-transition: $transition; + -o-transition: $transition; + transition: $transition; +} + +// Transformations +@mixin rotate($degrees) { + -webkit-transform: rotate($degrees); + -moz-transform: rotate($degrees); + -ms-transform: rotate($degrees); + -o-transform: rotate($degrees); + transform: rotate($degrees); +} +@mixin scale($ratio) { + -webkit-transform: scale($ratio); + -moz-transform: scale($ratio); + -ms-transform: scale($ratio); + -o-transform: scale($ratio); + transform: scale($ratio); +} +@mixin translate($x, $y) { + -webkit-transform: translate($x, $y); + -moz-transform: translate($x, $y); + -ms-transform: translate($x, $y); + -o-transform: translate($x, $y); + transform: translate($x, $y); +} +@mixin skew($x, $y) { + -webkit-transform: skew($x, $y); + -moz-transform: skew($x, $y); + -ms-transform: skew($x, $y); + -o-transform: skew($x, $y); + transform: skew($x, $y); +} +@mixin translate3d($x, $y, $z) { + -webkit-transform: translate($x, $y, $z); + -moz-transform: translate($x, $y, $z); + -ms-transform: translate($x, $y, $z); + -o-transform: translate($x, $y, $z); + transform: translate($x, $y, $z); +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden +// See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples +@mixin backface-visibility($visibility){ + -webkit-backface-visibility: $visibility; + -moz-backface-visibility: $visibility; + -ms-backface-visibility: $visibility; + backface-visibility: $visibility; +} + +// Background clipping +// Heads up: FF 3.6 and under need "padding" instead of "padding-box" +@mixin background-clip($clip) { + -webkit-background-clip: $clip; + -moz-background-clip: $clip; + background-clip: $clip; +} + +// Background sizing +@mixin background-size($size) { + -webkit-background-size: $size; + -moz-background-size: $size; + -o-background-size: $size; + background-size: $size; +} + + +// Box sizing +@mixin box-sizing($boxmodel) { + -webkit-box-sizing: $boxmodel; + -moz-box-sizing: $boxmodel; + -ms-box-sizing: $boxmodel; + box-sizing: $boxmodel; +} + +// Uses box-sizing mixin, so must be defined here +.input-block-level { + @include input-block-level(); +} + +// User select +// For selecting text on the page +@mixin user-select($select) { + -webkit-user-select: $select; + -moz-user-select: $select; + -ms-user-select: $select; + -o-user-select: $select; + user-select: $select; +} + +// Resize anything +@mixin resizable($direction) { + resize: $direction; // Options: horizontal, vertical, both + overflow: auto; // Safari fix +} + +// CSS3 Content Columns +@mixin content-columns($columnCount, $columnGap: $gridGutterWidth) { + -webkit-column-count: $columnCount; + -moz-column-count: $columnCount; + column-count: $columnCount; + -webkit-column-gap: $columnGap; + -moz-column-gap: $columnGap; + column-gap: $columnGap; +} + +// Opacity +@mixin opacity($opacity) { + opacity: $opacity / 100; + filter: alpha(opacity=$opacity); +} + + + +// BACKGROUNDS +// -------------------------------------------------- + +// Add an alphatransparency value to any background or border color (via Elyse Holladay) +@mixin translucent-background($color: $white, $alpha: 1) { + background-color: hsla(hue($color), saturation($color), lightness($color), $alpha); +} + +@mixin translucent-border($color: $white, $alpha: 1) { + border-color: hsla(hue($color), saturation($color), lightness($color), $alpha); + @include background-clip(padding-box); +} + +// Gradient Bar Colors for buttons and alerts +@mixin gradientBar($primaryColor, $secondaryColor) { + @include gradient-vertical($primaryColor, $secondaryColor); + border-color: $secondaryColor $secondaryColor darken($secondaryColor, 15%); + border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fade-in(rgba(0,0,0,.1), 0.15); +} + +// Gradients +@mixin gradient-horizontal($startColor: #555, $endColor: #333) { + background-color: $endColor; + background-image: -ms-linear-gradient(left, $startColor, $endColor); // IE10 + background-image: -webkit-gradient(linear, 0 0, 100% 0, from($startColor), to($endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(left, $startColor, $endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(left, $startColor, $endColor); // Opera 11.10 + background-image: linear-gradient(left, $startColor, $endColor); // Le standard + background-image: -moz-linear-gradient(left, $startColor, $endColor); // FF 3.6+ + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{$startColor}', endColorstr='#{$endColor}', GradientType=1); // IE9 and down +} +@mixin gradient-vertical($startColor: #555, $endColor: #333) { + background-color: mix($startColor, $endColor, 60%); + background-image: -ms-linear-gradient(top, $startColor, $endColor); // IE10 + background-image: -webkit-gradient(linear, 0 0, 0 100%, from($startColor), to($endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(top, $startColor, $endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(top, $startColor, $endColor); // Opera 11.10 + background-image: -moz-linear-gradient(top, $startColor, $endColor); // FF 3.6+ + background-image: linear-gradient(top, $startColor, $endColor); // The standard + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{$startColor}', endColorstr='#{$endColor}', GradientType=0); // IE9 and down +} +@mixin gradient-directional($startColor: #555, $endColor: #333, $deg: 45deg) { + background-color: $endColor; + background-repeat: repeat-x; + background-image: -moz-linear-gradient($deg, $startColor, $endColor); // FF 3.6+ + background-image: -ms-linear-gradient($deg, $startColor, $endColor); // IE10 + background-image: -webkit-linear-gradient($deg, $startColor, $endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient($deg, $startColor, $endColor); // Opera 11.10 + background-image: linear-gradient($deg, $startColor, $endColor); // The standard +} +@mixin gradient-vertical-three-colors($startColor: #00b3ee, $midColor: #7a43b6, $colorStop: 50%, $endColor: #c3325f) { + background-color: mix($midColor, $endColor, 80%); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from($startColor), color-stop($colorStop, $midColor), to($endColor)); + background-image: -webkit-linear-gradient($startColor, $midColor $colorStop, $endColor); + background-image: -moz-linear-gradient(top, $startColor, $midColor $colorStop, $endColor); + background-image: -ms-linear-gradient($startColor, $midColor $colorStop, $endColor); + background-image: -o-linear-gradient($startColor, $midColor $colorStop, $endColor); + background-image: linear-gradient($startColor, $midColor $colorStop, $endColor); + background-repeat: no-repeat; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{$startColor}', endColorstr='#{$endColor}', GradientType=0); // IE9 and down, gets no color-stop at all the proper fallback +} +@mixin gradient-radial($innerColor: #555, $outerColor: #333) { + background-color: $outerColor; + background-image: -webkit-gradient(radial, center center, 0, center center, 460, from($innerColor), to($outerColor)); + background-image: -webkit-radial-gradient(circle, $innerColor, $outerColor); + background-image: -moz-radial-gradient(circle, $innerColor, $outerColor); + background-image: -ms-radial-gradient(circle, $innerColor, $outerColor); + background-image: -o-radial-gradient(circle, $innerColor, $outerColor); + background-repeat: no-repeat; +} +@mixin gradient-striped($color, $angle: -45deg) { + background-color: $color; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient($angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient($angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -ms-linear-gradient($angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient($angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: linear-gradient($angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); +} + +// Reset filters for IE +@mixin gradient-reset-filter() { + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + + + +// COMPONENT MIXINS +// -------------------------------------------------- + +// Horizontal dividers +// ------------------------- +// Dividers (basically an hr) within dropdowns and nav lists +@mixin nav-divider { + // IE7 needs a set width since we gave a height. Restricting just + // to IE7 to keep the 1px left/right space in other browsers. + // It is unclear where IE is getting the extra space that we need + // to negative-margin away, but so it goes. + *width: 100%; + height: 1px; + margin: (($baseLineHeight / 2) - 1) 1px; // 8px 1px + *margin: -5px 0 5px; + overflow: hidden; + background-color: #e5e5e5; + border-bottom: 1px solid $white; +} + +// Button backgrounds +// ------------------ +@mixin buttonBackground($startColor, $endColor) { + // gradientBar will set the background to a pleasing blend of these, to support IE<=9 + @include gradientBar($startColor, $endColor); + *background-color: $endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + @include gradient-reset-filter(); + + // in these cases the gradient won't cover the background, so we override + &:hover, &:active, &.active, &.disabled, &[disabled] { + background-color: $endColor; + *background-color: darken($endColor, 5%); + } + + // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves + &:active, + &.active { + background-color: darken($endColor, 10%) \9; + } +} + +// Navbar vertical align +// ------------------------- +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbarVerticalAlign(30px);` to calculate the appropriate top margin. +@mixin navbarVerticalAlign($elementHeight) { + margin-top: ($navbarHeight - $elementHeight) / 2; +} + +// Popover arrows +// ------------------------- +// For tipsies and popovers +@mixin popoverArrow-top($arrowWidth: 5px, $color: $black) { + bottom: 0; + left: 50%; + margin-left: -$arrowWidth; + border-left: $arrowWidth solid transparent; + border-right: $arrowWidth solid transparent; + border-top: $arrowWidth solid $color; +} +@mixin popoverArrow-left($arrowWidth: 5px, $color: $black) { + top: 50%; + right: 0; + margin-top: -$arrowWidth; + border-top: $arrowWidth solid transparent; + border-bottom: $arrowWidth solid transparent; + border-left: $arrowWidth solid $color; +} +@mixin popoverArrow-bottom($arrowWidth: 5px, $color: $black) { + top: 0; + left: 50%; + margin-left: -$arrowWidth; + border-left: $arrowWidth solid transparent; + border-right: $arrowWidth solid transparent; + border-bottom: $arrowWidth solid $color; +} +@mixin popoverArrow-right($arrowWidth: 5px, $color: $black) { + top: 50%; + left: 0; + margin-top: -$arrowWidth; + border-top: $arrowWidth solid transparent; + border-bottom: $arrowWidth solid transparent; + border-right: $arrowWidth solid $color; +} + +// Grid System +// ----------- + +// Centered container element +@mixin container-fixed() { + margin-right: auto; + margin-left: auto; + @include clearfix(); +} + +// Table columns +@mixin tableColumns($columnSpan: 1) { + float: none; // undo default grid column styles + width: (($gridColumnWidth) * $columnSpan) + ($gridGutterWidth * ($columnSpan - 1)) - 16; // 16 is total padding on left and right of table cells + margin-left: 0; // undo default grid column styles +} + +// Make a Grid +// Use .makeRow and .makeColumn to assign semantic layouts grid system behavior +@mixin makeRow() { + margin-left: $gridGutterWidth * -1; + @include clearfix(); +} +@mixin makeColumn($columns: 1, $offset: 0) { + float: left; + margin-left: ($gridColumnWidth * $offset) + ($gridGutterWidth * ($offset - 1)) + ($gridGutterWidth * 2); + width: ($gridColumnWidth * $columns) + ($gridGutterWidth * ($columns - 1)); +} + +// The Grid +@mixin core-offset($columns, $gridColumnWidth, $gridGutterWidth) { + margin-left: ($gridColumnWidth * $columns) + ($gridGutterWidth * ($columns + 1)); +} + +@mixin core-span($columns, $gridColumnWidth, $gridGutterWidth) { + width: ($gridColumnWidth * $columns) + ($gridGutterWidth * ($columns - 1)); +} + +@mixin grid-core($gridColumnWidth, $gridGutterWidth) { + .row { + margin-left: $gridGutterWidth * -1; + @include clearfix(); + } + + [class*="span"] { + float: left; + margin-left: $gridGutterWidth; + } + + // Set the container width, and override it for fixed navbars in media queries + .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { + @include core-span($gridColumns, $gridColumnWidth, $gridGutterWidth); + } + + // generate .spanX and .offsetX + $i: $gridColumns; + @while $i > 0 { + .span#{$i} { @include core-span($i, $gridColumnWidth, $gridGutterWidth); } + $i: $i - 1; + } + + $i: $gridColumns; + @while $i > 0 { + .offset#{$i} { @include core-offset($i, $gridColumnWidth, $gridGutterWidth); } + $i: $i - 1; + } +} + +@mixin fluid-span($columns, $fluidGridColumnWidth, $fluidGridGutterWidth) { + width: ($fluidGridColumnWidth * $columns) + ($fluidGridGutterWidth * ($columns - 1)); + *width: ($fluidGridColumnWidth * $columns) + ($fluidGridGutterWidth * ($columns - 1)) - (.5 / $gridRowWidth * 100px * 1%); +} + +@mixin grid-fluid($fluidGridColumnWidth, $fluidGridGutterWidth) { + .row-fluid { + width: 100%; + @include clearfix(); + [class*="span"] { + @include input-block-level(); + float: left; + margin-left: $fluidGridGutterWidth; + *margin-left: $fluidGridGutterWidth - (.5 / $gridRowWidth * 100px * 1%); + } + [class*="span"]:first-child { + margin-left: 0; + } + + // generate .spanX + $i: $gridColumns; + @while $i > 0 { + .span#{$i} { + @include fluid-span($i, $fluidGridColumnWidth, $fluidGridGutterWidth); + } + $i: $i - 1; + } + } +} + +@mixin input-span($columns, $gridColumnWidth, $gridGutterWidth) { + width: (($gridColumnWidth) * $columns) + ($gridGutterWidth * ($columns - 1)) - 10; +} + +@mixin grid-input($gridColumnWidth, $gridGutterWidth) { + input, + textarea, + .uneditable-input { + margin-left: 0; // override margin-left from core grid system + } + + // generate .spanX + $i: $gridColumns; + @while $i > 0 { + input.span#{$i}, + textarea.span#{$i}, + .uneditable-input.span#{$i} { + @include input-span($i, $gridColumnWidth, $gridGutterWidth); + } + $i: $i - 1; + } +} + +// Mixins +// Snippets of reusable CSS to develop faster and keep code readable +// ----------------------------------------------------------------- + + +// UTILITY MIXINS +// -------------------------------------------------- + +// Clearfix +// -------- +// For clearing floats like a boss h5bp.com/q +@mixin clearfix { + *zoom: 1; + &:before, + &:after { + display: table; + content: ""; + } + &:after { + clear: both; + } +} + +.clearfix { + @include clearfix(); +} + +// Webkit-style focus +// ------------------ +@mixin tab-focus() { + // Default + outline: thin dotted #333; + // Webkit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +// Center-align a block level element +// ---------------------------------- +@mixin center-block() { + display: block; + margin-left: auto; + margin-right: auto; +} + +// IE7 inline-block +// ---------------- +@mixin ie7-inline-block() { + *display: inline; // IE7 inline-block hack + *zoom: 1; +} + +// IE7 likes to collapse whitespace on either side of the inline-block elements. +// Ems because we're attempting to match the width of a space character. Left +// version is for form buttons, which typically come after other elements, and +// right version is for icons, which come before. Applying both is ok, but it will +// mean that space between those elements will be .6em (~2 space characters) in IE7, +// instead of the 1 space in other browsers. +@mixin ie7-restore-left-whitespace() { + *margin-left: .3em; + + &:first-child { + *margin-left: 0; + } +} + +@mixin ie7-restore-right-whitespace() { + *margin-right: .3em; + + &:last-child { + *margin-left: 0; + } +} + +// Sizing shortcuts +// ------------------------- +@mixin size($height, $width) { + width: $width; + height: $height; +} +@mixin square($size) { + @include size($size, $size); +} + +// Placeholder text +// ------------------------- +@mixin placeholder($color: $placeholderText) { + :-moz-placeholder { + color: $color; + } + :-ms-input-placeholder { + color: $color; + } + ::-webkit-input-placeholder { + color: $color; + } +} + +// Text overflow +// ------------------------- +// Requires inline-block or block for proper styling +@mixin text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// CSS image replacement +// ------------------------- +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 +@mixin hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.hide-text { + @include hide-text(); +} + +// FONTS +// -------------------------------------------------- + +@mixin font-family-serif() { + font-family: $serifFontFamily; +} +@mixin font-family-sans-serif() { + font-family: $sansFontFamily; +} +@mixin font-family-monospace() { + font-family: $monoFontFamily; +} +@mixin font-shorthand($size: $baseFontSize, $weight: normal, $lineHeight: $baseLineHeight) { + font-size: $size; + font-weight: $weight; + line-height: $lineHeight; +} +@mixin font-serif($size: $baseFontSize, $weight: normal, $lineHeight: $baseLineHeight) { + @include font-family-serif(); + @include font-shorthand($size, $weight, $lineHeight); +} +@mixin font-sans-serif($size: $baseFontSize, $weight: normal, $lineHeight: $baseLineHeight) { + @include font-family-sans-serif(); + @include font-shorthand($size, $weight, $lineHeight); +} +@mixin font-monospace($size: $baseFontSize, $weight: normal, $lineHeight: $baseLineHeight) { + @include font-family-monospace(); + @include font-shorthand($size, $weight, $lineHeight); +} + + +// FORMS +// -------------------------------------------------- + +// Block level inputs +@mixin input-block-level { + display: block; + width: 100%; + min-height: 28px; // Make inputs at least the height of their button counterpart + @include box-sizing(border-box); // Makes inputs behave like true block-level elements +} + + +// Mixin for form field states +@mixin formFieldState($textColor: #555, $borderColor: #ccc, $backgroundColor: #f5f5f5) { + // Set the text color + > label, + .help-block, + .help-inline { + color: $textColor; + } + // Style inputs accordingly + .checkbox, + .radio, + input, + select, + textarea { + color: $textColor; + border-color: $borderColor; + &:focus { + border-color: darken($borderColor, 10%); + @include box-shadow(0 0 6px lighten($borderColor, 20%)); + } + } + // Give a small background color for input-prepend/-append + .input-prepend .add-on, + .input-append .add-on { + color: $textColor; + background-color: $backgroundColor; + border-color: $textColor; + } +} + + + +// CSS3 PROPERTIES +// -------------------------------------------------- + +// Border Radius +@mixin border-radius($radius) { + -webkit-border-radius: $radius; + -moz-border-radius: $radius; + border-radius: $radius; +} + +// Drop shadows +@mixin box-shadow($shadow) { + -webkit-box-shadow: $shadow; + -moz-box-shadow: $shadow; + box-shadow: $shadow; +} + +// Transitions +@mixin transition($transition) { + -webkit-transition: $transition; + -moz-transition: $transition; + -ms-transition: $transition; + -o-transition: $transition; + transition: $transition; +} + +// Transformations +@mixin rotate($degrees) { + -webkit-transform: rotate($degrees); + -moz-transform: rotate($degrees); + -ms-transform: rotate($degrees); + -o-transform: rotate($degrees); + transform: rotate($degrees); +} +@mixin scale($ratio) { + -webkit-transform: scale($ratio); + -moz-transform: scale($ratio); + -ms-transform: scale($ratio); + -o-transform: scale($ratio); + transform: scale($ratio); +} +@mixin translate($x, $y) { + -webkit-transform: translate($x, $y); + -moz-transform: translate($x, $y); + -ms-transform: translate($x, $y); + -o-transform: translate($x, $y); + transform: translate($x, $y); +} + +@mixin skew($x, $y) { + -webkit-transform: skew($x, $y); + -moz-transform: skew($x, $y); + -ms-transform: skew($x, $y); + -o-transform: skew($x, $y); + transform: skew($x, $y); +} + +@mixin translate3d($x, $y, $z) { + -webkit-transform: translate($x, $y, $z); + -moz-transform: translate($x, $y, $z); + -ms-transform: translate($x, $y, $z); + -o-transform: translate($x, $y, $z); + transform: translate($x, $y, $z); +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden +// See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples +@mixin backface-visibility($visibility){ + -webkit-backface-visibility: $visibility; + -moz-backface-visibility: $visibility; + -ms-backface-visibility: $visibility; + backface-visibility: $visibility; +} + +// Background clipping +// Heads up: FF 3.6 and under need "padding" instead of "padding-box" +@mixin background-clip($clip) { + -webkit-background-clip: $clip; + -moz-background-clip: $clip; + background-clip: $clip; +} + +// Background sizing +@mixin background-size($size) { + -webkit-background-size: $size; + -moz-background-size: $size; + -o-background-size: $size; + background-size: $size; +} + + +// Box sizing +@mixin box-sizing($boxmodel) { + -webkit-box-sizing: $boxmodel; + -moz-box-sizing: $boxmodel; + -ms-box-sizing: $boxmodel; + box-sizing: $boxmodel; +} + +// Uses box-sizing mixin, so must be defined here +.input-block-level { + @include input-block-level(); +} + +// User select +// For selecting text on the page +@mixin user-select($select) { + -webkit-user-select: $select; + -moz-user-select: $select; + -ms-user-select: $select; + -o-user-select: $select; + user-select: $select; +} + +// Resize anything +@mixin resizable($direction) { + resize: $direction; // Options: horizontal, vertical, both + overflow: auto; // Safari fix +} + +// CSS3 Content Columns +@mixin content-columns($columnCount, $columnGap: $gridGutterWidth) { + -webkit-column-count: $columnCount; + -moz-column-count: $columnCount; + column-count: $columnCount; + -webkit-column-gap: $columnGap; + -moz-column-gap: $columnGap; + column-gap: $columnGap; +} + +// Optional hyphenation +@mixin hyphens($mode: auto) { + word-wrap: break-word; + -webkit-hyphens: $mode; + -moz-hyphens: $mode; + -ms-hyphens: $mode; + -o-hyphens: $mode; + hyphens: $mode; +} + +// Opacity +@mixin opacity($opacity) { + opacity: $opacity / 100; + filter: alpha(opacity=$opacity); +} + + + +// BACKGROUNDS +// -------------------------------------------------- + +// Add an alphatransparency value to any background or border color (via Elyse Holladay) +@mixin translucent-background($color: $white, $alpha: 1) { + background-color: hsla(hue($color), saturation($color), lightness($color), $alpha); +} + +@mixin translucent-border($color: $white, $alpha: 1) { + border-color: hsla(hue($color), saturation($color), lightness($color), $alpha); + @include background-clip(padding-box); +} + +// Gradient Bar Colors for buttons and alerts +@mixin gradientBar($primaryColor, $secondaryColor) { + @include gradient-vertical($primaryColor, $secondaryColor); + border-color: $secondaryColor $secondaryColor darken($secondaryColor, 15%); + border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fade-in(rgba(0,0,0,.1), 0.15); +} + +// Gradients +@mixin gradient-horizontal($startColor: #555, $endColor: #333) { + background-color: $endColor; + background-image: -moz-linear-gradient(left, $startColor, $endColor); // FF 3.6+ + background-image: -ms-linear-gradient(left, $startColor, $endColor); // IE10 + background-image: -webkit-gradient(linear, 0 0, 100% 0, from($startColor), to($endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(left, $startColor, $endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(left, $startColor, $endColor); // Opera 11.10 + background-image: linear-gradient(left, $startColor, $endColor); // Le standard + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{darken($startColor, 1%)}', endColorstr='#{darken($endColor, 1%)}', GradientType=1); // IE9 and down +} +@mixin gradient-vertical($startColor: #555, $endColor: #333) { + background-color: mix($startColor, $endColor, 60%); + background-image: -moz-linear-gradient(top, $startColor, $endColor); // FF 3.6+ + background-image: -ms-linear-gradient(top, $startColor, $endColor); // IE10 + background-image: -webkit-gradient(linear, 0 0, 0 100%, from($startColor), to($endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(top, $startColor, $endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(top, $startColor, $endColor); // Opera 11.10 + background-image: linear-gradient(top, $startColor, $endColor); // The standard + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{darken($startColor, 1%)}', endColorstr='#{darken($endColor, 1%)}', GradientType=0); // IE9 and down +} +@mixin gradient-directional($startColor: #555, $endColor: #333, $deg: 45deg) { + background-color: $endColor; + background-repeat: repeat-x; + background-image: -moz-linear-gradient($deg, $startColor, $endColor); // FF 3.6+ + background-image: -ms-linear-gradient($deg, $startColor, $endColor); // IE10 + background-image: -webkit-linear-gradient($deg, $startColor, $endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient($deg, $startColor, $endColor); // Opera 11.10 + background-image: linear-gradient($deg, $startColor, $endColor); // The standard +} +@mixin gradient-vertical-three-colors($startColor: #00b3ee, $midColor: #7a43b6, $colorStop: 50%, $endColor: #c3325f) { + background-color: mix($midColor, $endColor, 80%); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from($startColor), color-stop($colorStop, $midColor), to($endColor)); + background-image: -webkit-linear-gradient($startColor, $midColor $colorStop, $endColor); + background-image: -moz-linear-gradient(top, $startColor, $midColor $colorStop, $endColor); + background-image: -ms-linear-gradient($startColor, $midColor $colorStop, $endColor); + background-image: -o-linear-gradient($startColor, $midColor $colorStop, $endColor); + background-image: linear-gradient($startColor, $midColor $colorStop, $endColor); + background-repeat: no-repeat; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#{darken($startColor, 1%)}', endColorstr='#{darken($endColor, 1%)}', GradientType=0); // IE9 and down, gets no color-stop at all for proper fallback +} +@mixin gradient-radial($innerColor: #555, $outerColor: #333) { + background-color: $outerColor; + background-image: -webkit-gradient(radial, center center, 0, center center, 460, from($innerColor), to($outerColor)); + background-image: -webkit-radial-gradient(circle, $innerColor, $outerColor); + background-image: -moz-radial-gradient(circle, $innerColor, $outerColor); + background-image: -ms-radial-gradient(circle, $innerColor, $outerColor); + background-image: -o-radial-gradient(circle, $innerColor, $outerColor); + background-repeat: no-repeat; +} +@mixin gradient-striped($color, $angle: -45deg) { + background-color: $color; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient($angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -moz-linear-gradient($angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -ms-linear-gradient($angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient($angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); + background-image: linear-gradient($angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent); +} + +// Reset filters for IE +@mixin gradient-reset-filter() { + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} + + + +// COMPONENT MIXINS +// -------------------------------------------------- + +// Horizontal dividers +// ------------------------- +// Dividers (basically an hr) within dropdowns and nav lists +@mixin nav-divider($top: #e5e5e5, $bottom: $white) { + // IE7 needs a set width since we gave a height. Restricting just + // to IE7 to keep the 1px left/right space in other browsers. + // It is unclear where IE is getting the extra space that we need + // to negative-margin away, but so it goes. + *width: 100%; + height: 1px; + margin: (($baseLineHeight / 2) - 1) 1px; // 8px 1px + *margin: -5px 0 5px; + overflow: hidden; + background-color: $top; + border-bottom: 1px solid $bottom; +} + +// Button backgrounds +// ------------------ +@mixin buttonBackground($startColor, $endColor) { + // gradientBar will set the background to a pleasing blend of these, to support IE<=9 + @include gradientBar($startColor, $endColor); + *background-color: $endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + @include gradient-reset-filter(); + + // in these cases the gradient won't cover the background, so we override + &:hover, &:active, &.active, &.disabled, &[disabled] { + background-color: $endColor; + *background-color: darken($endColor, 5%); + } + + // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves + &:active, + &.active { + background-color: darken($endColor, 10%) \9; + } +} + +// Navbar vertical align +// ------------------------- +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbarVerticalAlign(30px);` to calculate the appropriate top margin. +@mixin navbarVerticalAlign($elementHeight) { + margin-top: ($navbarHeight - $elementHeight) / 2; +} + +// Popover arrows +// ------------------------- +// For tipsies and popovers +@mixin popoverArrow-top($arrowWidth: 5px, $color: $black) { + bottom: 0; + left: 50%; + margin-left: -$arrowWidth; + border-left: $arrowWidth solid transparent; + border-right: $arrowWidth solid transparent; + border-top: $arrowWidth solid $color; +} +@mixin popoverArrow-left($arrowWidth: 5px, $color: $black) { + top: 50%; + right: 0; + margin-top: -$arrowWidth; + border-top: $arrowWidth solid transparent; + border-bottom: $arrowWidth solid transparent; + border-left: $arrowWidth solid $color; +} +@mixin popoverArrow-bottom($arrowWidth: 5px, $color: $black) { + top: 0; + left: 50%; + margin-left: -$arrowWidth; + border-left: $arrowWidth solid transparent; + border-right: $arrowWidth solid transparent; + border-bottom: $arrowWidth solid $color; +} +@mixin popoverArrow-right($arrowWidth: 5px, $color: $black) { + top: 50%; + left: 0; + margin-top: -$arrowWidth; + border-top: $arrowWidth solid transparent; + border-bottom: $arrowWidth solid transparent; + border-right: $arrowWidth solid $color; +} + +// Grid System +// ----------- + +// Centered container element +@mixin container-fixed() { + margin-right: auto; + margin-left: auto; + @include clearfix(); +} + +// Table columns +@mixin tableColumns($columnSpan: 1) { + float: none; // undo default grid column styles + width: (($gridColumnWidth) * $columnSpan) + ($gridGutterWidth * ($columnSpan - 1)) - 16; // 16 is total padding on left and right of table cells + margin-left: 0; // undo default grid column styles +} + +// Make a Grid +// Use .makeRow and .makeColumn to assign semantic layouts grid system behavior +@mixin makeRow() { + margin-left: $gridGutterWidth * -1; + @include clearfix(); +} +@mixin makeColumn($columns: 1, $offset: 0) { + float: left; + margin-left: ($gridColumnWidth * $offset) + ($gridGutterWidth * ($offset - 1)) + ($gridGutterWidth * 2); + width: ($gridColumnWidth * $columns) + ($gridGutterWidth * ($columns - 1)); +} + +// The Grid +@mixin core-offset($columns, $gridColumnWidth, $gridGutterWidth) { + margin-left: ($gridColumnWidth * $columns) + ($gridGutterWidth * ($columns + 1)); +} + +@mixin core-span($columns, $gridColumnWidth, $gridGutterWidth) { + width: ($gridColumnWidth * $columns) + ($gridGutterWidth * ($columns - 1)); +} + +@mixin grid-core($gridColumnWidth, $gridGutterWidth) { + .row { + margin-left: $gridGutterWidth * -1; + @include clearfix(); + } + + [class*="span"] { + float: left; + margin-left: $gridGutterWidth; + } + + // Set the container width, and override it for fixed navbars in media queries + .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { + @include core-span($gridColumns, $gridColumnWidth, $gridGutterWidth); + } + + // generate .spanX and .offsetX + $i: $gridColumns; + @while $i > 0 { + .span#{$i} { @include core-span($i, $gridColumnWidth, $gridGutterWidth); } + $i: $i - 1; + } + + $i: $gridColumns; + @while $i > 0 { + .offset#{$i} { @include core-offset($i, $gridColumnWidth, $gridGutterWidth); } + $i: $i - 1; + } +} + +@mixin fluid-span($columns, $fluidGridColumnWidth, $fluidGridGutterWidth) { + width: ($fluidGridColumnWidth * $columns) + ($fluidGridGutterWidth * ($columns - 1)); + *width: ($fluidGridColumnWidth * $columns) + ($fluidGridGutterWidth * ($columns - 1)) - (.5 / $gridRowWidth * 100px * 1%); +} + +@mixin grid-fluid($fluidGridColumnWidth, $fluidGridGutterWidth) { + .row-fluid { + width: 100%; + @include clearfix(); + [class*="span"] { + @include input-block-level(); + float: left; + margin-left: $fluidGridGutterWidth; + *margin-left: $fluidGridGutterWidth - (.5 / $gridRowWidth * 100px * 1%); + } + [class*="span"]:first-child { + margin-left: 0; + } + + // generate .spanX + $i: $gridColumns; + @while $i > 0 { + .span#{$i} { + @include fluid-span($i, $fluidGridColumnWidth, $fluidGridGutterWidth); + } + $i: $i - 1; + } + } +} + +@mixin input-span($columns, $gridColumnWidth, $gridGutterWidth) { + width: (($gridColumnWidth) * $columns) + ($gridGutterWidth * ($columns - 1)) - 10; +} + +@mixin grid-input($gridColumnWidth, $gridGutterWidth) { + input, + textarea, + .uneditable-input { + margin-left: 0; // override margin-left from core grid system + } + + // generate .spanX + $i: $gridColumns; + @while $i > 0 { + input.span#{$i}, + textarea.span#{$i}, + .uneditable-input.span#{$i} { + @include input-span($i, $gridColumnWidth, $gridGutterWidth); + } + $i: $i - 1; + } +} \ No newline at end of file diff --git a/views/shared/css/sass/_sprites.scss b/views/shared/css/sass/_sprites.scss new file mode 100755 index 0000000..bb72ea3 --- /dev/null +++ b/views/shared/css/sass/_sprites.scss @@ -0,0 +1,191 @@ +// SPRITES +// Glyphs and icons for buttons, nav, and more +// ------------------------------------------- + + +// ICONS +// ----- + +// All icons receive the styles of the tag with a base class +// of .i and are then given a unique class to add width, height, +// and background-position. Your resulting HTML will look like +// . + +// For the white version of the icons, just add the .icon-white class: +// + +[class^="icon-"], +[class*=" icon-"] { + display: inline-block; + width: 14px; + height: 14px; + @include ie7-restore-right-whitespace(); + line-height: 14px; + vertical-align: text-top; + background-image: url($iconSpritePath); + background-position: 14px 14px; + background-repeat: no-repeat; + +} +.icon-white { + background-image: url($iconWhiteSpritePath); +} + +.icon-glass { background-position: 0 0; } +.icon-music { background-position: -24px 0; } +.icon-search { background-position: -48px 0; } +.icon-envelope { background-position: -72px 0; } +.icon-heart { background-position: -96px 0; } +.icon-star { background-position: -120px 0; } +.icon-star-empty { background-position: -144px 0; } +.icon-user { background-position: -168px 0; } +.icon-film { background-position: -192px 0; } +.icon-th-large { background-position: -216px 0; } +.icon-th { background-position: -240px 0; } +.icon-th-list { background-position: -264px 0; } +.icon-ok { background-position: -288px 0; } +.icon-remove { background-position: -312px 0; } +.icon-zoom-in { background-position: -336px 0; } +.icon-zoom-out { background-position: -360px 0; } +.icon-off { background-position: -384px 0; } +.icon-signal { background-position: -408px 0; } +.icon-cog { background-position: -432px 0; } +.icon-trash { background-position: -456px 0; } + +.icon-home { background-position: 0 -24px; } +.icon-file { background-position: -24px -24px; } +.icon-time { background-position: -48px -24px; } +.icon-road { background-position: -72px -24px; } +.icon-download-alt { background-position: -96px -24px; } +.icon-download { background-position: -120px -24px; } +.icon-upload { background-position: -144px -24px; } +.icon-inbox { background-position: -168px -24px; } +.icon-play-circle { background-position: -192px -24px; } +.icon-repeat { background-position: -216px -24px; } +.icon-refresh { background-position: -240px -24px; } +.icon-list-alt { background-position: -264px -24px; } +.icon-lock { background-position: -287px -24px; } // 1px off +.icon-flag { background-position: -312px -24px; } +.icon-headphones { background-position: -336px -24px; } +.icon-volume-off { background-position: -360px -24px; } +.icon-volume-down { background-position: -384px -24px; } +.icon-volume-up { background-position: -408px -24px; } +.icon-qrcode { background-position: -432px -24px; } +.icon-barcode { background-position: -456px -24px; } + +.icon-tag { background-position: 0 -48px; } +.icon-tags { background-position: -25px -48px; } // 1px off +.icon-book { background-position: -48px -48px; } +.icon-bookmark { background-position: -72px -48px; } +.icon-print { background-position: -96px -48px; } +.icon-camera { background-position: -120px -48px; } +.icon-font { background-position: -144px -48px; } +.icon-bold { background-position: -167px -48px; } // 1px off +.icon-italic { background-position: -192px -48px; } +.icon-text-height { background-position: -216px -48px; } +.icon-text-width { background-position: -240px -48px; } +.icon-align-left { background-position: -264px -48px; } +.icon-align-center { background-position: -288px -48px; } +.icon-align-right { background-position: -312px -48px; } +.icon-align-justify { background-position: -336px -48px; } +.icon-list { background-position: -360px -48px; } +.icon-indent-left { background-position: -384px -48px; } +.icon-indent-right { background-position: -408px -48px; } +.icon-facetime-video { background-position: -432px -48px; } +.icon-picture { background-position: -456px -48px; } + +.icon-pencil { background-position: 0 -72px; } +.icon-map-marker { background-position: -24px -72px; } +.icon-adjust { background-position: -48px -72px; } +.icon-tint { background-position: -72px -72px; } +.icon-edit { background-position: -96px -72px; } +.icon-share { background-position: -120px -72px; } +.icon-check { background-position: -144px -72px; } +.icon-move { background-position: -168px -72px; } +.icon-step-backward { background-position: -192px -72px; } +.icon-fast-backward { background-position: -216px -72px; } +.icon-backward { background-position: -240px -72px; } +.icon-play { background-position: -264px -72px; } +.icon-pause { background-position: -288px -72px; } +.icon-stop { background-position: -312px -72px; } +.icon-forward { background-position: -336px -72px; } +.icon-fast-forward { background-position: -360px -72px; } +.icon-step-forward { background-position: -384px -72px; } +.icon-eject { background-position: -408px -72px; } +.icon-chevron-left { background-position: -432px -72px; } +.icon-chevron-right { background-position: -456px -72px; } + +.icon-plus-sign { background-position: 0 -96px; } +.icon-minus-sign { background-position: -24px -96px; } +.icon-remove-sign { background-position: -48px -96px; } +.icon-ok-sign { background-position: -72px -96px; } +.icon-question-sign { background-position: -96px -96px; } +.icon-info-sign { background-position: -120px -96px; } +.icon-screenshot { background-position: -144px -96px; } +.icon-remove-circle { background-position: -168px -96px; } +.icon-ok-circle { background-position: -192px -96px; } +.icon-ban-circle { background-position: -216px -96px; } +.icon-arrow-left { background-position: -240px -96px; } +.icon-arrow-right { background-position: -264px -96px; } +.icon-arrow-up { background-position: -289px -96px; } // 1px off +.icon-arrow-down { background-position: -312px -96px; } +.icon-share-alt { background-position: -336px -96px; } +.icon-resize-full { background-position: -360px -96px; } +.icon-resize-small { background-position: -384px -96px; } +.icon-plus { background-position: -408px -96px; } +.icon-minus { background-position: -433px -96px; } +.icon-asterisk { background-position: -456px -96px; } + +.icon-exclamation-sign { background-position: 0 -120px; } +.icon-gift { background-position: -24px -120px; } +.icon-leaf { background-position: -48px -120px; } +.icon-fire { background-position: -72px -120px; } +.icon-eye-open { background-position: -96px -120px; } +.icon-eye-close { background-position: -120px -120px; } +.icon-warning-sign { background-position: -144px -120px; } +.icon-plane { background-position: -168px -120px; } +.icon-calendar { background-position: -192px -120px; } +.icon-random { background-position: -216px -120px; } +.icon-comment { background-position: -240px -120px; } +.icon-magnet { background-position: -264px -120px; } +.icon-chevron-up { background-position: -288px -120px; } +.icon-chevron-down { background-position: -313px -119px; } // 1px, 1px off +.icon-retweet { background-position: -336px -120px; } +.icon-shopping-cart { background-position: -360px -120px; } +.icon-folder-close { background-position: -384px -120px; } +.icon-folder-open { background-position: -408px -120px; } +.icon-resize-vertical { background-position: -432px -119px; } // 1px, 1px off +.icon-resize-horizontal { background-position: -456px -118px; } // 1px, 2px off + +.icon-hdd { background-position: 0 -144px; } +.icon-bullhorn { background-position: -24px -144px; } +.icon-bell { background-position: -48px -144px; } +.icon-certificate { background-position: -72px -144px; } +.icon-thumbs-up { background-position: -96px -144px; } +.icon-thumbs-down { background-position: -120px -144px; } +.icon-hand-right { background-position: -144px -144px; } +.icon-hand-left { background-position: -168px -144px; } +.icon-hand-up { background-position: -192px -144px; } +.icon-hand-down { background-position: -216px -144px; } +.icon-circle-arrow-right { background-position: -240px -144px; } +.icon-circle-arrow-left { background-position: -264px -144px; } +.icon-circle-arrow-up { background-position: -288px -144px; } +.icon-circle-arrow-down { background-position: -312px -144px; } +.icon-globe { background-position: -336px -144px; } +.icon-wrench { background-position: -360px -144px; } +.icon-tasks { background-position: -384px -144px; } +.icon-filter { background-position: -408px -144px; } +.icon-briefcase { background-position: -432px -144px; } +.icon-fullscreen { background-position: -456px -144px; } + + + + + + + + + + + + diff --git a/views/shared/css/sass/_variables.scss b/views/shared/css/sass/_variables.scss new file mode 100755 index 0000000..55d47c4 --- /dev/null +++ b/views/shared/css/sass/_variables.scss @@ -0,0 +1,209 @@ +// Variables.less +// Variables to customize the look and feel of Bootstrap +// ----------------------------------------------------- + +// Updated for SASS: +// - replace @ with $ +// - replace `spin` with `adjust-hue` +// - add !default to variable definitions + +// GLOBAL VALUES +// -------------------------------------------------- + + +// Grays +// ------------------------- +$black: #000 !default; +$grayDarker: #222 !default; +$grayDark: #333 !default; +$gray: #555 !default; +$grayLight: #999 !default; +$grayLighter: #eee !default; +$white: #fff !default; + + +// Accent colors +// ------------------------- +$blue: #049cdb !default; +$blueDark: #0064cd !default; +$green: #46a546 !default; +$red: #9d261d !default; +$yellow: #ffc40d !default; +$orange: #f89406 !default; +$pink: #c3325f !default; +$purple: #7a43b6 !default; + + +// Scaffolding +// ------------------------- +$bodyBackground: $white !default; +$textColor: $grayDark !default; + + +// Links +// ------------------------- +$linkColor: #08c !default; +$linkColorHover: darken($linkColor, 15%) !default; + + +// Typography +// ------------------------- +$sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif !default; +$serifFontFamily: Georgia, "Times New Roman", Times, serif !default; +$monoFontFamily: Menlo, Monaco, Consolas, "Courier New", monospace !default; + +$baseFontSize: 13px !default; +$baseFontFamily: $sansFontFamily !default; +$baseLineHeight: 18px !default; +$altFontFamily: $serifFontFamily !default; + +$headingsFontFamily: inherit !default; // empty to use BS default, $baseFontFamily +$headingsFontWeight: bold !default; // instead of browser default, bold +$headingsColor: inherit !default; // empty to use BS default, $textColor + + +// Tables +// ------------------------- +$tableBackground: transparent !default; // overall background-color +$tableBackgroundAccent: #f9f9f9 !default; // for striping +$tableBackgroundHover: #f5f5f5 !default; // for hover +$tableBorder: #ddd !default; // table and cell border + + +// Buttons +// ------------------------- +$btnBackground: $white !default; +$btnBackgroundHighlight: darken($white, 10%) !default; +$btnBorder: #ccc !default; + +$btnPrimaryBackground: $linkColor !default; +$btnPrimaryBackgroundHighlight: adjust-hue($btnPrimaryBackground, 15%) !default; + +$btnInfoBackground: #5bc0de !default; +$btnInfoBackgroundHighlight: #2f96b4 !default; + +$btnSuccessBackground: #62c462 !default; +$btnSuccessBackgroundHighlight: #51a351 !default; + +$btnWarningBackground: lighten($orange, 15%) !default; +$btnWarningBackgroundHighlight: $orange !default; + +$btnDangerBackground: #ee5f5b !default; +$btnDangerBackgroundHighlight: #bd362f !default; + +$btnInverseBackground: $gray !default; +$btnInverseBackgroundHighlight: $grayDarker !default; + + +// Forms +// ------------------------- +$inputBackground: $white !default; +$inputBorder: #ccc !default; +$inputBorderRadius: 3px !default; +$inputDisabledBackground: $grayLighter !default; +$formActionsBackground: #f5f5f5 !default; + +// Dropdowns +// ------------------------- +$dropdownBackground: $white !default; +$dropdownBorder: rgba(0,0,0,.2) !default; +$dropdownLinkColor: $grayDark !default; +$dropdownLinkColorHover: $white !default; +$dropdownLinkBackgroundHover: $linkColor !default; + + + + +// COMPONENT VARIABLES +// -------------------------------------------------- + +// Z-index master list +// ------------------------- +// Used for a bird's eye view of components dependent on the z-axis +// Try to avoid customizing these :) +$zindexDropdown: 1000 !default; +$zindexPopover: 1010 !default; +$zindexTooltip: 1020 !default; +$zindexFixedNavbar: 1030 !default; +$zindexModalBackdrop: 1040 !default; +$zindexModal: 1050 !default; + + +// Sprite icons path +// ------------------------- +$iconSpritePath: "./img/glyphicons-halflings.png" !default; +$iconWhiteSpritePath: "./img/glyphicons-halflings-white.png" !default; + + +// Input placeholder text color +// ------------------------- +$placeholderText: $grayLight !default; + + +// Hr border color +// ------------------------- +$hrBorder: $grayLighter !default; + + +// Navbar +// ------------------------- +$navbarHeight: 40px !default; +$navbarBackground: $grayDarker !default; +$navbarBackgroundHighlight: $grayDark !default; + +$navbarText: $grayLight !default; +$navbarLinkColor: $grayLight !default; +$navbarLinkColorHover: $white !default; +$navbarLinkColorActive: $navbarLinkColorHover !default; +$navbarLinkBackgroundHover: transparent !default; +$navbarLinkBackgroundActive: $navbarBackground !default; + +$navbarSearchBackground: lighten($navbarBackground, 25%) !default; +$navbarSearchBackgroundFocus: $white !default; +$navbarSearchBorder: darken($navbarSearchBackground, 30%) !default; +$navbarSearchPlaceholderColor: #ccc !default; +$navbarBrandColor: $navbarLinkColor !default; + + +// Hero unit +// ------------------------- +$heroUnitBackground: $grayLighter !default; +$heroUnitHeadingColor: inherit !default; +$heroUnitLeadColor: inherit !default; + + +// Form states and alerts +// ------------------------- +$warningText: #c09853 !default; +$warningBackground: #fcf8e3 !default; +$warningBorder: darken(adjust-hue($warningBackground, -10), 3%) !default; + +$errorText: #b94a48 !default; +$errorBackground: #f2dede !default; +$errorBorder: darken(adjust-hue($errorBackground, -10), 3%) !default; + +$successText: #468847 !default; +$successBackground: #dff0d8 !default; +$successBorder: darken(adjust-hue($successBackground, -10), 5%) !default; + +$infoText: #3a87ad !default; +$infoBackground: #d9edf7 !default; +$infoBorder: darken(adjust-hue($infoBackground, -10), 7%) !default; + + + +// GRID +// -------------------------------------------------- + +// Default 940px grid +// ------------------------- +$gridColumns: 12 !default; +$gridColumnWidth: 60px !default; +$gridGutterWidth: 20px !default; +$gridRowWidth: ($gridColumns * $gridColumnWidth) + ($gridGutterWidth * ($gridColumns - 1)) !default; + +// Fluid grid +// ------------------------- +$fluidGridColumnWidth: 6.382978723% !default; +$fluidGridGutterWidth: 2.127659574% !default; + diff --git a/views/shared/css/sass/fields.scss b/views/shared/css/sass/fields.scss new file mode 100755 index 0000000..adf9c62 --- /dev/null +++ b/views/shared/css/sass/fields.scss @@ -0,0 +1,26 @@ +@import "compass/css3/box-sizing"; +@import "mixins"; +@import "variables"; +@import "buttons"; +@import "sprites"; + +#solr-fields { + + td { + vertical-align: middle; + } + + input[type=text] { + margin: 0; + width: 100%; + } + + span.original-label { + font-weight: bold; + } + +} + +#submit { + margin-top: 2em !important; +} diff --git a/views/shared/css/sass/results.scss b/views/shared/css/sass/results.scss new file mode 100755 index 0000000..bb0f60e --- /dev/null +++ b/views/shared/css/sass/results.scss @@ -0,0 +1,78 @@ +@import "mixins"; +@import "variables"; +@import "buttons"; + +ul { + margin-left: 1em; +} + +#solr-search-form { + overflow: hidden; + box-sizing: border-box; + + span.float-wrap { + display: block; + padding-right: 10px; + overflow: hidden; + + input[type="text"] { + width: 100%; + } + + } + + input[type="submit"] { + float: right; + } + +} + +#solr-applied-facets { + + ul { + margin: 0; + padding: 0; + + li { + display: inline; + + &+li:before { + content: "ยท " + } + + } + + } + +} + +#solr-facets { + width: 20%; + float: left; +} + +#solr-results { + margin-left: 22%; + + div.result { + margin: 1em 0; + + a.result-title { + font-weight: bold; + } + + ul.hl { + em { + font-weight: bold; + } + } + + /* + * .gallery img { + * height: 50px; + * } + */ + + } + +} diff --git a/views/shared/results/index.php b/views/shared/results/index.php new file mode 100755 index 0000000..5c9a53d --- /dev/null +++ b/views/shared/results/index.php @@ -0,0 +1,161 @@ + + + + + __('Solr Search')));?> + + +

+ + + +
+
+ + + + +
+
+ + + +
+ +
    + + + +
  • + + + + > + + + + + (remove) + +
  • + + +
+ +
+ + + +
+ +

+ + facet_counts->facet_fields as $name => $facets): ?> + + + + + + + + +
    + + $count): ?> +
  • + + + + + + + + + + + () + +
  • + +
+ + + + +
+ + + +
+ + +

+ response->numFound; ?> results +

+ + response->docs as $doc): ?> + + +
+ + + + + + +
    + highlighting->{$doc->id} as $field): ?> + +
  • '); ?>
  • + + +
+ + + getTable($doc->model)->find($doc->modelid); + echo item_image_gallery( + array('wrapper' => array('class' => 'gallery')), + 'square_thumbnail', + false, + $item + ); + ?> + +
+ + + +
+ + + +
  • @eR}@2I=H|i^_}qC zGKRkV+d@?fMqnwNI6A#(Ehq9%W#qJ4=IEM!>UG`*ND57+d{{Fztd^q03+}Iw%1TrR z5KcDnRWT2w*iK}<@K**3Wz<*@V>RdiF&&^iypZsyk`d*$zfRka*6w!r3_=TKloW%12ZSTAtz3^A57>6EmV z*+I{wV-J9ZnU$qW-b}!^su6)R5!hkNO6i$!WwKZ}C`xq~xb@;#HW&psw>*Ang^)IrJU`4&INW*$1c46xwKiz52vYG-& z>bIA{<|G$cPC23|@FZ)+N8y{q@}>S~o7vuesBCb^dd}?B@J&1+*m+uX{^%Y;GvvwX)wNK*sPk|A)W!kCu=Tj(zgMc#;O z55e|wUoOV262uN?)tH)z9V%0?zFtY&0fMiiyFd}dG+lpAn?YfvJf&sKaMsy`EKu0p z7eMbSRuK}$O;@nSyYg2O&8m+YrJi`{B6ADZVP^#1qH>LAbS~Y$OdN9XGt#7?JjhRdUm{tL1fflGk9RrScHNo`D>7YVr6cV7@^IuVbaL8l2Sls zGRcuzKI^W^jhRY>CSYxVu@DmJwvJfmCs)HOKZ%{LY)#zQ+CZe4?DL5~Luo%qvbZy3 zehs&+<#3^G%FF)GjYp^RQKCw%Bu-M)?~TY84X=E}?U$NzNcUU!7KOu>NMh3nFgV`s zem=M$f`!-B0F^GnUr2K=zmO;u02=8D5mFO9pdWN;gMEa#vL&mnqsX(fPmpnykdxE8Z%CLjDU^Tkd}8k*-oDYKRRepIMgkfU4DOAL+(W=8@Z;{SK@U$LOI%Wtevk1`;&kCdR?(`wxZWrb>D` z25;IL2V>k@?~gS_1MZAo6Pm+h(rXQnhUE8a-)KXd!w3aTpOb&{<*;}dYG`9h!t38c zcj?@2OYhFQDd&1x=M^M)-raeQWY1~F4L1*JhT__(z>(o4xq#DfBUo39w;t zkJavqp=hi_?zudjA>dT{Ru7t)b1<0q)dT0ci4Pj_KsXcRxt0Mr(SU$KQDQ7T%R^G5 zbO^SFS(=%`qAaU>k1l52GypwU?fwj>$wt=*cgcE}{1}!6B{fLcdviB+aDiItUI&bj zfj{6@fw(;cq{^SpO=h8nrK;x~t6Ro$?g~3Kos`HGg&SLgczeTZIU}OE)z;V3stcj6 zo)2MJb3NnAQkdQn6B*vd{g(2}6Wi*@%lrOD{&pXc&}&KUr%ZZ(UzrzK`spyfj^F zeNcYrgj0(yJ>_MUJZ>f(o=zrL|K1(J60edcd)J{wB@qWb4T6Q48E~@R5GvHFX3D7p z?YIsS_zQNsuREvjYd+@UuBJd>w8kdpmUwe=m3R;L&d(ubOss>|&-K7pM~-KCf2M9M$%qiXU^dSW9_r|;JMzZtxU2CM4Sfxgk1(S zfdIi2<=oiEHq`v}4Fqg^$l!9S$)k%vvz^O?56Bx%z4Xe;RNWzs;5=~ui9w4&1v;{a)Im?u&=B*j;tG|lI}+#bKbJRc6;pA->W z1r0Wox-kWE@s(0)qy|p5d;vCWQO1u`*p6Roe5cV0($PcsXu}7Z-d(~k=nhTtz3-2Z z6lH`Un08PUjQf@b7^+t^AQ=ZXPLaQ4+~fTh*=AMfMoN9(>0fgqAX0qjHisXl@Dmj) z+9B-R$LoXbMmbm$W!eo;oXmy`SnxrG)QCoGB!R3oOO*M+5j3WuQ8%o=T zVM?AwM@NYIt?h>&=I^F*@Xxy>(M9N&5Mc{x{LEu(Fz;?@3be9kDMVSAFU<=AZIPFe z%SynTgXyndg3YgwYV6abuhPAiI3h>mj0nN_O-!bGBW8>7qS4qne$wwua0O)G4sLcx zneu}hE{PFVR3-h5dT%=uT&H24thfNMb+t<=I5`+?aVRo=8B;}fKKO`o(`oY)eH+wj z)gqfU7&?cvktFs*B*f#+*)!>L6HtV~r?)fo`^=?`%y^bcS=&Kc$MyAFSe>D1>UR#J zduDm+d5?g1C)8xCf;_Ioj_j)_oZ}QnIT6XA9AVN~%_Hecq`%(^qF^I(pxOS# zm!<@dwF?uz41^svJR%p{LaOK@=$Cp3QWiJy`XDkr7xBp>4@uLNk-QSUn>-5;$up(I z^_WsMd3k}o4Ol(N8~O&4M@B?6OB%Y;C*3?C*#ieK$FWP&x@4pg)`Sexe(~DO778Xg z3P6@nfkqyn#D{I-+9gJ*$PX|;ep@5cd&=n~ENWHh`C zY z5!Jegttq6$!y>vgs3~Fwn#NiucnJX2A_!wjD4%G`U4@))mv}Y!gC zh8C1^eUDxd2ALc)Ncy!m?0Q-woJU#oRJv{?KVVg=l+?=#GKHeLyag#8U-LBF}0^0vt)DT(w5^l!{)C(qvB9iwY2no#5gzsU`1)p(x|?;zIkn+N~%R z2h7}Ds7uDfKLxAE&@f?UoD8oBo3jQyrR6$D(ah9F_2SUQN7I)Fs~eNuFJX&rX5+b+4n;087bmV*Ir@uwHlkSmXk_8sYVy_=WUp z0Wvb9uMczrRCAkKHSp&YGCZy)c<+RYT!;YVE}qa0a@d}bxeo%XR|i6hzC^B%Iwm?H z7clGNO+D6y-xmEPyB>81Yl2&s2f9Y}Cs{dN&u zj&eVcM8jiab&N}X$M6N!7cW?=vb!{Iy*e4-QX&%;fi9kEQYLbd??Ih$t1EF6BTgH; zo0H^C*rf_}^eDS#Wlsv~YurNidUBDxrUAIy8@zck9N{)<060|$2s{wGb`Z|Mp&S;k zv#cRvYxbUUf$ClIYseuhXbrvUREGoG=$7v3m!V?V`$mFhUTs=7jRgnpE+66wGgxQW zyycgBUG24#lV{sUPxrPMQ^t!+{U8L+PnHLE{*WEmLDa(a6>|paougl@-Cb+#L+>Jv z3LE)M6O9W+X$8WH8pU1_;e3wkf?+1;SQ^jqz*M{Hy_E|>im_n5lFtJ0=FQ<-M6xzO z{xDAR>A=mvLMQe*so_77W`{wuP`W&%Ac^u%>>KX1$Jh3YqbI9>;Cju>?~E=8Q2}Gx zqDBF&vBU}N{WmADok307ve<&>ubCrgx!qV&j0EG0K!kJ-rpLJbwho-{;eY|vjS11W zbxE6X3q;$avF8+73>Q)8tngR>LK;I4T~ zkxU?UKBb_lwge}MBeMxsuEG*nyQde!e8jGFOSGUtN;#5Eh^4r{zW46 z2aF2r>gmzZ{+)Ykcl8e4$Uw60O}0a2i#mE5 z14vRXP=z=jxdD)SAFWgIPg`0Jal<%!kXx-x+vsTZ2%etx7H_k?b1}I@c4f18zTwR* zkjbEFHGwd57;Y-#&R}nk7>8m7!D~*Pae&!Y_~GO|uCu=AN-||9A$6q%0aBUdoQ%^6 zz#JDm@WBTjbK8G}dvf1KyC3qUmXK9ShB->jwvv86oWRU1aNdZ7sIvp4KgGPIXh~H; zg}y2u15w?Uke^MRZJ*O@@*Awd8W8?b+~+ov;^OZ@kfjZnPKcFF>dvHZ@R7VkWq|m@EsS-??%JHYc>K# zhnpLD>SRO2H|dPcWvNBL)hY@5CgK-Q=uifm?JJ92GQ{oI)QP%B0+;%O)!ptzVXfk}uujvd?z?|S#)!-wtdy(jI3R>R-Ji~E}I{F}vB{o(1GxM;-d zF7Ayu@BO^^sIfX*eTa?Ifj1J6g&XIwb=4g$KDu>w=hoT7J1q4<4G96FnKA5#k7&j) z%7$jO^6<{Z{bo=fp7q}Pa0O?x@H6ataoI5^osW~%IjS#Ujus#VK|c8Jgye{S#c<=Z zs5~W>{SJ(C(uy?I=2dzjXC+uYe-AeYcSzBM4IWgI=?DCI}?}|{NM7eF(bLNkr>%aw~_Q&h!OgJVkthm@h%6;Qj~1S?yJ?} zHYj)LDK7}EZQ$BhE`YgZ40}2MV3;aLk^!u!&svJmSp)1g<-^5?cYwL$!D24Iz@dx6 zI(LSBe0_KaB;}==7eIRA%3ul-F9_&>_HDOfbZIo0W-4Q5)-g#T&R&-#-Dn|Y5=X@6 zGe6!TSPmPRK~LOBrprRjG$w=<`M^j{{t;g5oqe-7K3~O}qdPI$Ty&1|_cc8D@Z#Ph zMGpA+UX`DHp7IW5D#(vAW6jn@Vm8iDxp8z=QM~5xw|;@T#HLj1-o2}zTi*`GYrDX{h)leMVv+m%* z45--!Aos?50|C(bXoBvu78eRF9k;b$W4d?ihCNR2YNeH+S&9PKONgD`01+S8@b5Rl z*m?D8gtD)OBZS$nVooN*OUyT>`wYKf*?)>)3(PMr?&btvbo*D$kYAxC5{ypcy)UnR z&S&!eUA|FT`ScIJ=l>qSY&+vVh4;-ejnHEI1BRFP@=j^z75#vXRXQkbJ^udm_xHZ~ zw>TIx{ifCJU9vaD&iY`8qXx|#tzSrZ$}h4;Ts0FLmKYLv0=FcX=E~hmAJmvDzx5uJ zZSScOdtM?EOn)^qsl4~CbW0h|x=2Fy#oZ5+#5#bP2B zkbeL5i01REv-|mZ&ii3D*IU2mRG;^U9f*&9_k8le=y4VIcYxs$sZuH6+|`;+?5zYk zmG4gLtL$jK^+oqG^yUis>n%LB zjAzE+AritW<4e{-OTEhuTn;G&%1ljsz2;J&GHahck-yk4XQFlg4+O>9H>61qnJ}kd zUwzko^`XLWVdIqO|Rci-de&smdmHml<0@V~r2?ZTvm(^7qRO|_It>S4V3 zsPYArRo2XJh{jZCyJ7rmJ6@0Te8bSk(v7A%S-ZzVbA)FxHpH~LM^C5GJ z=QTxB$Cjuh?|g=9^{(CvBV)JjHm2(yVyA!Xz_s{E3xUIHEjavI@_pRmB60*b`Wh-B zS_YP17jaf+asYiKzRkynTdiGv4Q4}J*7}`0XN<{ahKsfLY<3Mn&UfBl_pr8?>T9z2 z`0P$LnSA$)F%b>>gb_%54}N_$JbS-_h5yP~f8}hla(1?I-Cr5@SNgpb*qj&lS1!I- zx%hJB;;WU5->)DFV}%EKR_F#=Iq!A*XIKrd^t;GQdA8CUU~hVbEDDEgXXR=|Op?D| zBQNsGS5ABAR)9S_AFOmQS9C2wNDyE1i!NKl_8u_Kp#MPL*b>8D{o3#S zLN}S(W-5|?^$sQ%zlb=3;r@ECPF3>Y*9yw4A2I&!-n~`Z$zRuBb?FdW`KvB!F<;_f zUWa@6SFYDqI;U6$y}yj36<9HhUI(jn2NqFTzd%4E4ye;#Rj33&=%WX}j;~+UepJ)% z!LP;SAq^n5fff35r1t5*U+)brc*T$oLDucr8^fHxYiRJZWVuLu{HnB*k|(g!*ODm@#Sy>c6^t{V|70B-6Og~rJAK5m8|i0@JGCtHBk%XmWjF{uiytp3tZ|NV3UiutDidU zSdzN~^nIcP1j*|YG;?~f-2U|CpYI&w@2Ag}J|#TXC`-yA7ye-A7czG96UHYFV03Ds zn6XC|-3{->kIG^w0Op_6?q9@a;BC}6u=cHUJ?Ev6v~Tt0(QilRU@;U|Qo-TL4gZIo z)&)W+1j{W$_;*9bTk63BqF#JmEpA411pkBp!zS#GOY!=d5-i&f(`fB{v2?cBLUiY= z{wWl}n{O5&7Fy@uQ1h+zkmP#w^yt~{gZxpyN=y1h8$Ewe2VXKD8GP=QERQQF(TYD@ z@EP;GFkoZ;qeGD2mz+Y*cH7R&*u9we!U!aMv?wz;uD<6RFCS^Ohj;LE ziua#AA;Ew2Buke9JZ7G^ol1$hdX!pehw%uHcf<2-)vbf@6?E>uK?fl#9a5ypQIZ<~ zwAj8iZZrNwdrSGzdmxj?E*Mlb&xi%LEGz@%wrFg_&*01fr!>=EBX7c;{H{A%2JK>% z$=G6CeXFx#S*UNXwQD*9LCJxhxqntAZ>LleEmkXe#AItOhz>4|FV($jw~{ZcHW0 zvYeI>j*Yq2%9!En$@w3iLLtFeSw_~55k>lM6-DxO7C}B|6p4+9N^*P!msqDSK^FoX z0#BYFo&4#=-qH3Ut{`c=@%dXRclhG*;nB{~i=%C*k-L!e7Q`L!Qc=ArvluS^j^N}A zl?YXlfFp?kU0!;1eSY4RN%GnfVHzSNtw7XNj8=egg}*X#gIgsQubH+hdRB=%4VR?c zMU1{w3cbO;1eyZFSm(+!k}gnJVo4AzM#&Cr3p8oXgG;Rqs`un8$AD4TWQ0NT<&96m zBne$Les=_H&3D*EjI!s$iJX@Sd!6$hd8s+sHofW=qxhxI-60bsX=%9)9!tsN4Q%lX`^7TC4jE?91ONH-KAG&ruvP zW;4~Ipu?RA1Dt`cd<-eDAj0rh(z`|m%K-w7vPRGXbM>zzErmHPVwXIM0g>ElS0Yv! zu@J1DhQh$sMF5ge#~~OMyNNi{f83U+Bne3ZGZrQC7W3N1EHyQ7`Ks+$pgvhsIYU^T z{B>8)5h;7}Tr?KvRd}H(@(!71Hh;tBi}tx{?eGF%Gn{4Kpmo|%ZSWLAbMgUuAw8aO z!Q9I|u{=mBwi{LfikR!u^WF$I=EcuAl5|XY#d8}dlcPr88A@z?c226C2aL-~vqvuX zdV_1tZnSg`1LpmouCY>vOW{nZO(I8ID$HuJ6yT{xkjk`pek*1~IP3!R9Yj{|(6gkZ zqi4Gs-~i^1j}cc*<3tckys@54CC*pWgU^{nLCGUg^oj506;OdB>w*VeauW3^(mop=Eay1kRJ4u=hNI zU4gg)94iYgejDu35_3%``=Ajp0`YZ!_zE$5q3gzRq)-cdwun8&sgd3pB88bt#?&~& zC@KGNTZMgFjtEh5Fa!wK>5@G*1Ved|U5N5dUK1G^;<{_E*IJA1wO}y9yj zuOmV!sSBYZhJK~3qyY4Apa(`69S^p!*TpbR%ofJh>a>m*p<|C1C99rD@I`Ol)jAKH zDMOYTmMJbJn-KsFo7O;Ye%7MuwrIpEtz6;?X`cPqnY*avTDhzK2?Huf!Xd#oQi3L; z(A)+Bgc?SeJ(*Dzi};WPyv4>T5B-T;-T;O4lu0hhc^#_KHkwhc0vb@rA^nQ_PM6gBD@N|s! z8YFFg0qj1=wCoJ_QI+X>#L*_@P)hsN_(Br?4G}rt8287cFaO2H8SQ=*eb|;7YBlK5IHQIKGCm}u-3k#4huLFbrwWTpU}>^bBEd*r+sUxob^W3 z5_AyI4_RBarmk>}Ep>u00Tz73feA5m@gTAjGPz)6t-W@2aRprvXi}1Z9CR7o1><}N zMw(b8_V053d8>zu@nEpL_i|2$$P8dDREn&xGj-3&*51(}R@t$jH{t)TukUVee7}8o zviE%VM`b2;_^KF8#zKh&*Ql`H6a08iH0q&X86-z*Yt-R@+aD}+G~Cn}Qe&LZ0@+Dh zNZ2UhO!3Vq_DrYvLepqWWSml1mBb*Wr$G?A1d%0bHq0Wm2WL=>H_Qdi6nm&FPbn-7y}1HbIZV&kA9|AdCt${ zQF(_oWOI>apD{0@YfXE?k`p2JvikJzc`q_O|5&$C^i%}@0!=Rdh4Cg%2=jFm?u`GR zy*KZ!>$vVj|Hr4el3`!aA~;EwWlEw9Vh|CN03QHK)*~b=5V#}}VPG&wWGrPRwCPTI zZn5VvolbYB(?h^+rW-TYdLanVYZ=7o|L^@I@3((@?>cqPJ@?{*q-Fa(Gmdc2IaRx= z_FTKFcCq+Ni)je&e4UZAknE?+#4W-^W3jh7NF+}+aGCONV)O|25Wvb;1!dGij7t_9Ja(F(mvUQ_D@sQ)uT&Bk&nma@y z;-Vm69rA*y4hvP)>SE1sPu#lN`@~qMG6V+)0HFxn(}d{=?h^6ZKBJWF-<(fcfiBaL zLix<}GOq+@n>iocwK!T}G6zu1l{uf!ori0@X5~dy{UY9@- zb$P^qoirMYNg9|$?5sp06zO=~1QrD)dP|W&h%Ny@9Ge#>bH>^xyvTGDU53WI zjWD=OazgjpGA@UfO4|DsFIT~chMiU?KdH93a?E{dw9s(MvF8k>WJ|$dOYZZZf2$sZwBo%v4+#(uh zR2LVfuIri1WLZH6C@o@0q?P=}2%747HM! z02ZoinWrcf-(kRk?qpKwSm9*hbwNpDKrp3eX2L39XdtSXr&p^xpbAyp)kMwwhN{zq zlV4b$Dq$O7n=hNP+s{wE(TY9j-^FC|+8A8^%h-iIIWkt7Zfd$*nx4R}3&|OQw2xRj zD_y%&xG{~b#3da3zdl_m6-11X?&Joz1qJ}gjtbazK7$HV*RX~(DS0ul&mJ4zn^021 z71-Z+efD!n#DS0mypWkGY}b+1ztO1?=A2|o(Sn2|W(qChOugD~HC{zX3XO~+&WbrO zcfb25?8*hN&&@)lz;0t)g*cADz{vOmw|JuX-g@ZLL? zk*)(bXx~tTw3kyj>y9JU{$ni5OJsXuRp=?odj!PXWuBQzIy$B_aGOx$X z*`}red>X!n(>EN89x}kJ4*ULzZW4f@hS`lqm#)F2#>8* z1=)26o~Rn;3ABHqv(Vq$HSl3uUuU7azi_#)_k->Z>e{54w9guvP1am-{;V`wP8&P2HC+U+nJ0yY8O$i&r|jd)_acN4=ij zfx^Y^OWl&7e4w`=4H~F!?C%1wOPzh~7XZ2KeD}rffsc+ib#)IQp?QCytGBPvRzUo7 zpu7Fb#kRh}Rsw^8-8-`Y*IyyeLgIwOv8) z`=oz`_TI}M^>x30VW4oK_hLsUUY_qn&)Uvk?9`?(sP>C(-ItCRI@&I^y)V7%EA#?N zUy~Hm-W5K)fZVE`ebQbV{?|Ux-PXz%SA=))t<<@WXsgsLBQ_jlr)eP4IKAf&6W z_tNnuK__YeI)I>F4}!e{MNmxy1&W}EAnXbT?R_b9bhcdtERd%sMZOb9iuH7fyxy6? zQht)1&i$SwUKR9H9H%;3P^n%+ehoPK(vw{d4tct}dqXb}Vax`{^73=TM2dkIuRspr z@UDYol#Gq@M!Te(MBVMlSB+yAK`+^X3m~(C|bx;JkVuTpW~(D#Tpn z5oSLM&hc;p6vYFd!<5gn<2-#-43QzJfScA8zc|czy|I(h)@MV!9 zk<~oB28skq#3;{=HiynjKcBCc&?EU1usnVmjQU+`^I>j1d0C}3T!qndFC_J#Inw{K zG!vJnrFjY#cx+Otfwgeh4al$uIUxHv4oQ`IO4!a$z+gK%&8{)vZIp&5hOoZ^nXZPB zo-TpL^$OWLaUGyjA4Gp7cc}{8sxc)Q6}zxKO7~q!cXeKLt%M!CSjZB{@IBznOd>rR zZx83DrKl6f<>Ucl05u%zmC@L?C=1H9y7p@zJDDjFfpM@FoTfTv~JT&C=_vPeY(K!qdIr`SUV(BO2td7AS$MT zvb{jO-wfSStO%F7HW8}^r^tu~mtY5j`y8VL1@uSwF_EEGjaemmcr-ZEtzp) zrbbtcCvIhiM{#tqZ29I5d58b92QvKvJ79|?D4vWkkiQEZ!$ZV5{-k}cCzh!Vj9I&* z3bz>=S1#uQMd+`7_q*Q}tCEC<)P0zp@?AE_{*M~Ey#~KT`h)=BFg)=iIV-ifPzs2+ za@+X#lZLHbKhOkF8XyEc7%0@d-H18MCPD!shAh>LWsYO%qbUN&8t$1F%1D|xVjqyv zqil#Wg*`#fXhW@+S8+1@D?)%qhopq>GpTtG$Mb$l5ql@MYC zT*iThN3d6hcu%Yf+YYw#6?qK1Eci`&WPU9TsgzAN@Cl2tc5t||h@738!|`CnBd8$; z9lU0a34%T0@i~x;?3Mrya0>Rl!uO-rtTFsyE5~qTEdFfl9b`md)y97^c?*7-wl4fk zUM9L?P7An=l|mLjdWbG!G!Z&Q7Xm#J))^^5cMT3q5Az%j(kw_Pt;D;yqeXZBi8-@v zWzsGbjQXpfPB_&WZ|IdW>T$6+kQmpd#DzBa1&6|<15nqjfD(zuQV`q;4Y<-j83i8Y)f^=E8lOKlT3X6MIzFa6|oX|vV!l^6Oh{g3Bn^yR�{W!A9%P>K|0?%)h1 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 F_lUv4zu#46g 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 z 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 b=l}a}e_x6J`CtCeAOF*De)AiiQJDV^93d3o literal 0 HcmV?d00001 diff --git a/controllers/AdminController.php b/controllers/AdminController.php new file mode 100755 index 0000000..5a7e0fb --- /dev/null +++ b/controllers/AdminController.php @@ -0,0 +1,254 @@ +_request->isPost() && $form->isValid($_POST)) { + + // Set the options. + foreach ($form->getValues() as $option => $value) { + set_option($option, $value); + } + + } + + // Are the current parameters valid? + if (ESearch_Helpers_Index::pingEServer()) { + + // Notify valid connection. + $this->_helper->flashMessenger( + __('E connection is valid.'), 'success' + ); + + } + + // Notify invalid connection. + else $this->_helper->flashMessenger( + __('E connection is invalid.'), 'error' + ); + + $this->view->form = $form; + + } + + /** + * Display the "Collection Configuration" form. + * + * @return void + * @author Eric Rochester + **/ + public function collectionsAction() + { + if ($this->_request->isPost()) { + $this->_updateCollections($this->_request->getPost()); + } + $this->view->form = $this->_collectionsForm(); + } + + /** + * This updates the excluded collections. + * + * @param array $post The post data to update from. + * @return void + * @author Eric Rochester + **/ + protected function _updateCollections($post) + { + $etable = $this->_helper->db->getTable('ESearchExclude'); + $etable->query("DELETE FROM {$etable->getTableName()};"); + + $c = 0; + if (isset($post['solrexclude'])) { + foreach ($post['solrexclude'] as $exc) { + $exclude = new ESearchExclude(); + $exclude->collection_id = $exc; + $exclude->save(); + $c += 1; + } + } + + $this->_helper->_flashMessenger("$c collection(s) excluded."); + } + + /** + * This returns the form for the collections. + * + * @return Zend_Form + * @author Eric Rochester + **/ + protected function _collectionsForm() + { + $ctable = $this->_helper->db->getTable('Collection'); + $private = (int)get_option('solr_search_display_private_items'); + + if ($private) { + $collections = $ctable->findAll(); + } else { + $collections = $ctable->findBy(array('public' => 1)); + } + + $form = new Zend_Form(); + $form->setAction(url('esearch/collections'))->setMethod('post'); + + $collbox = new Zend_Form_Element_MultiCheckbox('solrexclude'); + $form->addElement($collbox); + foreach ($collections as $c) { + $title = metadata($c, array('Dublin Core', 'Title')); + $collbox->addMultiOption("{$c->id}", $title); + } + + $etable = $this->_helper->db->getTable('ESearchExclude'); + $excludes = array(); + foreach ($etable->findAll() as $exclude) { + $excludes[] = "{$exclude->collection_id}"; + } + $collbox->setValue($excludes); + + $form->addElement('submit', 'Exclude'); + + return $form; + } + + /** + * Update the set of fields in the facet set. + * + * @author Eric Rochester + */ + public function updatefacetAction() + { + $fieldTable = $this->_helper->db->getTable('ESearchField'); + $fieldTable->updateFacetMappings(); + $this->redirect('esearch/fields'); + } + + /** + * Display the "Field Configuration" form. + */ + public function fieldsAction() + { + + // Get the facet mapping table. + $fieldTable = $this->_helper->db->getTable('ESearchField'); + + // If the form was submitted. + if ($this->_request->isPost()) { + + // Gather the POST arguments. + $post = $this->_request->getPost(); + + // Save the facets. + foreach ($post['facets'] as $name => $data) { + + // Were "Is Indexed?" and "Is Facet?" checked? + $indexed = array_key_exists('is_indexed', $data) ? 1 : 0; + $faceted = array_key_exists('is_facet', $data) ? 1 : 0; + + // Load the facet mapping. + $facet = $fieldTable->findBySlug($name); + + // Apply the updated values. + $facet->label = $data['label']; + $facet->is_indexed = $indexed; + $facet->is_facet = $faceted; + $facet->save(); + + } + + // Flash success. + $this->_helper->flashMessenger( + __('Fields successfully updated! Be sure to reindex.'), + 'success' + ); + + } + + // Assign the facet groups. + $this->view->groups = $fieldTable->groupByElementSet(); + + } + + + /** + * Display the "Results Configuration" form. + */ + public function resultsAction() + { + + $form = new ESearch_Form_Results(); + + // If a valid form was submitted. + if ($this->_request->isPost() && $form->isValid($_POST)) { + + // Set the options. + foreach ($form->getValues() as $option => $value) { + set_option($option, $value); + } + + // Flash success. + $this->_helper->flashMessenger( + __('Highlighting options successfully saved!'), 'success' + ); + + } + + $this->view->form = $form; + + } + + + /** + * Display the "Index Items" form. + */ + public function reindexAction() + { + + $form = new ESearch_Form_Reindex(); + + if ($this->_request->isPost()) { + try { + + // Clear and reindex. + Zend_Registry::get('job_dispatcher')->sendLongRunning( + 'ESearch_Job_Reindex' + ); + + // Flash success. + $this->_helper->flashMessenger( + __('Reindexing started.'), 'success' + ); + + } catch (Exception $err) { + + // Flash error. + $this->_helper->flashMessenger( + $err->getMessage(), 'error' + ); + + } + } + + $this->view->form = $form; + + } + + +} diff --git a/controllers/ResultsController.php b/controllers/ResultsController.php new file mode 100755 index 0000000..0e4a86a --- /dev/null +++ b/controllers/ResultsController.php @@ -0,0 +1,173 @@ +_fields = $this->_helper->db->getTable('ESearchField'); + } + + + /** + * Intercept queries from the simple search form. + */ + public function interceptorAction() + { + $this->_redirect('esearch?'.http_build_query(array( + 'q' => $this->_request->getParam('query') + ))); + } + + + /** + * Display E results. + */ + public function indexAction() + { + + // Get pagination settings. + $limit = get_option('per_page_public'); + $page = $this->_request->page ? $this->_request->page : 1; + $start = ($page-1) * $limit; + + + // determine whether to display private items or not + // items will only be displayed if: + // esearch_display_private_items has been enabled in the E Search admin panel + // user is logged in + // user_role has sufficient permissions + + $user = current_user(); + if(get_option('esearch_display_private_items') + && $user + && is_allowed('Items','showNotPublic')) { + // limit to public items + $limitToPublicItems = false; + } else { + $limitToPublicItems = true; + } + + // Execute the query. + $results = $this->_search($start, $limit, $limitToPublicItems); + + // Set the pagination. + Zend_Registry::set('pagination', array( + 'page' => $page, + 'total_results' => $results->response->numFound, + 'per_page' => $limit + )); + + // Push results to the view. + $this->view->results = $results; + + } + + + /** + * Pass setting to E search + * + * @param int $offset Results offset + * @param int $limit Limit per page + * @return EResultDoc E results + */ + protected function _search($offset, $limit, $limitToPublicItems = true) + { + + // Connect to E. + $solr = ESearch_Helpers_Index::connect(); + + // Get the parameters. + $params = $this->_getParameters(); + + // Construct the query. + $query = $this->_getQuery($limitToPublicItems); + + // Execute the query. + return $e>search($query, $offset, $limit, $params); + + } + + + /** + * Form the complete E query. + * + * @return string The E query. + */ + protected function _getQuery($limitToPublicItems = true) + { + + // Get the `q` GET parameter. + $query = $this->_request->q; + + // If defined, replace `:`; otherwise, revert to `*:*`. + // Also, clean it up some. + if (!empty($query)) { + $query = str_replace(':', ' ', $query); + $to_remove = array('[', ']'); + foreach ($to_remove as $c) { + $query = str_replace($c, '', $query); + } + } else { + $query = '*:*'; + } + + // Get the `facet` GET parameter + $facet = $this->_request->facet; + + // Form the composite E query. + if (!empty($facet)) $query .= " AND {$facet}"; + + // Limit the query to public items if required + if($limitToPublicItems) { + $query .= ' AND public:"true"'; + } + + return $query; + + } + + + /** + * Construct the E search parameters. + * + * @return array Array of fields to pass to E + */ + protected function _getParameters() + { + + // Get a list of active facets. + $facets = $this->_fields->getActiveFacetKeys(); + + return array( + + 'facet' => 'true', + 'facet.field' => $facets, + 'facet.mincount' => 1, + 'facet.limit' => get_option('esearch_facet_limit'), + 'facet.sort' => get_option('esearch_facet_sort'), + 'hl' => get_option('esearch_hl')?'true':'false', + 'hl.snippets' => get_option('esearch_hl_snippets'), + 'hl.fragsize' => get_option('esearch_hl_fragsize'), + 'hl.maxAnalyzedChars' => get_option('esearch_hl_max_analyzed_chars'), + 'hl.fl' => '*_t' + + ); + + } + + +} diff --git a/forms/ESearch_Form_Reindex.php b/forms/ESearch_Form_Reindex.php new file mode 100755 index 0000000..d5346c8 --- /dev/null +++ b/forms/ESearch_Form_Reindex.php @@ -0,0 +1,29 @@ +addElement('submit', 'submit', array( + 'label' => __('Clear and Reindex') + )); + + } + + +} diff --git a/forms/ESearch_Form_Results.php b/forms/ESearch_Form_Results.php new file mode 100755 index 0000000..6908fe3 --- /dev/null +++ b/forms/ESearch_Form_Results.php @@ -0,0 +1,141 @@ +addElement('checkbox', 'esearch_hl', array( + 'label' => __('Enable Highlighting'), + 'description' => __('Display snippets with highlighted term matches.'), + 'value' => get_option('esearch_hl') + )); + + // Number of Snippets: + $this->addElement('text', 'esearch_hl_snippets', array( + 'label' => __('Number of Snippets'), + 'description' => __('The maximum number of snippets to display.'), + 'value' => get_option('esearch_hl_snippets'), + 'required' => true, + 'size' => 40, + 'validators' => array( + array('validator' => 'Int', 'breakChainOnFailure' => true, 'options' => + array( + 'messages' => array( + Zend_Validate_Int::NOT_INT => __('Must be an integer.') + ) + ) + ) + ) + )); + + // Snippet Length: + $this->addElement('text', 'esearch_hl_fragsize', array( + 'label' => __('Snippet Length'), + 'description' => __('The maximum number of characters to display in a snippet.'), + 'value' => get_option('esearch_hl_fragsize'), + 'required' => true, + 'size' => 40, + 'validators' => array( + array('validator' => 'Int', 'breakChainOnFailure' => true, 'options' => + array( + 'messages' => array( + Zend_Validate_Int::NOT_INT => __('Must be an integer.') + ) + ) + ) + ) + )); + + // Max Analyzed Chars + $this->addElement('text', 'esearch_hl_max_analyzed_chars', array( + 'label' => __('Extent of Document Highlightable'), + 'description' => __('How much of the document can be highlighted, in characters. Occurrences past this point will not be returned in the results highlighting.'), + 'value' => get_option('esearch_hl_max_analyzed_chars'), + 'required' => true, + 'size' => 10, + 'validators' => array( + array( + 'validator' => 'Int', + 'breakChainOnFailure' => true, + 'options' => array( + 'messages' => array( + Zend_Validate_Int::NOT_INT => __('Must be an integer.') + ) + ) + ) + ) + )); + + // Facet Ordering: + $this->addElement('select', 'esearch_facet_sort', array( + 'label' => __('Facet Ordering'), + 'description' => __('The sorting criteria for result facets.'), + 'multiOptions' => array( 'index' => __('Alphabetical'), 'count' => __('Occurrences')), + 'value' => get_option('esearch_facet_sort') + )); + + // Maximum Facet Count: + $this->addElement('text', 'esearch_facet_limit', array( + 'label' => __('Facet Count'), + 'description' => __('The maximum number of facets to display.'), + 'value' => get_option('esearch_facet_limit'), + 'required' => true, + 'size' => 40, + 'validators' => array( + array('validator' => 'Int', 'breakChainOnFailure' => true, 'options' => + array( + 'messages' => array( + Zend_Validate_Int::NOT_INT => __('Must be an integer.') + ) + ) + ) + ) + )); + + // Display Private Items: + $this->addElement('checkbox', 'esearch_display_private_items', array( + 'label' => __('Display private items'), + 'description' => __('Display private items for all user roles with sufficient permission to view them.'), + 'value' => get_option('esearch_display_private_items') + )); + + + // Submit: + $this->addElement('submit', 'submit', array( + 'label' => __('Save Settings') + )); + + $this->addDisplayGroup(array( + 'esearch_hl', + 'esearch_hl_snippets', + 'esearch_hl_fragsize', + 'esearch_facet_sort', + 'esearch_facet_limit', + 'esearch_display_private_items' + ), 'fields'); + + $this->addDisplayGroup(array( + 'submit' + ), 'submit_button'); + + } + + +} diff --git a/forms/ESearch_Form_Server.php b/forms/ESearch_Form_Server.php new file mode 100755 index 0000000..bba9fbe --- /dev/null +++ b/forms/ESearch_Form_Server.php @@ -0,0 +1,87 @@ +addElement('text', 'esearch_host', array( + 'label' => __('Server Host'), + 'description' => __('The location of the Solr server.'), + 'value' => get_option('esearch_host'), + 'required' => true, + 'size' => 40 + )); + + // Server Port: + $this->addElement('text', 'esearch_port', array( + 'label' => __('Server Port'), + 'description' => __('The port that Solr is listening on.'), + 'value' => get_option('esearch_port'), + 'required' => true, + 'size' => 40, + 'validators' => array( + array('validator' => 'Int', 'breakChainOnFailure' => true, 'options' => + array( + 'messages' => array( + Zend_Validate_Int::NOT_INT => __('Must be an integer.') + ) + ) + ) + ) + )); + + // Core URL: + $this->addElement('text', 'esearch_core', array( + 'label' => __('Core URL'), + 'description' => __('The URL of the Solr core to index against.'), + 'value' => get_option('esearch_core'), + 'required' => true, + 'size' => 40, + 'validators' => array( + array('validator' => 'Regex', 'breakChainOnFailure' => true, 'options' => + array( + 'pattern' => '/\/.*\//i', + 'messages' => array( + Zend_Validate_Regex::NOT_MATCH => __('Invalid core URL.') + ) + ) + ) + ) + )); + + // Submit: + $this->addElement('submit', 'submit', array( + 'label' => __('Save Settings') + )); + + $this->addDisplayGroup(array( + 'esearch_host', + 'esearch_port', + 'esearch_core' + ), 'fields'); + + $this->addDisplayGroup(array( + 'submit' + ), 'submit_button'); + + } + + +} diff --git a/helpers/ESearch_Helpers_Facet.php b/helpers/ESearch_Helpers_Facet.php new file mode 100755 index 0000000..4aed18f --- /dev/null +++ b/helpers/ESearch_Helpers_Facet.php @@ -0,0 +1,136 @@ +[\w]+):"(?P[^"]+)"/', + $_GET['facet'], $matches + ); + + // Collapse into an array of pairs. + foreach ($matches['field'] as $i => $field) { + $facets[] = array($field, $matches['value'][$i]); + } + + } + + return $facets; + + } + + + /** + * Rebuild the URL with a new array of facets. + * + * @param array $facets The parsed facets. + * @return string The new URL. + */ + public static function makeUrl($facets) + { + + // Collapse the facets to `:` delimited pairs. + $fParam = array(); + foreach ($facets as $facet) { + $fParam[] = "{$facet[0]}:\"{$facet[1]}\""; + } + + // Implode on ` AND `. + $fParam = urlencode(implode(' AND ', $fParam)); + + // Get the `q` parameter, reverting to ''. + $qParam = array_key_exists('q', $_GET) ? $_GET['q'] : ''; + + // Get the base results URL. + $results = url('solr-search'); + + // String together the final route. + return htmlspecialchars("$results?q=$qParam&facet=$fParam"); + + } + + + /** + * Add a facet to the current URL. + * + * @param string $field The facet field. + * @param string $value The facet value. + * @return string The new URL. + */ + public static function addFacet($field, $value) + { + + // Get the current facets. + $facets = self::parseFacets(); + + // Add the facet, if it's not already present. + if (!in_array(array($field, $value), $facets)) { + $facets[] = array($field, $value); + } + + // Rebuild the route. + return self::makeUrl($facets); + + } + + + /** + * Remove a facet to the current URL. + * + * @param string $field The facet field. + * @param string $value The facet value. + * @return string The new URL. + */ + public static function removeFacet($field, $value) + { + + // Get the current facets. + $facets = self::parseFacets(); + + // Reject the field/value pair. + $reduced = array(); + foreach ($facets as $facet) { + if ($facet !== array($field, $value)) $reduced[] = $facet; + } + + // Rebuild the route. + return self::makeUrl($reduced); + + } + + + /** + * Get the human-readable label for a facet key. + * + * @param string $key The facet key. + * @return string The label. + */ + public static function keyToLabel($key) + { + $fields = get_db()->getTable('ESearchField'); + return $fields->findBySlug(rtrim($key, '_s'))->label; + } + + +} diff --git a/helpers/ESearch_Helpers_Index.php b/helpers/ESearch_Helpers_Index.php new file mode 100755 index 0000000..0a71e39 --- /dev/null +++ b/helpers/ESearch_Helpers_Index.php @@ -0,0 +1,287 @@ + + **/ + public static function connect($options=array()) + { + + $server = array_key_exists('solr_search_host', $options) + ? $options['solr_search_host'] + : get_option('solr_search_host'); + + $port = array_key_exists('solr_search_port', $options) + ? $options['solr_search_port'] + : get_option('solr_search_port'); + + $core = array_key_exists('solr_search_core', $options) + ? $options['solr_search_core'] + : get_option('solr_search_core'); + + return new Apache_Solr_Service($server, $port, $core); + + } + + /** + * This indexes something that implements Mixin_ElementText into a Solr Document. + * + * @param array $fields The fields to index. + * @param Mixin_ElementText $item The item containing the element texts. + * @param Apache_Solr_Document $doc The document to index everything into. + * @return void + * @author Eric Rochester + **/ + public static function indexItem($fields, $item, $doc) + { + foreach ($item->getAllElementTexts() as $text) { + $field = $fields->findByText($text); + + // Set text field. + if ($field->is_indexed) { + $doc->setMultiValue($field->indexKey(), $text->text); + } + + // Set string field. + if ($field->is_facet) { + $doc->setMultiValue($field->facetKey(), $text->text); + } + } + } + + + /** + * This takes an Omeka_Record instance and returns a populated + * Apache_Solr_Document. + * + * @param Omeka_Record $item The record to index. + * + * @return Apache_Solr_Document + * @author Eric Rochester + **/ + public static function itemToDocument($item) + { + + $fields = get_db()->getTable('ESearchField'); + + $doc = new Apache_Solr_Document(); + $doc->setField('id', "Item_{$item->id}"); + $doc->setField('resulttype', 'Item'); + $doc->setField('model', 'Item'); + $doc->setField('modelid', $item->id); + + // extend $doc to to include and items public / private status + $doc->setField('public', $item->public); + + // Title: + $title = metadata($item, array('Dublin Core', 'Title')); + $doc->setField('title', $title); + + // Elements: + self::indexItem($fields, $item, $doc); + + // Tags: + foreach ($item->getTags() as $tag) { + $doc->setMultiValue('tag', $tag->name); + } + + // Collection: + if ($collection = $item->getCollection()) { + $doc->collection = metadata( + $collection, array('Dublin Core', 'Title') + ); + } + + // Item type: + if ($itemType = $item->getItemType()) { + $doc->itemtype = $itemType->name; + } + + $doc->featured = (bool) $item->featured; + + // File metadata + foreach ($item->getFiles() as $file) { + self::indexItem($fields, $file, $doc); + } + + return $doc; + + } + + + /** + * This returns the URI for an Omeka_Record. + * + * @param Omeka_Record $record The record to return the URI for. + * + * @return string $uri The URI to access the record with. + * @author Eric Rochester + **/ + public static function getUri($record) + { + $uri = ''; + $action = 'show'; + $rc = get_class($record); + + if ($rc === 'SimplePagesPage') { + $uri = url($record->slug); + } + + else if ($rc === 'ExhibitPage') { + + $exhibit = $record->getExhibit(); + $exUri = self::getSlugUri($exhibit, $action); + $uri = "$exUri/$record->slug"; + + } else if (property_exists($record, 'slug')) { + $uri = self::getSlugUri($record, $action); + } else { + $uri = record_url($record, $action); + } + + // Always index public URLs. + $uri = preg_replace('|/admin/|', '/', $uri, 1); + + return $uri; + } + + + /** + * This returns the URL for an Omeka_Record with a 'slug' property. + * + * @param Omeka_Record $record The sluggable record to create the URL for. + * @param string $action The action to access the record with. + * + * @return string $uri The URI for the record. + * @author Eric Rochester + **/ + public static function getSlugURI($record, $action) + { + // Copied from omeka/applications/helpers/UrlFunctions.php, record_uri. + // Yuck. + $recordClass = get_class($record); + $inflector = new Zend_Filter_Word_CamelCaseToDash(); + $controller = strtolower($inflector->filter($recordClass)); + $controller = Inflector::pluralize($controller); + $options = array( + 'controller' => $controller, + 'action' => $action, + 'id' => $record->slug + ); + $uri = url($options, 'id'); + + return $uri; + } + + + /** + * This pings the Solr server with the given options and returns true if + * it's currently up. + * + * @param array $options The configuration to test. Missing values will be + * pulled from the current set of options. + * + * @return bool + * @author Eric Rochester + */ + public static function pingEServer($options=array()) + { + try { + return self::connect($options)->ping(); + } catch (Exception $e) { + return false; + } + } + + + /** + * This re-indexes everything in the Omeka DB. + * + * @return void + * @author Eric Rochester + **/ + public static function indexAll($options=array()) + { + + $solr = self::connect($options); + + $db = get_db(); + $table = $db->getTable('Item'); + $select = $table->getSelect(); + + // Removed in order to index both public and private items + // $table->filterByPublic($select, true); + $table->applySorting($select, 'id', 'ASC'); + + $excTable = $db->getTable('ESearchExclude'); + $excludes = array(); + foreach ($excTable->findAll() as $e) { + $excludes[] = $e->collection_id; + } + if (!empty($excludes)) { + $select->where( + 'collection_id IS NULL OR collection_id NOT IN (?)', + $excludes); + } + + // First get the items. + $pager = new ESearch_DbPager($db, $table, $select); + while ($items = $pager->next()) { + foreach ($items as $item) { + $docs = array(); + $doc = self::itemToDocument($item); + $docs[] = $doc; + $solr->addDocuments($docs); + } + $solr->commit(); + } + + // Now the other addon stuff. + $mgr = new ESearch_Addon_Manager($db); + $docs = $mgr->reindexAddons(); + $solr->addDocuments($docs); + $solr->commit(); + + $solr->optimize(); + + } + + + /** + * This deletes everything in the Solr index. + * + * @param array $options The configuration to test. Missing values will be + * pulled from the current set of options. + * + * @return void + * @author Eric Rochester + **/ + public static function deleteAll($options=array()) + { + + $solr = self::connect($options); + + $solr->deleteByQuery('*:*'); + $solr->commit(); + $solr->optimize(); + + } + + +} diff --git a/helpers/ESearch_Helpers_View.php b/helpers/ESearch_Helpers_View.php new file mode 100755 index 0000000..99864ce --- /dev/null +++ b/helpers/ESearch_Helpers_View.php @@ -0,0 +1,97 @@ + + **/ + public static function getBaseUrl() + { + return url('solr-search'); + } + + + /** + * Lookup the element name for a solr element. + * + * @param string $field Field name to look up. + * + * @return string Human readable solr element name. + */ + public static function lookupElement($field) + { + $fieldArray = explode('_', $field); + $fieldId = $fieldArray[0]; + $db = get_db(); + $element = $db->getTable('Element')->find($fieldId); + return $element['name']; + } + + + /** + * Return the path for an image. + * + * @param string $type Omeka File type (size). + * @param int $fileId Id of the file to look up. + * + * @return string Link to file. + */ + public static function getImagePath($type='fullsize', $fileId) + { + $db = get_db(); + $file = $db->getTable('File')->find($fileId); + return $file->getWebPath($type); + } + + + /** + * Generate an image tag for use in search results. + * + * @param int $image_id Image to look up. + * @param string $alt Alt text for image. + * + * @return string $html Link to image. + * @author Wayne Graham + **/ + public static function createResultImgHtml($image_id, $alt) + { + $html = ''; + $thumbnail = self::getImagePath('square_thumbnail', $image_id); + $fullsize = self::getImagePath('fullsize', $image_id); + + $html .= ''; + $html .= '' . $alt . ''; + $html .= ''; + + return $html; + } + + + /** + * Get the URL for the record that corresponds to a Solr document. + * + * @param Apache_Solr_Document $doc A Solr document. + * + * @return string The record URL. + */ + public static function getDocumentUrl($doc) + { + $record = get_db()->getTable($doc->model)->find($doc->modelid); + return ESearch_Helpers_Index::getUri($record); + } + + +} diff --git a/jobs/ESearch_Job_Reindex.php b/jobs/ESearch_Job_Reindex.php new file mode 100755 index 0000000..920807a --- /dev/null +++ b/jobs/ESearch_Job_Reindex.php @@ -0,0 +1,25 @@ +n#+#UH5J40X#IKtd6@r2wdJBsG*WI%p5$_(hnCf3w_v@jG^MeJA?0JA@bqNAAJ_?aUe>O5h~85p06%zz*nvJ@5hW8?XWX1U?KN zxm$=0U>$rEJO}a`3&r?a!Oubb#La@gftxY@6TBZ>dyfzgf?L6D;689ScmiAxw!yVv z4`llV@DcDbxCML%WV??*p66qbco3d2fMizvbTC{s72v9R)|g8h9W0a=}+Y zUhgfC^Klhq`wxrzpMdPgSKub_SMW*jZ?FRHhSQwlS#St^7CZ)C0g*!SDTtr=0tfc% zTkt(-^9#s&T)~0kcoXFHKLA;e>md8F13@>ygCOhiBFK8Y2C^RSf~?1lV*E$JjR=zU zs1!U3vL3@A$J+ookI#Xew-{tSE`yjtybrS9*FmXEyIakmu#b{^PMMu3g|$=qN9- z75y>vGCDWTGq(YBUV|I^vJaho;l?>)z1Y|7=y#%XV_!I4$SwAw_Zw?fEe0I-j^dni z>!9-E8;=L(@@8Uk6Z90EDUC-?%|fgECg!N)i;TcHwT^*S|cGgGr-T*iFMOX660mTW?^NvP>!V7p|yiu_<+#eBOH_^FmQNa8Y0 zhe{enURQY+Xdy{88Kviyrfy>FA?hU>M;W*AoK|Hq4s%U=X+{NcTV299z;ir%E44O> zsinf?BCoA;3zA_-NhhDWm$qO}fmM;AAchq`nR%H_hxrTda1{%AU783~*fz_rBc|Ji zh3O=F6vV!$#hjVSslYI)8~EK2-Qpfa(jrz~Jzuwz&p#nOW%I=dJ{YSd{G*0$EA5I2 zBwMvPDqq55jkFkx3vFp-8FxdYMCNFIOlwytP+ zUaGZ-jcFkALh-55P z3#C0&6;eef6&a|#Y%i5XQ~IKbEfW>W#c9++Ibwf#U8$vKl{Wl9J21UaF0dYwOdjih zJh+%u)N^5(P2sCp;U%F&Q>RMI#Wt|m17a?7uc)v7?wr=id8p(}ww-6-0jk%fS+ARf zQk%MLm1Y#bN1Q3+2W{tM>Kmt-xYV__=T<5{-cM<(>?Kjf6wlzzR^gQ9>8k|Bq{@`0 z8%<|iV-H|{=M-FXX|Osl=v0q7gGXrKhA5I>ehibSAJ@j7xDZ8=BFrS^7J$`WfLr0RNNE$UkcM9svLV literal 0 HcmV?d00001 diff --git a/languages/en_US.po b/languages/en_US.po new file mode 100755 index 0000000..4cc050b --- /dev/null +++ b/languages/en_US.po @@ -0,0 +1,222 @@ +# Translation for the SolrSearch Omeka plugin. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: ScholarsLab\n" +"Report-Msgid-Bugs-To: http://github.com/scholarslab/SolrSearch/issues\n" +"POT-Creation-Date: 2012-05-24 14:30-0500\n" +"PO-Revision-Date: 2012-08-16 17:32+0000\n" +"Last-Translator: Eric Rochester \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: en_US\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: SolrSearchPlugin.php:222 +msgid "Solr Options" +msgstr "Solr Options" + +#: SolrSearchPlugin.php:244 +msgid "" +"Cannot connect to Solr with the given configuration. Please check your " +"server host, port, and core." +msgstr "Cannot connect to Solr with the given configuration. Please check your server host, port, and core." + +#: SolrSearchPlugin.php:317 models/SolrSearchField.php:84 +msgid "Tag" +msgstr "Tag" + +#: SolrSearchPlugin.php:320 models/SolrSearchField.php:87 +msgid "Result Type" +msgstr "Result Type" + +#: SolrSearchPlugin.php:376 +msgid "ACL not available" +msgstr "ACL not available" + +#: controllers/ConfigController.php:56 +msgid "Solr configuration updated. Be sure to reindex." +msgstr "Solr configuration updated. Be sure to reindex." + +#: controllers/HighlightController.php:46 +msgid "Hit highlighting features modified." +msgstr "Hit highlighting features modified." + +#: controllers/HighlightController.php:48 +msgid "Failed to gather posted data." +msgstr "Failed to gather posted data." + +#: controllers/HighlightController.php:69 +msgid "Highlighting:" +msgstr "Highlighting:" + +#: controllers/HighlightController.php:70 +msgid "Enable/Disable highlighting matches in Solr fields" +msgstr "Enable/Disable highlighting matches in Solr fields" + +#: controllers/HighlightController.php:71 +msgid "True" +msgstr "True" + +#: controllers/HighlightController.php:72 +msgid "False" +msgstr "False" + +#: controllers/HighlightController.php:78 +msgid "Snippets:" +msgstr "Snippets:" + +#: controllers/HighlightController.php:79 +msgid "The maximum number of highlighted snippets to generate" +msgstr "The maximum number of highlighted snippets to generate" + +#: controllers/HighlightController.php:87 +msgid "Snippet Size:" +msgstr "Snippet Size:" + +#: controllers/HighlightController.php:88 +msgid "The maximum number of characters to display in a snippet" +msgstr "The maximum number of characters to display in a snippet" + +#: controllers/ReindexController.php:32 +msgid "Reindex process started." +msgstr "Reindex process started." + +#: controllers/ReindexController.php:52 +msgid "Clear & Reindex" +msgstr "Clear & Reindex" + +#: forms/FacetForm.php:99 +msgid "Update Search Fields" +msgstr "Update Search Fields" + +#: helpers/SolrSearch_Helpers_Query.php:121 +msgid "ALL TERMS" +msgstr "ALL TERMS" + +#: helpers/SolrSearch_Helpers_Query.php:132 +#, php-format +msgid "Remove constraint %s" +msgstr "Remove constraint %s" + +#: helpers/SolrSearch_Helpers_View.php:143 +msgid "Untitled" +msgstr "Untitled" + +#: helpers/SolrSearch_Helpers_View.php:326 +msgid "Server Host:" +msgstr "Server Host:" + +#: helpers/SolrSearch_Helpers_View.php:329 +msgid "Server Port:" +msgstr "Server Port:" + +#: helpers/SolrSearch_Helpers_View.php:333 +msgid "Solr Core Name:" +msgstr "Solr Core Name:" + +#: helpers/SolrSearch_Helpers_View.php:345 +msgid "Facet Field Constraint Order:" +msgstr "Facet Field Constraint Order:" + +#: helpers/SolrSearch_Helpers_View.php:349 +msgid "Occurrences" +msgstr "Occurrences" + +#: helpers/SolrSearch_Helpers_View.php:352 +msgid "Maximum Facet Count:" +msgstr "Maximum Facet Count:" + +#: models/SolrSearchFieldTable.php:29 +msgid "Omeka Categories" +msgstr "Omeka Categories" + +#: views/admin/config/index.php:17 +msgid "Solr Search Configuration" +msgstr "Solr Search Configuration" + +#: views/admin/config/index.php:22 +msgid "Configure SolrSearch Indexing" +msgstr "Configure SolrSearch Indexing" + +#: views/admin/config/index.php:26 views/admin/highlight/index.php:8 +#: views/admin/reindex/index.php:9 +msgid "Field Configuration" +msgstr "Field Configuration" + +#: views/admin/config/index.php:29 views/admin/highlight/index.php:11 +#: views/admin/reindex/index.php:12 +msgid "Hit Highlighting Options" +msgstr "Hit Highlighting Options" + +#: views/admin/config/index.php:32 views/admin/highlight/index.php:14 +#: views/admin/reindex/index.php:15 +msgid "Index Items" +msgstr "Index Items" + +#: views/admin/config/index.php:38 +msgid "Configure Search Fields" +msgstr "Configure Search Fields" + +#: views/admin/config/index.php:53 +msgid "Is Searchable" +msgstr "Is Searchable" + +#: views/admin/config/index.php:54 +msgid "Is Facet" +msgstr "Is Facet" + +#: views/admin/config/index.php:56 +msgid "Field" +msgstr "Field" + +#: views/admin/config/update.php:2 views/admin/reindex/update.php:2 +msgid "Solr Search Facets" +msgstr "Solr Search Facets" + +#: views/admin/config/update.php:4 views/admin/reindex/update.php:4 +msgid "Manage Solr Search Facets" +msgstr "Manage Solr Search Facets" + +#: views/admin/config/update.php:13 views/admin/highlight/update.php:13 +#: views/admin/reindex/update.php:13 +msgid "Return to form." +msgstr "Return to form." + +#: views/admin/highlight/index.php:2 views/admin/highlight/update.php:2 +msgid "Solr Search Hit Highlighting Options" +msgstr "Solr Search Hit Highlighting Options" + +#: views/admin/highlight/index.php:4 views/admin/reindex/index.php:5 +msgid "Configure Solr" +msgstr "Configure Solr" + +#: views/admin/highlight/index.php:18 views/admin/highlight/update.php:4 +msgid "Hit Highlighting" +msgstr "Hit Highlighting" + +#: views/admin/highlight/index.php:24 +msgid "Select hit highlighting options from available fields below." +msgstr "Select hit highlighting options from available fields below." + +#: views/admin/reindex/index.php:2 +msgid "Reindex Items in Solr" +msgstr "Reindex Items in Solr" + +#: views/admin/reindex/index.php:19 +msgid "Reindex All Items" +msgstr "Reindex All Items" + +#: views/admin/reindex/index.php:27 +msgid "" +"Click the button below to reindex all of your public items into the Solr " +"index." +msgstr "Click the button below to reindex all of your public items into the Solr index." + +#: views/shared/results/index.php:46 +#, php-format +msgid "%s results" +msgstr "%s results" diff --git a/languages/nl_BE.mo b/languages/nl_BE.mo new file mode 100755 index 0000000000000000000000000000000000000000..af5e700fbcfd6e7485ac2060c47dbfb0f03f3425 GIT binary patch literal 2239 zcmZ9MyN_E%6vhV#1k5|U0}_WKA}exj@3KhIO%^G;@g^H~v#VWap@XsSv3=v~8Ea;) zo$yG3C}=^VrJ+U8P$C)%8tAAa{s0OJiZn=kXMCM~jC}pOcOGZH`DW(aJ$2$6h4Lz% z*YNy{=M0|TAHW~VM-M7h12y<8Sb&d#cfcyo>L$N0=gNmGM%rMX^YH@kaJsycT`)A|4`CETT)+AiC#8G>Wcyku}JA zq@2bhn#8B)@QAPKj3>vwEP_{;);7u5KzgF{LU#&Qmo_)4-P+ySQ)}8tQ`Z{9-9W+8 zo-G_r^Dv+=VCv@+HqzY7`=!$%x28@z1#8dL9dLI@w{7XjvzxG^f%V}WjjhAKHW|h_ zuB){oM?ZFE;4_(9&1qRy6~)Go7#xeY)DMC;b~oWImpW_K=fow8{Mxan#YxHUhP&YFG1xj z#JnbcL1dJNe&RQ^Z;u)8p>#&p*t2d_S9>#quEL;n{;>m}@F9M$!6>?}M_hexkHu*} zYdoqZ_CK;ARE{$pIIGhtlP?kJz}mWM>%MBclGP0p@=$Q5cAUM5%}zG5WS^bKL@Dic z2e#18Z|Y8Mmt_Z&E#J?xWVP)3q-|3ggfLDUjXq9B*{OH!sNt(II8zN#HPhM1y)W6< zcDCEen&Wt6lFQJS(o%DADQUiwEG^UGa{6`?@69GUCc8YzMaSWO=(@B-i_LU-`K=~O zZBzS@w4FA-(7`%r)+556*xdB#;=5=!{SsT4v{{c*x?BbvjkBv<^z(AGKxeB3OsCU& zkSKYNZ^d0Z3f*bo$ut~zVwvhT(zA7cdba)adbTD#_`jyEM1`4 z>&jU}Z>-Sb!iCzwMTf;^cGq(bxMSF!eAv<2$#rkV``)^)T8@U=P@i$Fab-KWQ{rEr zM_La9O|8q&GD3uS(Bvg!8kBjK<8~Z6cU-3{T;x1qDyPR20L~A^yn(}LG@PMfesc(Y z?-luw>{w!2+)3wyMxsEoL8To@CSZywfQMWq0gN87iHTyuhN2_+0%Jy1zOcv_T&0U6 r0;A-^yGB!X8Mgb^pJUPp2fKL8W+V;nO);l9`M%h8MGQg?;X?fnHXB_A literal 0 HcmV?d00001 diff --git a/languages/nl_BE.po b/languages/nl_BE.po new file mode 100755 index 0000000..e2ecebb --- /dev/null +++ b/languages/nl_BE.po @@ -0,0 +1,223 @@ +# Translation for the SolrSearch Omeka plugin. +# +# Translators: +# Sam Alloing <>, 2012. +msgid "" +msgstr "" +"Project-Id-Version: ScholarsLab\n" +"Report-Msgid-Bugs-To: http://github.com/scholarslab/SolrSearch/issues\n" +"POT-Creation-Date: 2012-05-24 14:30-0500\n" +"PO-Revision-Date: 2012-12-22 10:44+0000\n" +"Last-Translator: Sam Alloing <>\n" +"Language-Team: Dutch (Belgium) (http://www.transifex.com/projects/p/scholarslab/language/nl_BE/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nl_BE\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: SolrSearchPlugin.php:222 +msgid "Solr Options" +msgstr "Solr opties" + +#: SolrSearchPlugin.php:244 +msgid "" +"Cannot connect to Solr with the given configuration. Please check your " +"server host, port, and core." +msgstr "Er kan geen verbinding gemaakt worden met Solr met de huidige configuratie. Gelieve uw server host, poort en core na te kijken" + +#: SolrSearchPlugin.php:317 models/SolrSearchField.php:84 +msgid "Tag" +msgstr "Trefwoorden" + +#: SolrSearchPlugin.php:320 models/SolrSearchField.php:87 +msgid "Result Type" +msgstr "Resultaatstype" + +#: SolrSearchPlugin.php:376 +msgid "ACL not available" +msgstr "ACL niet beschikbaar" + +#: controllers/ConfigController.php:56 +msgid "Solr configuration updated. Be sure to reindex." +msgstr "Solr configuratie werd geรผpdatet. Gelieve te herindexeren" + +#: controllers/HighlightController.php:46 +msgid "Hit highlighting features modified." +msgstr "" + +#: controllers/HighlightController.php:48 +msgid "Failed to gather posted data." +msgstr "" + +#: controllers/HighlightController.php:69 +msgid "Highlighting:" +msgstr "" + +#: controllers/HighlightController.php:70 +msgid "Enable/Disable highlighting matches in Solr fields" +msgstr "" + +#: controllers/HighlightController.php:71 +msgid "True" +msgstr "Waar" + +#: controllers/HighlightController.php:72 +msgid "False" +msgstr "Onwaar" + +#: controllers/HighlightController.php:78 +msgid "Snippets:" +msgstr "" + +#: controllers/HighlightController.php:79 +msgid "The maximum number of highlighted snippets to generate" +msgstr "" + +#: controllers/HighlightController.php:87 +msgid "Snippet Size:" +msgstr "" + +#: controllers/HighlightController.php:88 +msgid "The maximum number of characters to display in a snippet" +msgstr "" + +#: controllers/ReindexController.php:32 +msgid "Reindex process started." +msgstr "" + +#: controllers/ReindexController.php:52 +msgid "Clear & Reindex" +msgstr "" + +#: forms/FacetForm.php:99 +msgid "Update Search Fields" +msgstr "" + +#: helpers/SolrSearch_Helpers_Query.php:121 +msgid "ALL TERMS" +msgstr "ALLE TERMEN" + +#: helpers/SolrSearch_Helpers_Query.php:132 +#, php-format +msgid "Remove constraint %s" +msgstr "" + +#: helpers/SolrSearch_Helpers_View.php:143 +msgid "Untitled" +msgstr "Geen titel" + +#: helpers/SolrSearch_Helpers_View.php:326 +msgid "Server Host:" +msgstr "Server host:" + +#: helpers/SolrSearch_Helpers_View.php:329 +msgid "Server Port:" +msgstr "Server poort:" + +#: helpers/SolrSearch_Helpers_View.php:333 +msgid "Solr Core Name:" +msgstr "Solr Core naam:" + +#: helpers/SolrSearch_Helpers_View.php:345 +msgid "Facet Field Constraint Order:" +msgstr "" + +#: helpers/SolrSearch_Helpers_View.php:349 +msgid "Occurrences" +msgstr "" + +#: helpers/SolrSearch_Helpers_View.php:352 +msgid "Maximum Facet Count:" +msgstr "" + +#: models/SolrSearchFieldTable.php:29 +msgid "Omeka Categories" +msgstr "" + +#: views/admin/config/index.php:17 +msgid "Solr Search Configuration" +msgstr "" + +#: views/admin/config/index.php:22 +msgid "Configure SolrSearch Indexing" +msgstr "" + +#: views/admin/config/index.php:26 views/admin/highlight/index.php:8 +#: views/admin/reindex/index.php:9 +msgid "Field Configuration" +msgstr "" + +#: views/admin/config/index.php:29 views/admin/highlight/index.php:11 +#: views/admin/reindex/index.php:12 +msgid "Hit Highlighting Options" +msgstr "" + +#: views/admin/config/index.php:32 views/admin/highlight/index.php:14 +#: views/admin/reindex/index.php:15 +msgid "Index Items" +msgstr "Indexeer items" + +#: views/admin/config/index.php:38 +msgid "Configure Search Fields" +msgstr "Configureer zoekvelden" + +#: views/admin/config/index.php:53 +msgid "Is Searchable" +msgstr "Is doorzoekbaar" + +#: views/admin/config/index.php:54 +msgid "Is Facet" +msgstr "Is een facet" + +#: views/admin/config/index.php:56 +msgid "Field" +msgstr "Veld" + +#: views/admin/config/update.php:2 views/admin/reindex/update.php:2 +msgid "Solr Search Facets" +msgstr "Solr zoekfacetten" + +#: views/admin/config/update.php:4 views/admin/reindex/update.php:4 +msgid "Manage Solr Search Facets" +msgstr "Beheer Solr zoekfacetten" + +#: views/admin/config/update.php:13 views/admin/highlight/update.php:13 +#: views/admin/reindex/update.php:13 +msgid "Return to form." +msgstr "Ga terug naar formulier" + +#: views/admin/highlight/index.php:2 views/admin/highlight/update.php:2 +msgid "Solr Search Hit Highlighting Options" +msgstr "" + +#: views/admin/highlight/index.php:4 views/admin/reindex/index.php:5 +msgid "Configure Solr" +msgstr "Configureer Solr" + +#: views/admin/highlight/index.php:18 views/admin/highlight/update.php:4 +msgid "Hit Highlighting" +msgstr "" + +#: views/admin/highlight/index.php:24 +msgid "Select hit highlighting options from available fields below." +msgstr "" + +#: views/admin/reindex/index.php:2 +msgid "Reindex Items in Solr" +msgstr "Herindexeer items in Solr" + +#: views/admin/reindex/index.php:19 +msgid "Reindex All Items" +msgstr "Herindexeer alle items" + +#: views/admin/reindex/index.php:27 +msgid "" +"Click the button below to reindex all of your public items into the Solr " +"index." +msgstr "Klik op de onderstaande knop om alle publieke items te herindexeren in de Solr index." + +#: views/shared/results/index.php:46 +#, php-format +msgid "%s results" +msgstr "%s resultaten" diff --git a/languages/template.base.pot b/languages/template.base.pot new file mode 100755 index 0000000..7fd59a4 --- /dev/null +++ b/languages/template.base.pot @@ -0,0 +1,17 @@ +# Translation for the SolrSearch Omeka plugin. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: SolrSearch\n" +"Report-Msgid-Bugs-To: http://github.com/scholarslab/SolrSearch/issues\n" +"POT-Creation-Date: 2012-05-24 14:30-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + diff --git a/languages/template.pot b/languages/template.pot new file mode 100755 index 0000000..3922d65 --- /dev/null +++ b/languages/template.pot @@ -0,0 +1,222 @@ +# Translation for the SolrSearch Omeka plugin. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: SolrSearch\n" +"Report-Msgid-Bugs-To: http://github.com/scholarslab/SolrSearch/issues\n" +"POT-Creation-Date: 2012-05-24 14:30-0500\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: SolrSearchPlugin.php:233 +msgid "Solr Options" +msgstr "" + +#: SolrSearchPlugin.php:255 +msgid "" +"Cannot connect to Solr with the given configuration. Please check your " +"server host, port, and core." +msgstr "" + +#: SolrSearchPlugin.php:328 models/SolrSearchField.php:84 +msgid "Tag" +msgstr "" + +#: SolrSearchPlugin.php:331 models/SolrSearchField.php:87 +msgid "Result Type" +msgstr "" + +#: SolrSearchPlugin.php:387 +msgid "ACL not available" +msgstr "" + +#: controllers/ConfigController.php:56 +msgid "Solr configuration updated. Be sure to reindex." +msgstr "" + +#: controllers/HighlightController.php:46 +msgid "Hit highlighting features modified." +msgstr "" + +#: controllers/HighlightController.php:48 +msgid "Failed to gather posted data." +msgstr "" + +#: controllers/HighlightController.php:69 +msgid "Highlighting:" +msgstr "" + +#: controllers/HighlightController.php:70 +msgid "Enable/Disable highlighting matches in Solr fields" +msgstr "" + +#: controllers/HighlightController.php:71 +msgid "True" +msgstr "" + +#: controllers/HighlightController.php:72 +msgid "False" +msgstr "" + +#: controllers/HighlightController.php:78 +msgid "Snippets:" +msgstr "" + +#: controllers/HighlightController.php:79 +msgid "The maximum number of highlighted snippets to generate" +msgstr "" + +#: controllers/HighlightController.php:87 +msgid "Snippet Size:" +msgstr "" + +#: controllers/HighlightController.php:88 +msgid "The maximum number of characters to display in a snippet" +msgstr "" + +#: controllers/ReindexController.php:32 +msgid "Reindex process started." +msgstr "" + +#: controllers/ReindexController.php:52 +msgid "Clear & Reindex" +msgstr "" + +#: forms/FacetForm.php:99 +msgid "Update Search Fields" +msgstr "" + +#: helpers/SolrSearch_Helpers_Query.php:121 +msgid "ALL TERMS" +msgstr "" + +#: helpers/SolrSearch_Helpers_Query.php:132 +#, php-format +msgid "Remove constraint %s" +msgstr "" + +#: helpers/SolrSearch_Helpers_View.php:143 +msgid "Untitled" +msgstr "" + +#: helpers/SolrSearch_Helpers_View.php:326 +msgid "Server Host:" +msgstr "" + +#: helpers/SolrSearch_Helpers_View.php:329 +msgid "Server Port:" +msgstr "" + +#: helpers/SolrSearch_Helpers_View.php:333 +msgid "Solr Core Name:" +msgstr "" + +#: helpers/SolrSearch_Helpers_View.php:345 +msgid "Facet Field Constraint Order:" +msgstr "" + +#: helpers/SolrSearch_Helpers_View.php:349 +msgid "Occurrences" +msgstr "" + +#: helpers/SolrSearch_Helpers_View.php:352 +msgid "Maximum Facet Count:" +msgstr "" + +#: models/SolrSearchFieldTable.php:29 +msgid "Omeka Categories" +msgstr "" + +#: views/admin/config/index.php:17 +msgid "Solr Search Configuration" +msgstr "" + +#: views/admin/config/index.php:22 +msgid "Configure SolrSearch Indexing" +msgstr "" + +#: views/admin/config/index.php:26 views/admin/highlight/index.php:8 +#: views/admin/reindex/index.php:9 +msgid "Field Configuration" +msgstr "" + +#: views/admin/config/index.php:29 views/admin/highlight/index.php:11 +#: views/admin/reindex/index.php:12 +msgid "Hit Highlighting Options" +msgstr "" + +#: views/admin/config/index.php:32 views/admin/highlight/index.php:14 +#: views/admin/reindex/index.php:15 +msgid "Index Items" +msgstr "" + +#: views/admin/config/index.php:38 +msgid "Configure Search Fields" +msgstr "" + +#: views/admin/config/index.php:53 +msgid "Is Searchable" +msgstr "" + +#: views/admin/config/index.php:54 +msgid "Is Facet" +msgstr "" + +#: views/admin/config/index.php:56 +msgid "Field" +msgstr "" + +#: views/admin/config/update.php:2 views/admin/reindex/update.php:2 +msgid "Solr Search Facets" +msgstr "" + +#: views/admin/config/update.php:4 views/admin/reindex/update.php:4 +msgid "Manage Solr Search Facets" +msgstr "" + +#: views/admin/config/update.php:13 views/admin/highlight/update.php:13 +#: views/admin/reindex/update.php:13 +msgid "Return to form." +msgstr "" + +#: views/admin/highlight/index.php:2 views/admin/highlight/update.php:2 +msgid "Solr Search Hit Highlighting Options" +msgstr "" + +#: views/admin/highlight/index.php:4 views/admin/reindex/index.php:5 +msgid "Configure Solr" +msgstr "" + +#: views/admin/highlight/index.php:18 views/admin/highlight/update.php:4 +msgid "Hit Highlighting" +msgstr "" + +#: views/admin/highlight/index.php:24 +msgid "Select hit highlighting options from available fields below." +msgstr "" + +#: views/admin/reindex/index.php:2 +msgid "Reindex Items in Solr" +msgstr "" + +#: views/admin/reindex/index.php:19 +msgid "Reindex All Items" +msgstr "" + +#: views/admin/reindex/index.php:27 +msgid "" +"Click the button below to reindex all of your public items into the Solr " +"index." +msgstr "" + +#: views/shared/results/index.php:46 +#, php-format +msgid "%s results" +msgstr "" diff --git a/lib/SolrSearch/Addon/Addon.php b/lib/SolrSearch/Addon/Addon.php new file mode 100755 index 0000000..f01970c --- /dev/null +++ b/lib/SolrSearch/Addon/Addon.php @@ -0,0 +1,160 @@ +name = $name; + $this->resultType = $resultType; + $this->table = $table; + $this->idColumn = $idColumn; + $this->parentAddon = $parentAddon; + $this->parentKey = $parentKey; + $this->tagged = $tagged; + $this->flag = $flag; + $this->fields = array(); + $this->children = array(); + } + + + /** + * This tests whether this addon has a flag anywhere up its ancenstors. + * + * @return bool + * @author Eric Rochester + **/ + public function hasFlag() + { + $flag = false; + + if (is_null($this->parentAddon)) { + $flag = !is_null($this->flag); + } else { + $flag = $this->parentAddon->hasFlag(); + } + + return $flag; + } + + + /** + * This returns the field marked title, named title, or null for this + * addon. + * + * @return SolrSearch_Addon_Field|null + * @author Eric Rochester + **/ + public function getTitleField() + { + $result = null; + $named = null; + $flagged = null; + + foreach ($this->fields as $field) { + if ($field->name === 'title') { + $named = $field; + } + if ($field->is_title) { + $flagged = $field; + } + } + + if (!is_null($flagged)) { + $result = $flagged; + } else if (!is_null($named)) { + $result = $named; + } + + return $result; + } + + +} diff --git a/lib/SolrSearch/Addon/Config.php b/lib/SolrSearch/Addon/Config.php new file mode 100755 index 0000000..73f90c6 --- /dev/null +++ b/lib/SolrSearch/Addon/Config.php @@ -0,0 +1,210 @@ +db = $db; + } + + + /** + * This parses a string into an associative array of SolrSearch_Addon_Addon + * classes. + * + * @param string $configJson The input JSON configuration to parse. + * + * @return array of SolrSearch_Addon_Addon + * @author Eric Rochester + **/ + public function parseString($input) + { + $addons = array(); + $json = json_decode($input, TRUE); + + if ($json !== null) { + $addons = $this->parseAddonCollection($json); + } + + return $addons; + } + + + /** + * This parses the JSON data in a file. + * + * @param string $filename The file name to parse. + * + * @return array of SolrSearch_Addon_Addon + * @author Eric Rochester + **/ + public function parseFile($filename) + { + return $this->parseString(file_get_contents($filename)); + } + + + /** + * This parses a directory of JSON files. + * + * @param string $dirname The directory containing JSON files to parse. + * + * @return array of SolrSearch_Addon_Addon + * @author Eric Rochester + **/ + public function parseDir($dirname) + { + $addons = array(); + + if ($d = opendir($dirname)) { + while (($file = readdir($d)) !== false) { + if (preg_match('/\.json$/i', $file)) { + $a = $this->parseFile("$dirname/$file"); + $addons = array_merge($addons, $a); + } + } + closedir($d); + } + + return $addons; + } + + + /** + * This takes a JSON object describing an addon and parses it into an + * Addon. + * + * @param string $json The JSON object to parse. + * @param SolrSearch_Addon_Addon|null $parent The parent for the objects in + * this collection. + * + * @return array of SolrSearch_Addon_Addon + * @author Eric Rochester + **/ + private function parseAddonCollection($json, $parent=null) + { + $addons = array(); + + foreach ($json as $name => $jAddon) { + $addon = $this->parseAddon( + $name, $jAddon, $parent + ); + + if ($addon !== null) { + $addons = array_merge($addons, $addon); + } + } + + return $addons; + } + + + /** + * This parses the data for a single addon. + * + * @param string $name The name of the addon. + * @param array $json An associate array representing the JSON object for + * the addon. + * @param SolrSearch_Addon_Addon|null $parent The parent for the object. + * + * @return array of SolrSearch_Addon_Addon + * @author Eric Rochester + **/ + private function parseAddon($name, $json, $parent=null) + { + $addon = new SolrSearch_Addon_Addon($name); + $addons = array( $name => $addon ); + + if (array_key_exists('result_type', $json)) { + $addon->resultType = $json['result_type']; + } + if (array_key_exists('table', $json)) { + $addon->table = $json['table']; + } + if (array_key_exists('id_column', $json)) { + $addon->idColumn = $json['id_column']; + } + $addon->parentAddon = $parent; + if (array_key_exists('parent_key', $json)) { + $addon->parentKey = $json['parent_key']; + } + if (array_key_exists('tagged', $json)) { + $addon->tagged = $json['tagged']; + } + if (array_key_exists('flag', $json)) { + $addon->flag = $json['flag']; + } + + if (array_key_exists('fields', $json)) { + $addon->fields = array(); + foreach ($json['fields'] as $field) { + $addon->fields[] = $this->parseField($field); + } + } + + if (array_key_exists('children', $json)) { + $children = $this->parseAddonCollection( + $json['children'], $addon + ); + foreach ($children as $cname => $child) { + $addon->children[] = $child; + $addons[$cname] = $child; + } + } + + return $addons; + } + + + /** + * This parses a JSON object or string into a SolrSearch_Addon_Field. + * + * @param array|string $json The JSON object to parse into a field. + * + * @return SolrSearch_Addon_Field $field That parsed field. + * @author Eric Rochester + **/ + private function parseField($json) + { + $field = new SolrSearch_Addon_Field(); + + if (is_array($json)) { + $field->name = $json['field']; + $field->label = $json['label']; + $field->is_facet = array_key_exists('facet', $json) ? $json['facet'] : false; + $field->is_title = array_key_exists('is_title', $json) ? $json['is_title'] : false; + $field->remote = array_key_exists('remote', $json) ? (object) $json['remote'] : null; + } else { + $field->name = $json; + $field->label = $json; + $field->is_facet = false; + $field->is_title = false; + $field->remote = null; + } + + return $field; + } + + +} diff --git a/lib/SolrSearch/Addon/Field.php b/lib/SolrSearch/Addon/Field.php new file mode 100755 index 0000000..d5dea5c --- /dev/null +++ b/lib/SolrSearch/Addon/Field.php @@ -0,0 +1,66 @@ +name = $name; + $this->label = $label; + $this->is_facet = $is_facet; + $this->is_title = $is_title; + $this->remote = $remote; + } + + +} diff --git a/lib/SolrSearch/Addon/Indexer.php b/lib/SolrSearch/Addon/Indexer.php new file mode 100755 index 0000000..67ad801 --- /dev/null +++ b/lib/SolrSearch/Addon/Indexer.php @@ -0,0 +1,313 @@ +db = $db; + } + + + /** + * This creates a Solr-style name for an addon and field. + * + * @param SolrSearch_Addon_Addon $addon This is the addon. + * @param string $field The field to get. + * + * @return string $name The Solr name. + * @author Eric Rochester + **/ + public function makeSolrName($addon, $field) + { + return "{$addon->name}_{$field}_t"; + } + + + /** + * This gets all the records in the database matching all the addons passed + * in and returns a list of Solr documents for indexing. + * + * @param associative array of SolrSearch_Addon_Addon $addons The addon + * configuration information about the records to index. + * + * @return array of Apache_Solr_Document $docs The documents to index. + * @author Eric Rochester + **/ + public function indexAll($addons) + { + $docs = array(); + + foreach ($addons as $name => $addon) { + $docs = array_merge($docs, $this->indexAllAddon($addon)); + } + + return $docs; + } + + + /** + * This gets all the records associated with a single addon for indexing. + * + * @param SolrSearch_Addon_Addon The addon to pull records for. + * + * @return array of Apache_Solr_Documents $docs The documents to index. + * @author Eric Rochester + **/ + public function indexAllAddon($addon) + { + $docs = array(); + $table = $this->db->getTable($addon->table); + + if ($this->_tableExists($table->getTableName())) { + $select = $this->buildSelect($table, $addon); + $rows = $table->fetchObjects($select); + + foreach ($rows as $record) { + $doc = $this->indexRecord($record, $addon); + $docs[] = $doc; + } + } + + return $docs; + } + + + /** + * This tests whether the table exists. + * + * @param Omeka_Table $table The name of the table to check for. + * + * @return bool + * @author Eric Rochester + **/ + protected function _tableExists($table) + { + $exists = false; + + try { + $info = $this->db->describeTable($table); + $exists = !empty($info); + } catch (Zend_Db_Exception $e) { + } + + return $exists; + } + + + /** + * This returns an Apache_Solr_Document to index, if the addons say it + * should be. + * + * @param Omeka_Record $record The record to index. + * @param associative array of SolrSearch_Addon_Addon $addons The + * configuration controlling how records are indexed. + * + * @return Apache_Solr_Document|null + * @author Eric Rochester + **/ + public function indexRecord($record, $addon) + { + $doc = new Apache_Solr_Document(); + + $doc->id = "{$addon->table}_{$record->id}"; + $doc->addField('model', $addon->table); + $doc->addField('modelid', $record->id); + + // extend $doc to include public / private records + // not sure if required + //$doc->addField('public', $record->public); + + $titleField = $addon->getTitleField(); + foreach ($addon->fields as $field) { + $solrName = $this->makeSolrName($addon, $field->name); + + if (is_null($field->remote)) { + $value = $this->getLocalValue($record, $field); + } else { + $value = $this->getRemoteValue($record, $field); + } + + foreach ($value as $v) { + $doc->addField($solrName, $v); + + if (!is_null($titleField) && $titleField->name === $field->name) { + $doc->addField('title', $v); + } + } + } + + if ($addon->tagged) { + foreach ($record->getTags() as $tag) { + $doc->addField('tag', $tag->name); + } + } + + if ($addon->resultType) { + $doc->addField('resulttype', $addon->resultType); + } + + return $doc; + } + + + /** + * This returns a value that is local to the record. + * + * @param Omeka_Record $record The record to get the value from. + * @param SolrSearch_Addon_Field $field The field that defines where to get + * the value. + * + * @return mixed $value The value of the field in the record. + * @author Eric Rochester + **/ + protected function getLocalValue($record, $field) + { + $value = array(); + $value[] = $record[$field->name]; + return $value; + } + + + /** + * This returns a value that is remotely attached to the record. + * + * @param Omeka_Record $record The record to get the value from. + * @param SolrSearch_Addon_Field $field The field that defines where to get + * the value. + * + * @return mixed $value The value of the field in the record. + * @author Eric Rochester + **/ + protected function getRemoteValue($record, $field) + { + $value = array(); + + $table = $this->db->getTable($field->remote->table); + + $select = $table->getSelect(); + $select->where( + "{$table->getTableAlias()}.{$field->remote->key}={$record->id}" + ); + + $rows = $table->fetchObjects($select); + foreach ($rows as $item) { + $value[] = $item[$field->name]; + } + + return $value; + } + + + /** + * This builds a query for returning all the records to index from the + * database. + * + * @param Omeka_Db_Table $table The table to create the SQL for. + * @param SolrSearch_Addon_Addon $addon The addon to generate SQL for. + * + * @return Omeka_Db_Select $select The select statement to execute for the + * query. + * @author Eric Rochester + **/ + public function buildSelect($table, $addon) + { + $select = $table + ->select() + ->from($table->getTableName()); + + if ($addon->hasFlag()) { + $this->_addFlag($select, $addon); + } + + return $select; + } + + + /** + * This adds the joins and where clauses to respect an addon's privacy + * settings. + * + * @param Omeka_Db_Select $select The select object to modify. + * @param SolrSearch_Addon_Addon $addon The current addon. You should + * already know that this addon does have a public flag somewhere in its + * hierarchy before calling this. + * + * @return null + * @author Eric Rochester + **/ + private function _addFlag($select, $addon) + { + if (!is_null($addon->flag)) { + $table = $this->db->getTable($addon->table); + $select->where( + "`{$table->getTableName()}`.`{$addon->flag}`=1" + ); + } else if (!is_null($addon->parentAddon)) { + $parent = $addon->parentAddon; + $table = $this->db->getTable($addon->table)->getTableName(); + $ptable = $this->db->getTable($parent->table)->getTableName(); + + $select->join( + $ptable, + "`$table`.`{$addon->parentKey}`=`$ptable`.`{$parent->idColumn}`", + array() + ); + + $this->_addFlag($select, $parent); + } + } + + + /** + * This returns true if this addon (and none of its ancestors) are flagged. + * + * @param Omeka_Record $record The Omeka record to consider indexing. + * @param SolrSearch_Addon_Addon $addon The addon for the record. + * + * @return bool $indexed A flag indicating whether to index the record. + * @author Eric Rochester + **/ + public function isRecordIndexed($record, $addon) + { + $indexed = true; + + if (is_null($record)) { + + } else if (!is_null($addon->flag)) { + $flag = $addon->flag; + $indexed = $record->$flag; + + } else if (!is_null($addon->parentAddon)) { + $key = $addon->parentKey; + $table = $this->db->getTable($addon->parentAddon->table); + $parent = $table->find($record->$key); + + $indexed = $this->isRecordIndexed($parent, $addon->parentAddon); + } + + return $indexed; + } + + +} diff --git a/lib/SolrSearch/Addon/Manager.php b/lib/SolrSearch/Addon/Manager.php new file mode 100755 index 0000000..8cc3bd5 --- /dev/null +++ b/lib/SolrSearch/Addon/Manager.php @@ -0,0 +1,259 @@ +db = $db; + $this->addonDir = $addonDir; + $this->addons = null; + + if ($this->addonDir === null) { + $this->addonDir = SOLR_DIR . '/addons'; + } + } + + + /** + * This parses all the JSON configuration files in the addon directory and + * returns the addons. + * + * @param SolrSearch_Addon_Config $config The configuration parser. If + * null, this is created. + * + * @return array of SolrSearch_Addon_Addon + * @author Eric Rochester + **/ + public function parseAll($config=null) + { + if (is_null($config)) { + $config = new SolrSearch_Addon_Config($this->db); + } + if (is_null($this->addons)) { + $this->addons = array(); + } + + $this->addons = array_merge( + $this->addons, $config->parseDir($this->addonDir) + ); + + return $this->addons; + } + + + /** + * A helper method to the return the addon for the record. + * + * @param Omeka_Record $record The record to find an addon for. + * + * @return SolrSearch_Addon_Addon|null $addon The corresponding addon. + * @author Eric Rochester + **/ + public function findAddonForRecord($record) + { + $hit = null; + + $recordTable = get_class($record); + foreach ($this->addons as $key => $addon) { + if ($recordTable == $addon->table) { + $hit = $addon; + break; + } + } + + return $hit; + } + + + /** + * For a given record, re-save all child addon records, if any exist. + * + * @param Omeka_Record $record The record. + * + * @author David McClure + **/ + public function resaveChildren($record) + { + + // Get the record's addon. + $addon = $this->findAddonForRecord($record); + if (is_null($addon)) return; + + foreach ($addon->children as $childAddon) { + + // Load each of the child records. + $children = $this->db->getTable($childAddon->table)->findBySql( + "{$childAddon->parentKey}=?", array($record->id) + ); + + // Resave each of the children. + foreach ($children as $child) $child->save(); + + } + + } + + + /** + * For a given record, re-save the remote parent record, if one exists. + * + * @param Omeka_Record $record The record. + * + * @author David McClure + **/ + public function resaveRemoteParent($record) + { + + // Get the record type. + $table = get_class($record); + + // Iterate over all addon fields. + foreach ($this->addons as $addon) { + foreach ($addon->fields as $field) { + + // Match remote fields that point to the record's type. + if ($field->remote && $field->remote->table == $table) { + + $parentTable = $this->db->getTable($addon->table); + $parentIdKey = $field->remote->key; + + // Load the parent and re-save. + $parent = $parentTable->find($record->$parentIdKey); + $parent->save(); + + } + + } + } + + } + + + /** + * This reindexes all the addons and returns the Solr documents created. + * + * @param SolrSearch_Addon_Config $config The configuration parser. If + * null, this is created. If given, this forces the Addons to be re-parsed; + * otherwise, they're only re-parsed if they haven't been yet. + * + * @return array of Apache_Solr_Document $docs The documents generated by + * indexing the Addon records. + * @author Eric Rochester + **/ + public function reindexAddons($config=null) + { + $docs = array(); + $idxr = new SolrSearch_Addon_Indexer($this->db); + + if (is_null($this->addons) || !is_null($config)) { + $this->parseAll($config); + } + + $docs = $idxr->indexAll($this->addons); + + return $docs; + } + + + /** + * This indexes a single record. + * + * @param Omeka_Record $record The record to index. + * @param SolrSearch_Addon_Config $config The configuration parser. If + * null, this is created. If given, this forces the Addons to be re-parsed; + * otherwise, they're only re-parsed if they haven't been yet. + * + * @return Apache_Solr_Document|null $doc The indexed document or null, if + * the record's not to be indexed. + * @author Eric Rochester + **/ + public function indexRecord($record, $config=null) + { + $doc = null; + $idxr = new SolrSearch_Addon_Indexer($this->db); + + if (is_null($this->addons) || !is_null($config)) { + $this->parseAll($config); + } + + $addon = $this->findAddonForRecord($record); + if (!is_null($addon) && $idxr->isRecordIndexed($record, $addon)) { + $doc = $idxr->indexRecord($record, $addon); + } + + return $doc; + } + + + /** + * This returns the Solr ID for the record. + * + * @param Omeka_Record $record The record to index. + * @param SolrSearch_Addon_Config $config The configuration parser. If + * null, this is created. If given, this forces the Addons to be re-parsed; + * otherwise, they're only re-parsed if they haven't been yet. + * + * @return string|null + * @author Eric Rochester + **/ + public function getId($record, $config=null) + { + $id = null; + $idxr = new SolrSearch_Addon_Indexer($this->db); + + if (is_null($this->addons) || !is_null($config)) { + $this->parseAll($config); + } + + $addon = $this->findAddonForRecord($record); + if (!is_null($addon)) { + $id = "{$addon->table}_{$record->id}"; + } + + return $id; + } + + +} diff --git a/lib/SolrSearch/DbPager.php b/lib/SolrSearch/DbPager.php new file mode 100755 index 0000000..515425a --- /dev/null +++ b/lib/SolrSearch/DbPager.php @@ -0,0 +1,94 @@ +db = $db; + $this->table = $table; + $this->select = $select; + $this->pageNumber = 0; + $this->rowCount = $rowCount; + $this->params = $params; + } + + + /** + * This returns the next chunk of database result objects. + * + * @return array|null Set of Omeka_Record instances, or null if none can be + * found. + * @author Eric Rochester + **/ + public function next() + { + $this->pageNumber++; + $this->select->limitPage($this->pageNumber, $this->rowCount); + + $rows = $this->table->fetchObjects($this->select, $this->params); + return $rows; + } + +} diff --git a/lib/SolrSearch/Utils.php b/lib/SolrSearch/Utils.php new file mode 100755 index 0000000..d298639 --- /dev/null +++ b/lib/SolrSearch/Utils.php @@ -0,0 +1,49 @@ + + **/ + public static function ensureView() + { + if (! Zend_Registry::isRegistered('view')) { + $view = new Omeka_View(); + Zend_Registry::set('view', $view); + } + return Zend_Registry::get('view'); + } + + /** + * This creates an `li` element for the navigation list. Primarily, it adds + * the `current` class for the link to the current page. + * + * @return void + * @author Eric Rochester + **/ + public static function nav_li($tab, $key, $url, $label) + { + echo "$label