From 2c6910cb77a2eed93333b8210c6a3a85d020a819 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 31 Jul 2024 17:42:31 +0800 Subject: [PATCH 01/16] MDL-81031 core: Use ::class for PSR namespacing --- lib/classes/component.php | 62 +++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/lib/classes/component.php b/lib/classes/component.php index c9204931a63..50877e63a39 100644 --- a/lib/classes/component.php +++ b/lib/classes/component.php @@ -102,40 +102,40 @@ class component { ]; /** @var array> associative array of PRS-4 namespaces and corresponding paths. */ protected static $psr4namespaces = [ - 'MaxMind' => 'lib/maxmind/MaxMind', - 'GeoIp2' => 'lib/maxmind/GeoIp2', - 'Sabberworm\\CSS' => 'lib/php-css-parser', - 'MoodleHQ\\RTLCSS' => 'lib/rtlcss', - 'ScssPhp\\ScssPhp' => 'lib/scssphp', - 'OpenSpout' => 'lib/openspout/src', - 'MatthiasMullie\\Minify' => 'lib/minify/matthiasmullie-minify/src/', - 'MatthiasMullie\\PathConverter' => 'lib/minify/matthiasmullie-pathconverter/src/', - 'IMSGlobal\LTI' => 'lib/ltiprovider/src', - 'Packback\\Lti1p3' => 'lib/lti1p3/src', - 'Phpml' => 'lib/mlbackend/php/phpml/src/Phpml', - 'PHPMailer\\PHPMailer' => 'lib/phpmailer/src', - 'RedeyeVentures\\GeoPattern' => 'lib/geopattern-php/GeoPattern', - 'Firebase\\JWT' => 'lib/php-jwt/src', - 'ZipStream' => 'lib/zipstream/src/', - 'MyCLabs\\Enum' => 'lib/php-enum/src', - 'PhpXmlRpc' => 'lib/phpxmlrpc', - 'Psr\\Http\\Client' => 'lib/psr/http-client/src', - 'Psr\\Http\\Message' => [ + \MaxMind::class => 'lib/maxmind/MaxMind', + \GeoIp2::class => 'lib/maxmind/GeoIp2', + \Sabberworm\CSS::class => 'lib/php-css-parser', + \MoodleHQ\RTLCSS::class => 'lib/rtlcss', + \ScssPhp\ScssPhp::class => 'lib/scssphp', + \OpenSpout::class => 'lib/openspout/src', + \MatthiasMullie\Minify::class => 'lib/minify/matthiasmullie-minify/src/', + \MatthiasMullie\PathConverter::class => 'lib/minify/matthiasmullie-pathconverter/src/', + \IMSGlobal\LTI::class => 'lib/ltiprovider/src', + \Packback\Lti1p3::class => 'lib/lti1p3/src', + \Phpml::class => 'lib/mlbackend/php/phpml/src/Phpml', + \PHPMailer\PHPMailer::class => 'lib/phpmailer/src', + \RedeyeVentures\GeoPattern::class => 'lib/geopattern-php/GeoPattern', + \Firebase\JWT::class => 'lib/php-jwt/src', + \ZipStream::class => 'lib/zipstream/src/', + \MyCLabs\Enum::class => 'lib/php-enum/src', + \PhpXmlRpc::class => 'lib/phpxmlrpc', + \Psr\Http\Client::class => 'lib/psr/http-client/src', + \Psr\Http\Message::class => [ 'lib/psr/http-message/src', 'lib/psr/http-factory/src', ], - 'Psr\\EventDispatcher' => 'lib/psr/event-dispatcher/src', - 'Psr\\Clock' => 'lib/psr/clock/src', - 'Psr\\Container' => 'lib/psr/container/src', - 'GuzzleHttp\\Psr7' => 'lib/guzzlehttp/psr7/src', - 'GuzzleHttp\\Promise' => 'lib/guzzlehttp/promises/src', - 'GuzzleHttp' => 'lib/guzzlehttp/guzzle/src', - 'Kevinrob\\GuzzleCache' => 'lib/guzzlehttp/kevinrob/guzzlecache/src', - 'Aws' => 'lib/aws-sdk/src', - 'JmesPath' => 'lib/jmespath/src', - 'Laravel\\SerializableClosure' => 'lib/laravel/serializable-closure/src', - 'DI' => 'lib/php-di/php-di/src', - 'Invoker' => 'lib/php-di/invoker/src', + \Psr\EventDispatcher::class => 'lib/psr/event-dispatcher/src', + \Psr\Clock::class => 'lib/psr/clock/src', + \Psr\Container::class => 'lib/psr/container/src', + \GuzzleHttp\Psr7::class => 'lib/guzzlehttp/psr7/src', + \GuzzleHttp\Promise::class => 'lib/guzzlehttp/promises/src', + \GuzzleHttp::class => 'lib/guzzlehttp/guzzle/src', + \Kevinrob\GuzzleCache::class => 'lib/guzzlehttp/kevinrob/guzzlecache/src', + \Aws::class => 'lib/aws-sdk/src', + \JmesPath::class => 'lib/jmespath/src', + \Laravel\SerializableClosure::class => 'lib/laravel/serializable-closure/src', + \DI::class => 'lib/php-di/php-di/src', + \Invoker::class => 'lib/php-di/invoker/src', ]; /** From 9ca271e42d73a24c7d9b046ad958ebf785fac07c Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 31 Oct 2023 09:23:19 +0800 Subject: [PATCH 02/16] MDL-81031 core: Add PSR interfaces for PSRs 3, 7, 11, and 15 --- lib/classes/component.php | 5 + lib/psr/http-server-handler/LICENSE | 21 +++ lib/psr/http-server-handler/README.md | 12 ++ lib/psr/http-server-handler/composer.json | 36 +++++ .../src/RequestHandlerInterface.php | 22 +++ lib/psr/http-server-middleware/LICENSE | 21 +++ lib/psr/http-server-middleware/README.md | 12 ++ lib/psr/http-server-middleware/composer.json | 36 +++++ .../src/MiddlewareInterface.php | 25 +++ lib/psr/log/LICENSE | 19 +++ lib/psr/log/README.md | 58 +++++++ lib/psr/log/composer.json | 26 ++++ lib/psr/log/src/AbstractLogger.php | 15 ++ lib/psr/log/src/InvalidArgumentException.php | 7 + lib/psr/log/src/LogLevel.php | 18 +++ lib/psr/log/src/LoggerAwareInterface.php | 18 +++ lib/psr/log/src/LoggerAwareTrait.php | 26 ++++ lib/psr/log/src/LoggerInterface.php | 125 +++++++++++++++ lib/psr/log/src/LoggerTrait.php | 142 ++++++++++++++++++ lib/psr/log/src/NullLogger.php | 30 ++++ lib/thirdpartylibs.xml | 24 +++ 21 files changed, 698 insertions(+) create mode 100644 lib/psr/http-server-handler/LICENSE create mode 100644 lib/psr/http-server-handler/README.md create mode 100644 lib/psr/http-server-handler/composer.json create mode 100644 lib/psr/http-server-handler/src/RequestHandlerInterface.php create mode 100644 lib/psr/http-server-middleware/LICENSE create mode 100644 lib/psr/http-server-middleware/README.md create mode 100644 lib/psr/http-server-middleware/composer.json create mode 100644 lib/psr/http-server-middleware/src/MiddlewareInterface.php create mode 100644 lib/psr/log/LICENSE create mode 100644 lib/psr/log/README.md create mode 100644 lib/psr/log/composer.json create mode 100644 lib/psr/log/src/AbstractLogger.php create mode 100644 lib/psr/log/src/InvalidArgumentException.php create mode 100644 lib/psr/log/src/LogLevel.php create mode 100644 lib/psr/log/src/LoggerAwareInterface.php create mode 100644 lib/psr/log/src/LoggerAwareTrait.php create mode 100644 lib/psr/log/src/LoggerInterface.php create mode 100644 lib/psr/log/src/LoggerTrait.php create mode 100644 lib/psr/log/src/NullLogger.php diff --git a/lib/classes/component.php b/lib/classes/component.php index 50877e63a39..9db0d8f6678 100644 --- a/lib/classes/component.php +++ b/lib/classes/component.php @@ -124,9 +124,14 @@ class component { 'lib/psr/http-message/src', 'lib/psr/http-factory/src', ], + \Psr\Http\Server::class => [ + "lib/psr/http-server-handler/src", + "lib/psr/http-server-middleware/src", + ], \Psr\EventDispatcher::class => 'lib/psr/event-dispatcher/src', \Psr\Clock::class => 'lib/psr/clock/src', \Psr\Container::class => 'lib/psr/container/src', + \Psr\Log::class => "lib/psr/log/src", \GuzzleHttp\Psr7::class => 'lib/guzzlehttp/psr7/src', \GuzzleHttp\Promise::class => 'lib/guzzlehttp/promises/src', \GuzzleHttp::class => 'lib/guzzlehttp/guzzle/src', diff --git a/lib/psr/http-server-handler/LICENSE b/lib/psr/http-server-handler/LICENSE new file mode 100644 index 00000000000..b71ec5dfc20 --- /dev/null +++ b/lib/psr/http-server-handler/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 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/lib/psr/http-server-handler/README.md b/lib/psr/http-server-handler/README.md new file mode 100644 index 00000000000..111a9ed2e8b --- /dev/null +++ b/lib/psr/http-server-handler/README.md @@ -0,0 +1,12 @@ +HTTP Server Request Handlers for Middleware +=========================================== + +This repository holds the `RequestHandlerInterface` related to [PSR-15 (HTTP Server Request Handlers)][psr-url]. + +Note that this is not a Server Request Handler implementation of its own. It is merely the interface that describe a Server Request Handler. + +The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. + +[psr-url]: https://www.php-fig.org/psr/psr-15/ +[package-url]: https://packagist.org/packages/psr/http-server-handler +[implementation-url]: https://packagist.org/providers/psr/http-server-handler-implementation diff --git a/lib/psr/http-server-handler/composer.json b/lib/psr/http-server-handler/composer.json new file mode 100644 index 00000000000..c54b2dee822 --- /dev/null +++ b/lib/psr/http-server-handler/composer.json @@ -0,0 +1,36 @@ +{ + "name": "psr/http-server-handler", + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "psr", + "psr-7", + "psr-15", + "http-interop", + "http", + "server", + "handler", + "request", + "response" + ], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/lib/psr/http-server-handler/src/RequestHandlerInterface.php b/lib/psr/http-server-handler/src/RequestHandlerInterface.php new file mode 100644 index 00000000000..83911e265b8 --- /dev/null +++ b/lib/psr/http-server-handler/src/RequestHandlerInterface.php @@ -0,0 +1,22 @@ +=7.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0" + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/lib/psr/http-server-middleware/src/MiddlewareInterface.php b/lib/psr/http-server-middleware/src/MiddlewareInterface.php new file mode 100644 index 00000000000..a6c14f8c729 --- /dev/null +++ b/lib/psr/http-server-middleware/src/MiddlewareInterface.php @@ -0,0 +1,25 @@ +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + try { + $this->doSomethingElse(); + } catch (Exception $exception) { + $this->logger->error('Oh no!', array('exception' => $exception)); + } + + // 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/lib/psr/log/composer.json b/lib/psr/log/composer.json new file mode 100644 index 00000000000..879fc6f53bb --- /dev/null +++ b/lib/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": "https://www.php-fig.org/" + } + ], + "require": { + "php": ">=8.0.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + } +} diff --git a/lib/psr/log/src/AbstractLogger.php b/lib/psr/log/src/AbstractLogger.php new file mode 100644 index 00000000000..d60a091affa --- /dev/null +++ b/lib/psr/log/src/AbstractLogger.php @@ -0,0 +1,15 @@ +logger = $logger; + } +} diff --git a/lib/psr/log/src/LoggerInterface.php b/lib/psr/log/src/LoggerInterface.php new file mode 100644 index 00000000000..b3a24b5f7e9 --- /dev/null +++ b/lib/psr/log/src/LoggerInterface.php @@ -0,0 +1,125 @@ +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|\Stringable $message + * @param array $context + * + * @return void + */ + public function alert(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function critical(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function error(string|\Stringable $message, array $context = []): void + { + $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|\Stringable $message + * @param array $context + * + * @return void + */ + public function warning(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function notice(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function info(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string|\Stringable $message + * @param array $context + * + * @return void + */ + public function debug(string|\Stringable $message, array $context = []): void + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string|\Stringable $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, string|\Stringable $message, array $context = []): void; +} diff --git a/lib/psr/log/src/NullLogger.php b/lib/psr/log/src/NullLogger.php new file mode 100644 index 00000000000..c1cc3c0692a --- /dev/null +++ b/lib/psr/log/src/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string|\Stringable $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, string|\Stringable $message, array $context = []): void + { + // noop + } +} diff --git a/lib/thirdpartylibs.xml b/lib/thirdpartylibs.xml index 51f25ad575d..c64830bdc6f 100644 --- a/lib/thirdpartylibs.xml +++ b/lib/thirdpartylibs.xml @@ -644,6 +644,22 @@ All rights reserved. MIT https://github.com/php-fig/http-message + + psr/http-server-handler + psr/http-server-handler + Common interface for HTTP server-side request handler + 1.0.2 + MIT + https://github.com/php-fig/http-server-handler + + + psr/http-server-middleware + psr/http-server-middleware + Common interface for HTTP server-side middleware + 1.0.2 + MIT + https://github.com/php-fig/http-server-middleware + psr/event-dispatcher event-dispatcher @@ -652,6 +668,14 @@ All rights reserved. MIT https://github.com/php-fig/event-dispatcher + + psr/log + log + Common interface for logging libraries + 3.0.0 + MIT + https://github.com/php-fig/log + phpxmlrpc phpxmlrpc From cb75bffafabf20ad1b7db6679dddd7c48b93745a Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 31 Jul 2024 15:13:58 +0800 Subject: [PATCH 03/16] MDL-81031 core: De-duplicate HTTP-* PSRs PHPSpreadsheet was incorrectly importing these. --- .../vendor/psr/http-client/CHANGELOG.md | 23 -- .../vendor/psr/http-client/LICENSE | 19 - .../vendor/psr/http-client/README.md | 12 - .../vendor/psr/http-client/composer.json | 27 -- .../src/ClientExceptionInterface.php | 10 - .../psr/http-client/src/ClientInterface.php | 20 -- .../src/NetworkExceptionInterface.php | 24 -- .../src/RequestExceptionInterface.php | 24 -- .../vendor/psr/http-factory/LICENSE | 21 -- .../vendor/psr/http-factory/README.md | 12 - .../vendor/psr/http-factory/composer.json | 35 -- .../src/RequestFactoryInterface.php | 18 - .../src/ResponseFactoryInterface.php | 18 - .../src/ServerRequestFactoryInterface.php | 24 -- .../src/StreamFactoryInterface.php | 45 --- .../src/UploadedFileFactoryInterface.php | 34 -- .../http-factory/src/UriFactoryInterface.php | 17 - .../vendor/psr/http-message/CHANGELOG.md | 36 -- .../vendor/psr/http-message/LICENSE | 19 - .../vendor/psr/http-message/README.md | 16 - .../vendor/psr/http-message/composer.json | 26 -- .../psr/http-message/docs/PSR7-Interfaces.md | 130 ------- .../psr/http-message/docs/PSR7-Usage.md | 159 --------- .../psr/http-message/src/MessageInterface.php | 187 ---------- .../psr/http-message/src/RequestInterface.php | 130 ------- .../http-message/src/ResponseInterface.php | 68 ---- .../src/ServerRequestInterface.php | 261 -------------- .../psr/http-message/src/StreamInterface.php | 158 --------- .../src/UploadedFileInterface.php | 123 ------- .../psr/http-message/src/UriInterface.php | 324 ------------------ 30 files changed, 2020 deletions(-) delete mode 100644 lib/phpspreadsheet/vendor/psr/http-client/CHANGELOG.md delete mode 100644 lib/phpspreadsheet/vendor/psr/http-client/LICENSE delete mode 100644 lib/phpspreadsheet/vendor/psr/http-client/README.md delete mode 100644 lib/phpspreadsheet/vendor/psr/http-client/composer.json delete mode 100644 lib/phpspreadsheet/vendor/psr/http-client/src/ClientExceptionInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-client/src/ClientInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-client/src/NetworkExceptionInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-client/src/RequestExceptionInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-factory/LICENSE delete mode 100644 lib/phpspreadsheet/vendor/psr/http-factory/README.md delete mode 100644 lib/phpspreadsheet/vendor/psr/http-factory/composer.json delete mode 100644 lib/phpspreadsheet/vendor/psr/http-factory/src/RequestFactoryInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-factory/src/ResponseFactoryInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-factory/src/ServerRequestFactoryInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-factory/src/StreamFactoryInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-factory/src/UploadedFileFactoryInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-factory/src/UriFactoryInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-message/CHANGELOG.md delete mode 100644 lib/phpspreadsheet/vendor/psr/http-message/LICENSE delete mode 100644 lib/phpspreadsheet/vendor/psr/http-message/README.md delete mode 100644 lib/phpspreadsheet/vendor/psr/http-message/composer.json delete mode 100644 lib/phpspreadsheet/vendor/psr/http-message/docs/PSR7-Interfaces.md delete mode 100644 lib/phpspreadsheet/vendor/psr/http-message/docs/PSR7-Usage.md delete mode 100644 lib/phpspreadsheet/vendor/psr/http-message/src/MessageInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-message/src/RequestInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-message/src/ResponseInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-message/src/ServerRequestInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-message/src/StreamInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-message/src/UploadedFileInterface.php delete mode 100644 lib/phpspreadsheet/vendor/psr/http-message/src/UriInterface.php diff --git a/lib/phpspreadsheet/vendor/psr/http-client/CHANGELOG.md b/lib/phpspreadsheet/vendor/psr/http-client/CHANGELOG.md deleted file mode 100644 index e2dc25f519b..00000000000 --- a/lib/phpspreadsheet/vendor/psr/http-client/CHANGELOG.md +++ /dev/null @@ -1,23 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file, in reverse chronological order by release. - -## 1.0.1 - -Allow installation with PHP 8. No code changes. - -## 1.0.0 - -First stable release. No changes since 0.3.0. - -## 0.3.0 - -Added Interface suffix on exceptions - -## 0.2.0 - -All exceptions are in `Psr\Http\Client` namespace - -## 0.1.0 - -First release diff --git a/lib/phpspreadsheet/vendor/psr/http-client/LICENSE b/lib/phpspreadsheet/vendor/psr/http-client/LICENSE deleted file mode 100644 index cd5e0020afc..00000000000 --- a/lib/phpspreadsheet/vendor/psr/http-client/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2017 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/lib/phpspreadsheet/vendor/psr/http-client/README.md b/lib/phpspreadsheet/vendor/psr/http-client/README.md deleted file mode 100644 index 84af5c55d5f..00000000000 --- a/lib/phpspreadsheet/vendor/psr/http-client/README.md +++ /dev/null @@ -1,12 +0,0 @@ -HTTP Client -=========== - -This repository holds all the common code related to [PSR-18 (HTTP Client)][psr-url]. - -Note that this is not a HTTP Client implementation of its own. It is merely abstractions that describe the components of a HTTP Client. - -The installable [package][package-url] and [implementations][implementation-url] are listed on Packagist. - -[psr-url]: https://www.php-fig.org/psr/psr-18 -[package-url]: https://packagist.org/packages/psr/http-client -[implementation-url]: https://packagist.org/providers/psr/http-client-implementation diff --git a/lib/phpspreadsheet/vendor/psr/http-client/composer.json b/lib/phpspreadsheet/vendor/psr/http-client/composer.json deleted file mode 100644 index e4cab2f3ea4..00000000000 --- a/lib/phpspreadsheet/vendor/psr/http-client/composer.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "psr/http-client", - "description": "Common interface for HTTP clients", - "keywords": ["psr", "psr-18", "http", "http-client"], - "homepage": "https://github.com/php-fig/http-client", - "license": "MIT", - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Client\\": "src/" - } - }, - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - } -} diff --git a/lib/phpspreadsheet/vendor/psr/http-client/src/ClientExceptionInterface.php b/lib/phpspreadsheet/vendor/psr/http-client/src/ClientExceptionInterface.php deleted file mode 100644 index aa0b9cf14ba..00000000000 --- a/lib/phpspreadsheet/vendor/psr/http-client/src/ClientExceptionInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -=7.0.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - } -} diff --git a/lib/phpspreadsheet/vendor/psr/http-factory/src/RequestFactoryInterface.php b/lib/phpspreadsheet/vendor/psr/http-factory/src/RequestFactoryInterface.php deleted file mode 100644 index cb39a08bf5c..00000000000 --- a/lib/phpspreadsheet/vendor/psr/http-factory/src/RequestFactoryInterface.php +++ /dev/null @@ -1,18 +0,0 @@ - `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface` because the `Request` and the `Response` are `HTTP Messages`. -> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered. - diff --git a/lib/phpspreadsheet/vendor/psr/http-message/docs/PSR7-Usage.md b/lib/phpspreadsheet/vendor/psr/http-message/docs/PSR7-Usage.md deleted file mode 100644 index b6d048a341e..00000000000 --- a/lib/phpspreadsheet/vendor/psr/http-message/docs/PSR7-Usage.md +++ /dev/null @@ -1,159 +0,0 @@ -### PSR-7 Usage - -All PSR-7 applications comply with these interfaces -They were created to establish a standard between middleware implementations. - -> `RequestInterface`, `ServerRequestInterface`, `ResponseInterface` extend `MessageInterface` because the `Request` and the `Response` are `HTTP Messages`. -> When using `ServerRequestInterface`, both `RequestInterface` and `Psr\Http\Message\MessageInterface` methods are considered. - - -The following examples will illustrate how basic operations are done in PSR-7. - -##### Examples - - -For this examples to work (at least) a PSR-7 implementation package is required. (eg: zendframework/zend-diactoros, guzzlehttp/psr7, slim/slim, etc) -All PSR-7 implementations should have the same behaviour. - -The following will be assumed: -`$request` is an object of `Psr\Http\Message\RequestInterface` and - -`$response` is an object implementing `Psr\Http\Message\RequestInterface` - - -### Working with HTTP Headers - -#### Adding headers to response: - -```php -$response->withHeader('My-Custom-Header', 'My Custom Message'); -``` - -#### Appending values to headers - -```php -$response->withAddedHeader('My-Custom-Header', 'The second message'); -``` - -#### Checking if header exists: - -```php -$request->hasHeader('My-Custom-Header'); // will return false -$response->hasHeader('My-Custom-Header'); // will return true -``` - -> Note: My-Custom-Header was only added in the Response - -#### Getting comma-separated values from a header (also applies to request) - -```php -// getting value from request headers -$request->getHeaderLine('Content-Type'); // will return: "text/html; charset=UTF-8" -// getting value from response headers -$response->getHeaderLine('My-Custom-Header'); // will return: "My Custom Message; The second message" -``` - -#### Getting array of value from a header (also applies to request) -```php -// getting value from request headers -$request->getHeader('Content-Type'); // will return: ["text/html", "charset=UTF-8"] -// getting value from response headers -$response->getHeader('My-Custom-Header'); // will return: ["My Custom Message", "The second message"] -``` - -#### Removing headers from HTTP Messages -```php -// removing a header from Request, removing deprecated "Content-MD5" header -$request->withoutHeader('Content-MD5'); - -// removing a header from Response -// effect: the browser won't know the size of the stream -// the browser will download the stream till it ends -$response->withoutHeader('Content-Length'); -``` - -### Working with HTTP Message Body - -When working with the PSR-7 there are two methods of implementation: -#### 1. Getting the body separately - -> This method makes the body handling easier to understand and is useful when repeatedly calling body methods. (You only call `getBody()` once). Using this method mistakes like `$response->write()` are also prevented. - -```php -$body = $response->getBody(); -// operations on body, eg. read, write, seek -// ... -// replacing the old body -$response->withBody($body); -// this last statement is optional as we working with objects -// in this case the "new" body is same with the "old" one -// the $body variable has the same value as the one in $request, only the reference is passed -``` - -#### 2. Working directly on response - -> This method is useful when only performing few operations as the `$request->getBody()` statement fragment is required - -```php -$response->getBody()->write('hello'); -``` - -### Getting the body contents - -The following snippet gets the contents of a stream contents. -> Note: Streams must be rewinded, if content was written into streams, it will be ignored when calling `getContents()` because the stream pointer is set to the last character, which is `\0` - meaning end of stream. -```php -$body = $response->getBody(); -$body->rewind(); // or $body->seek(0); -$bodyText = $body->getContents(); -``` -> Note: If `$body->seek(1)` is called before `$body->getContents()`, the first character will be ommited as the starting pointer is set to `1`, not `0`. This is why using `$body->rewind()` is recommended. - -### Append to body - -```php -$response->getBody()->write('Hello'); // writing directly -$body = $request->getBody(); // which is a `StreamInterface` -$body->write('xxxxx'); -``` - -### Prepend to body -Prepending is different when it comes to streams. The content must be copied before writing the content to be prepended. -The following example will explain the behaviour of streams. - -```php -// assuming our response is initially empty -$body = $repsonse->getBody(); -// writing the string "abcd" -$body->write('abcd'); - -// seeking to start of stream -$body->seek(0); -// writing 'ef' -$body->write('ef'); // at this point the stream contains "efcd" -``` - -#### Prepending by rewriting separately - -```php -// assuming our response body stream only contains: "abcd" -$body = $response->getBody(); -$body->rewind(); -$contents = $body->getContents(); // abcd -// seeking the stream to beginning -$body->rewind(); -$body->write('ef'); // stream contains "efcd" -$body->write($contents); // stream contains "efabcd" -``` - -> Note: `getContents()` seeks the stream while reading it, therefore if the second `rewind()` method call was not present the stream would have resulted in `abcdefabcd` because the `write()` method appends to stream if not preceeded by `rewind()` or `seek(0)`. - -#### Prepending by using contents as a string -```php -$body = $response->getBody(); -$body->rewind(); -$contents = $body->getContents(); // efabcd -$contents = 'ef'.$contents; -$body->rewind(); -$body->write($contents); -``` diff --git a/lib/phpspreadsheet/vendor/psr/http-message/src/MessageInterface.php b/lib/phpspreadsheet/vendor/psr/http-message/src/MessageInterface.php deleted file mode 100644 index a83c98518d5..00000000000 --- a/lib/phpspreadsheet/vendor/psr/http-message/src/MessageInterface.php +++ /dev/null @@ -1,187 +0,0 @@ -getHeaders() as $name => $values) { - * echo $name . ": " . implode(", ", $values); - * } - * - * // Emit headers iteratively: - * foreach ($message->getHeaders() as $name => $values) { - * foreach ($values as $value) { - * header(sprintf('%s: %s', $name, $value), false); - * } - * } - * - * While header names are not case-sensitive, getHeaders() will preserve the - * exact case in which headers were originally specified. - * - * @return string[][] Returns an associative array of the message's headers. Each - * key MUST be a header name, and each value MUST be an array of strings - * for that header. - */ - public function getHeaders(): array; - - /** - * Checks if a header exists by the given case-insensitive name. - * - * @param string $name Case-insensitive header field name. - * @return bool Returns true if any header names match the given header - * name using a case-insensitive string comparison. Returns false if - * no matching header name is found in the message. - */ - public function hasHeader(string $name): bool; - - /** - * Retrieves a message header value by the given case-insensitive name. - * - * This method returns an array of all the header values of the given - * case-insensitive header name. - * - * If the header does not appear in the message, this method MUST return an - * empty array. - * - * @param string $name Case-insensitive header field name. - * @return string[] An array of string values as provided for the given - * header. If the header does not appear in the message, this method MUST - * return an empty array. - */ - public function getHeader(string $name): array; - - /** - * Retrieves a comma-separated string of the values for a single header. - * - * This method returns all of the header values of the given - * case-insensitive header name as a string concatenated together using - * a comma. - * - * NOTE: Not all header values may be appropriately represented using - * comma concatenation. For such headers, use getHeader() instead - * and supply your own delimiter when concatenating. - * - * If the header does not appear in the message, this method MUST return - * an empty string. - * - * @param string $name Case-insensitive header field name. - * @return string A string of values as provided for the given header - * concatenated together using a comma. If the header does not appear in - * the message, this method MUST return an empty string. - */ - public function getHeaderLine(string $name): string; - - /** - * Return an instance with the provided value replacing the specified header. - * - * While header names are case-insensitive, the casing of the header will - * be preserved by this function, and returned from getHeaders(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new and/or updated header and value. - * - * @param string $name Case-insensitive header field name. - * @param string|string[] $value Header value(s). - * @return static - * @throws \InvalidArgumentException for invalid header names or values. - */ - public function withHeader(string $name, $value): MessageInterface; - - /** - * Return an instance with the specified header appended with the given value. - * - * Existing values for the specified header will be maintained. The new - * value(s) will be appended to the existing list. If the header did not - * exist previously, it will be added. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * new header and/or value. - * - * @param string $name Case-insensitive header field name to add. - * @param string|string[] $value Header value(s). - * @return static - * @throws \InvalidArgumentException for invalid header names or values. - */ - public function withAddedHeader(string $name, $value): MessageInterface; - - /** - * Return an instance without the specified header. - * - * Header resolution MUST be done without case-sensitivity. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that removes - * the named header. - * - * @param string $name Case-insensitive header field name to remove. - * @return static - */ - public function withoutHeader(string $name): MessageInterface; - - /** - * Gets the body of the message. - * - * @return StreamInterface Returns the body as a stream. - */ - public function getBody(): StreamInterface; - - /** - * Return an instance with the specified message body. - * - * The body MUST be a StreamInterface object. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return a new instance that has the - * new body stream. - * - * @param StreamInterface $body Body. - * @return static - * @throws \InvalidArgumentException When the body is not valid. - */ - public function withBody(StreamInterface $body): MessageInterface; -} diff --git a/lib/phpspreadsheet/vendor/psr/http-message/src/RequestInterface.php b/lib/phpspreadsheet/vendor/psr/http-message/src/RequestInterface.php deleted file mode 100644 index 33f85e559d0..00000000000 --- a/lib/phpspreadsheet/vendor/psr/http-message/src/RequestInterface.php +++ /dev/null @@ -1,130 +0,0 @@ -getQuery()` - * or from the `QUERY_STRING` server param. - * - * @return array - */ - public function getQueryParams(): array; - - /** - * Return an instance with the specified query string arguments. - * - * These values SHOULD remain immutable over the course of the incoming - * request. They MAY be injected during instantiation, such as from PHP's - * $_GET superglobal, or MAY be derived from some other value such as the - * URI. In cases where the arguments are parsed from the URI, the data - * MUST be compatible with what PHP's parse_str() would return for - * purposes of how duplicate query parameters are handled, and how nested - * sets are handled. - * - * Setting query string arguments MUST NOT change the URI stored by the - * request, nor the values in the server params. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated query string arguments. - * - * @param array $query Array of query string arguments, typically from - * $_GET. - * @return static - */ - public function withQueryParams(array $query): ServerRequestInterface; - - /** - * Retrieve normalized file upload data. - * - * This method returns upload metadata in a normalized tree, with each leaf - * an instance of Psr\Http\Message\UploadedFileInterface. - * - * These values MAY be prepared from $_FILES or the message body during - * instantiation, or MAY be injected via withUploadedFiles(). - * - * @return array An array tree of UploadedFileInterface instances; an empty - * array MUST be returned if no data is present. - */ - public function getUploadedFiles(): array; - - /** - * Create a new instance with the specified uploaded files. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated body parameters. - * - * @param array $uploadedFiles An array tree of UploadedFileInterface instances. - * @return static - * @throws \InvalidArgumentException if an invalid structure is provided. - */ - public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface; - - /** - * Retrieve any parameters provided in the request body. - * - * If the request Content-Type is either application/x-www-form-urlencoded - * or multipart/form-data, and the request method is POST, this method MUST - * return the contents of $_POST. - * - * Otherwise, this method may return any results of deserializing - * the request body content; as parsing returns structured content, the - * potential types MUST be arrays or objects only. A null value indicates - * the absence of body content. - * - * @return null|array|object The deserialized body parameters, if any. - * These will typically be an array or object. - */ - public function getParsedBody(); - - /** - * Return an instance with the specified body parameters. - * - * These MAY be injected during instantiation. - * - * If the request Content-Type is either application/x-www-form-urlencoded - * or multipart/form-data, and the request method is POST, use this method - * ONLY to inject the contents of $_POST. - * - * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of - * deserializing the request body content. Deserialization/parsing returns - * structured data, and, as such, this method ONLY accepts arrays or objects, - * or a null value if nothing was available to parse. - * - * As an example, if content negotiation determines that the request data - * is a JSON payload, this method could be used to create a request - * instance with the deserialized parameters. - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated body parameters. - * - * @param null|array|object $data The deserialized body data. This will - * typically be in an array or object. - * @return static - * @throws \InvalidArgumentException if an unsupported argument type is - * provided. - */ - public function withParsedBody($data): ServerRequestInterface; - - /** - * Retrieve attributes derived from the request. - * - * The request "attributes" may be used to allow injection of any - * parameters derived from the request: e.g., the results of path - * match operations; the results of decrypting cookies; the results of - * deserializing non-form-encoded message bodies; etc. Attributes - * will be application and request specific, and CAN be mutable. - * - * @return array Attributes derived from the request. - */ - public function getAttributes(): array; - - /** - * Retrieve a single derived request attribute. - * - * Retrieves a single derived request attribute as described in - * getAttributes(). If the attribute has not been previously set, returns - * the default value as provided. - * - * This method obviates the need for a hasAttribute() method, as it allows - * specifying a default value to return if the attribute is not found. - * - * @see getAttributes() - * @param string $name The attribute name. - * @param mixed $default Default value to return if the attribute does not exist. - * @return mixed - */ - public function getAttribute(string $name, $default = null); - - /** - * Return an instance with the specified derived request attribute. - * - * This method allows setting a single derived request attribute as - * described in getAttributes(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that has the - * updated attribute. - * - * @see getAttributes() - * @param string $name The attribute name. - * @param mixed $value The value of the attribute. - * @return static - */ - public function withAttribute(string $name, $value): ServerRequestInterface; - - /** - * Return an instance that removes the specified derived request attribute. - * - * This method allows removing a single derived request attribute as - * described in getAttributes(). - * - * This method MUST be implemented in such a way as to retain the - * immutability of the message, and MUST return an instance that removes - * the attribute. - * - * @see getAttributes() - * @param string $name The attribute name. - * @return static - */ - public function withoutAttribute(string $name): ServerRequestInterface; -} diff --git a/lib/phpspreadsheet/vendor/psr/http-message/src/StreamInterface.php b/lib/phpspreadsheet/vendor/psr/http-message/src/StreamInterface.php deleted file mode 100644 index a62aabb8288..00000000000 --- a/lib/phpspreadsheet/vendor/psr/http-message/src/StreamInterface.php +++ /dev/null @@ -1,158 +0,0 @@ - - * [user-info@]host[:port] - * - * - * If the port component is not set or is the standard port for the current - * scheme, it SHOULD NOT be included. - * - * @see https://tools.ietf.org/html/rfc3986#section-3.2 - * @return string The URI authority, in "[user-info@]host[:port]" format. - */ - public function getAuthority(): string; - - /** - * Retrieve the user information component of the URI. - * - * If no user information is present, this method MUST return an empty - * string. - * - * If a user is present in the URI, this will return that value; - * additionally, if the password is also present, it will be appended to the - * user value, with a colon (":") separating the values. - * - * The trailing "@" character is not part of the user information and MUST - * NOT be added. - * - * @return string The URI user information, in "username[:password]" format. - */ - public function getUserInfo(): string; - - /** - * Retrieve the host component of the URI. - * - * If no host is present, this method MUST return an empty string. - * - * The value returned MUST be normalized to lowercase, per RFC 3986 - * Section 3.2.2. - * - * @see http://tools.ietf.org/html/rfc3986#section-3.2.2 - * @return string The URI host. - */ - public function getHost(): string; - - /** - * Retrieve the port component of the URI. - * - * If a port is present, and it is non-standard for the current scheme, - * this method MUST return it as an integer. If the port is the standard port - * used with the current scheme, this method SHOULD return null. - * - * If no port is present, and no scheme is present, this method MUST return - * a null value. - * - * If no port is present, but a scheme is present, this method MAY return - * the standard port for that scheme, but SHOULD return null. - * - * @return null|int The URI port. - */ - public function getPort(): ?int; - - /** - * Retrieve the path component of the URI. - * - * The path can either be empty or absolute (starting with a slash) or - * rootless (not starting with a slash). Implementations MUST support all - * three syntaxes. - * - * Normally, the empty path "" and absolute path "/" are considered equal as - * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically - * do this normalization because in contexts with a trimmed base path, e.g. - * the front controller, this difference becomes significant. It's the task - * of the user to handle both "" and "/". - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.3. - * - * As an example, if the value should include a slash ("/") not intended as - * delimiter between path segments, that value MUST be passed in encoded - * form (e.g., "%2F") to the instance. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.3 - * @return string The URI path. - */ - public function getPath(): string; - - /** - * Retrieve the query string of the URI. - * - * If no query string is present, this method MUST return an empty string. - * - * The leading "?" character is not part of the query and MUST NOT be - * added. - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.4. - * - * As an example, if a value in a key/value pair of the query string should - * include an ampersand ("&") not intended as a delimiter between values, - * that value MUST be passed in encoded form (e.g., "%26") to the instance. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.4 - * @return string The URI query string. - */ - public function getQuery(): string; - - /** - * Retrieve the fragment component of the URI. - * - * If no fragment is present, this method MUST return an empty string. - * - * The leading "#" character is not part of the fragment and MUST NOT be - * added. - * - * The value returned MUST be percent-encoded, but MUST NOT double-encode - * any characters. To determine what characters to encode, please refer to - * RFC 3986, Sections 2 and 3.5. - * - * @see https://tools.ietf.org/html/rfc3986#section-2 - * @see https://tools.ietf.org/html/rfc3986#section-3.5 - * @return string The URI fragment. - */ - public function getFragment(): string; - - /** - * Return an instance with the specified scheme. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified scheme. - * - * Implementations MUST support the schemes "http" and "https" case - * insensitively, and MAY accommodate other schemes if required. - * - * An empty scheme is equivalent to removing the scheme. - * - * @param string $scheme The scheme to use with the new instance. - * @return static A new instance with the specified scheme. - * @throws \InvalidArgumentException for invalid or unsupported schemes. - */ - public function withScheme(string $scheme): UriInterface; - - /** - * Return an instance with the specified user information. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified user information. - * - * Password is optional, but the user information MUST include the - * user; an empty string for the user is equivalent to removing user - * information. - * - * @param string $user The user name to use for authority. - * @param null|string $password The password associated with $user. - * @return static A new instance with the specified user information. - */ - public function withUserInfo(string $user, ?string $password = null): UriInterface; - - /** - * Return an instance with the specified host. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified host. - * - * An empty host value is equivalent to removing the host. - * - * @param string $host The hostname to use with the new instance. - * @return static A new instance with the specified host. - * @throws \InvalidArgumentException for invalid hostnames. - */ - public function withHost(string $host): UriInterface; - - /** - * Return an instance with the specified port. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified port. - * - * Implementations MUST raise an exception for ports outside the - * established TCP and UDP port ranges. - * - * A null value provided for the port is equivalent to removing the port - * information. - * - * @param null|int $port The port to use with the new instance; a null value - * removes the port information. - * @return static A new instance with the specified port. - * @throws \InvalidArgumentException for invalid ports. - */ - public function withPort(?int $port): UriInterface; - - /** - * Return an instance with the specified path. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified path. - * - * The path can either be empty or absolute (starting with a slash) or - * rootless (not starting with a slash). Implementations MUST support all - * three syntaxes. - * - * If the path is intended to be domain-relative rather than path relative then - * it must begin with a slash ("/"). Paths not starting with a slash ("/") - * are assumed to be relative to some base path known to the application or - * consumer. - * - * Users can provide both encoded and decoded path characters. - * Implementations ensure the correct encoding as outlined in getPath(). - * - * @param string $path The path to use with the new instance. - * @return static A new instance with the specified path. - * @throws \InvalidArgumentException for invalid paths. - */ - public function withPath(string $path): UriInterface; - - /** - * Return an instance with the specified query string. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified query string. - * - * Users can provide both encoded and decoded query characters. - * Implementations ensure the correct encoding as outlined in getQuery(). - * - * An empty query string value is equivalent to removing the query string. - * - * @param string $query The query string to use with the new instance. - * @return static A new instance with the specified query string. - * @throws \InvalidArgumentException for invalid query strings. - */ - public function withQuery(string $query): UriInterface; - - /** - * Return an instance with the specified URI fragment. - * - * This method MUST retain the state of the current instance, and return - * an instance that contains the specified URI fragment. - * - * Users can provide both encoded and decoded fragment characters. - * Implementations ensure the correct encoding as outlined in getFragment(). - * - * An empty fragment value is equivalent to removing the fragment. - * - * @param string $fragment The fragment to use with the new instance. - * @return static A new instance with the specified fragment. - */ - public function withFragment(string $fragment): UriInterface; - - /** - * Return the string representation as a URI reference. - * - * Depending on which components of the URI are present, the resulting - * string is either a full URI or relative reference according to RFC 3986, - * Section 4.1. The method concatenates the various components of the URI, - * using the appropriate delimiters: - * - * - If a scheme is present, it MUST be suffixed by ":". - * - If an authority is present, it MUST be prefixed by "//". - * - The path can be concatenated without delimiters. But there are two - * cases where the path has to be adjusted to make the URI reference - * valid as PHP does not allow to throw an exception in __toString(): - * - If the path is rootless and an authority is present, the path MUST - * be prefixed by "/". - * - If the path is starting with more than one "/" and no authority is - * present, the starting slashes MUST be reduced to one. - * - If a query is present, it MUST be prefixed by "?". - * - If a fragment is present, it MUST be prefixed by "#". - * - * @see http://tools.ietf.org/html/rfc3986#section-4.1 - * @return string - */ - public function __toString(): string; -} From 3782af5a134b31dc108b95c7a50482260bfe59c4 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 31 Jul 2024 15:14:46 +0800 Subject: [PATCH 04/16] MDL-81031 core: Move psr\simple-cache --- lib/classes/component.php | 1 + .../vendor => }/psr/simple-cache/LICENSE.md | 0 .../vendor => }/psr/simple-cache/README.md | 0 .../vendor => }/psr/simple-cache/composer.json | 0 lib/psr/simple-cache/readme_moodle.txt | 10 ++++++++++ .../psr/simple-cache/src/CacheException.php | 0 .../psr/simple-cache/src/CacheInterface.php | 0 .../psr/simple-cache/src/InvalidArgumentException.php | 0 lib/thirdpartylibs.xml | 10 +++++++++- 9 files changed, 20 insertions(+), 1 deletion(-) rename lib/{phpspreadsheet/vendor => }/psr/simple-cache/LICENSE.md (100%) rename lib/{phpspreadsheet/vendor => }/psr/simple-cache/README.md (100%) rename lib/{phpspreadsheet/vendor => }/psr/simple-cache/composer.json (100%) create mode 100644 lib/psr/simple-cache/readme_moodle.txt rename lib/{phpspreadsheet/vendor => }/psr/simple-cache/src/CacheException.php (100%) rename lib/{phpspreadsheet/vendor => }/psr/simple-cache/src/CacheInterface.php (100%) rename lib/{phpspreadsheet/vendor => }/psr/simple-cache/src/InvalidArgumentException.php (100%) diff --git a/lib/classes/component.php b/lib/classes/component.php index 9db0d8f6678..be3e7b2e321 100644 --- a/lib/classes/component.php +++ b/lib/classes/component.php @@ -132,6 +132,7 @@ class component { \Psr\Clock::class => 'lib/psr/clock/src', \Psr\Container::class => 'lib/psr/container/src', \Psr\Log::class => "lib/psr/log/src", + \Psr\SimpleCache::class => 'lib/psr/simple-cache/src', \GuzzleHttp\Psr7::class => 'lib/guzzlehttp/psr7/src', \GuzzleHttp\Promise::class => 'lib/guzzlehttp/promises/src', \GuzzleHttp::class => 'lib/guzzlehttp/guzzle/src', diff --git a/lib/phpspreadsheet/vendor/psr/simple-cache/LICENSE.md b/lib/psr/simple-cache/LICENSE.md similarity index 100% rename from lib/phpspreadsheet/vendor/psr/simple-cache/LICENSE.md rename to lib/psr/simple-cache/LICENSE.md diff --git a/lib/phpspreadsheet/vendor/psr/simple-cache/README.md b/lib/psr/simple-cache/README.md similarity index 100% rename from lib/phpspreadsheet/vendor/psr/simple-cache/README.md rename to lib/psr/simple-cache/README.md diff --git a/lib/phpspreadsheet/vendor/psr/simple-cache/composer.json b/lib/psr/simple-cache/composer.json similarity index 100% rename from lib/phpspreadsheet/vendor/psr/simple-cache/composer.json rename to lib/psr/simple-cache/composer.json diff --git a/lib/psr/simple-cache/readme_moodle.txt b/lib/psr/simple-cache/readme_moodle.txt new file mode 100644 index 00000000000..71603d33d01 --- /dev/null +++ b/lib/psr/simple-cache/readme_moodle.txt @@ -0,0 +1,10 @@ +# PSR-16 Simple Cache + +This is a description for including the PSR-16 Interfaces in Moodle + +## Installation + +1. Visit https://github.com/php-fig/simple-cache +2. Download the latest release +3. Unzip in this folder +4. Update `thirdpartylibs.xml` diff --git a/lib/phpspreadsheet/vendor/psr/simple-cache/src/CacheException.php b/lib/psr/simple-cache/src/CacheException.php similarity index 100% rename from lib/phpspreadsheet/vendor/psr/simple-cache/src/CacheException.php rename to lib/psr/simple-cache/src/CacheException.php diff --git a/lib/phpspreadsheet/vendor/psr/simple-cache/src/CacheInterface.php b/lib/psr/simple-cache/src/CacheInterface.php similarity index 100% rename from lib/phpspreadsheet/vendor/psr/simple-cache/src/CacheInterface.php rename to lib/psr/simple-cache/src/CacheInterface.php diff --git a/lib/phpspreadsheet/vendor/psr/simple-cache/src/InvalidArgumentException.php b/lib/psr/simple-cache/src/InvalidArgumentException.php similarity index 100% rename from lib/phpspreadsheet/vendor/psr/simple-cache/src/InvalidArgumentException.php rename to lib/psr/simple-cache/src/InvalidArgumentException.php diff --git a/lib/thirdpartylibs.xml b/lib/thirdpartylibs.xml index c64830bdc6f..8f388fc3cb6 100644 --- a/lib/thirdpartylibs.xml +++ b/lib/thirdpartylibs.xml @@ -51,7 +51,7 @@ - phpspreadsheet + phpspreadsheet/phpspreadsheet PhpSpreadsheet Library to read, write and create spreadsheet documents in PHP. 1.29.0 @@ -676,6 +676,14 @@ All rights reserved. MIT https://github.com/php-fig/log + + psr/simple-cache + simple-cache + Common interface for logging libraries + 3.0.0 + MIT + https://github.com/php-fig/simple-cache + phpxmlrpc phpxmlrpc From 712dc98c7e6d13ba7d4d80a7ed1a6d16822c8362 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 31 Jul 2024 16:36:18 +0800 Subject: [PATCH 05/16] MDL-81031 core: Move phpspreadsheet to autoload properly --- lib/classes/component.php | 3 + lib/excellib.class.php | 26 +- .../{vendor => }/markbaker/complex/README.md | 0 .../markbaker/complex/classes/src/Complex.php | 0 .../complex/classes/src/Exception.php | 0 .../complex/classes/src/Functions.php | 0 .../complex/classes/src/Operations.php | 0 .../markbaker/complex/composer.json | 0 .../complex/examples/complexTest.php | 0 .../complex/examples/testFunctions.php | 0 .../complex/examples/testOperations.php | 0 .../{vendor => }/markbaker/complex/license.md | 0 .../{vendor => }/markbaker/matrix/README.md | 0 .../markbaker/matrix/buildPhar.php | 0 .../markbaker/matrix/classes/src/Builder.php | 0 .../src/Decomposition/Decomposition.php | 0 .../matrix/classes/src/Decomposition/LU.php | 0 .../matrix/classes/src/Decomposition/QR.php | 0 .../matrix/classes/src/Div0Exception.php | 0 .../matrix/classes/src/Exception.php | 0 .../matrix/classes/src/Functions.php | 0 .../markbaker/matrix/classes/src/Matrix.php | 0 .../matrix/classes/src/Operations.php | 0 .../matrix/classes/src/Operators/Addition.php | 0 .../classes/src/Operators/DirectSum.php | 0 .../matrix/classes/src/Operators/Division.php | 0 .../classes/src/Operators/Multiplication.php | 0 .../matrix/classes/src/Operators/Operator.php | 0 .../classes/src/Operators/Subtraction.php | 0 .../markbaker/matrix/composer.json | 0 .../{vendor => }/markbaker/matrix/license.md | 0 .../phpspreadsheet/CHANGELOG.md | 0 .../phpspreadsheet/CONTRIBUTING.md | 0 .../phpoffice => }/phpspreadsheet/LICENSE | 0 .../phpoffice => }/phpspreadsheet/README.md | 0 .../phpspreadsheet/composer.json | 0 .../phpspreadsheet/phpunit10.xml.dist | 0 .../Calculation/ArrayEnabled.php | 0 .../Calculation/BinaryComparison.php | 0 .../Calculation/Calculation.php | 0 .../PhpSpreadsheet/Calculation/Category.php | 0 .../PhpSpreadsheet/Calculation/Database.php | 0 .../Calculation/Database/DAverage.php | 0 .../Calculation/Database/DCount.php | 0 .../Calculation/Database/DCountA.php | 0 .../Calculation/Database/DGet.php | 0 .../Calculation/Database/DMax.php | 0 .../Calculation/Database/DMin.php | 0 .../Calculation/Database/DProduct.php | 0 .../Calculation/Database/DStDev.php | 0 .../Calculation/Database/DStDevP.php | 0 .../Calculation/Database/DSum.php | 0 .../Calculation/Database/DVar.php | 0 .../Calculation/Database/DVarP.php | 0 .../Calculation/Database/DatabaseAbstract.php | 0 .../PhpSpreadsheet/Calculation/DateTime.php | 0 .../Calculation/DateTimeExcel/Constants.php | 0 .../Calculation/DateTimeExcel/Current.php | 0 .../Calculation/DateTimeExcel/Date.php | 0 .../Calculation/DateTimeExcel/DateParts.php | 0 .../Calculation/DateTimeExcel/DateValue.php | 0 .../Calculation/DateTimeExcel/Days.php | 0 .../Calculation/DateTimeExcel/Days360.php | 0 .../Calculation/DateTimeExcel/Difference.php | 0 .../Calculation/DateTimeExcel/Helpers.php | 0 .../Calculation/DateTimeExcel/Month.php | 0 .../Calculation/DateTimeExcel/NetworkDays.php | 0 .../Calculation/DateTimeExcel/Time.php | 0 .../Calculation/DateTimeExcel/TimeParts.php | 0 .../Calculation/DateTimeExcel/TimeValue.php | 0 .../Calculation/DateTimeExcel/Week.php | 0 .../Calculation/DateTimeExcel/WorkDay.php | 0 .../Calculation/DateTimeExcel/YearFrac.php | 0 .../Engine/ArrayArgumentHelper.php | 0 .../Engine/ArrayArgumentProcessor.php | 0 .../Calculation/Engine/BranchPruner.php | 0 .../Engine/CyclicReferenceStack.php | 0 .../Calculation/Engine/FormattedNumber.php | 0 .../Calculation/Engine/Logger.php | 0 .../Calculation/Engine/Operands/Operand.php | 0 .../Engine/Operands/StructuredReference.php | 0 .../Calculation/Engineering.php | 0 .../Calculation/Engineering/BesselI.php | 0 .../Calculation/Engineering/BesselJ.php | 0 .../Calculation/Engineering/BesselK.php | 0 .../Calculation/Engineering/BesselY.php | 0 .../Calculation/Engineering/BitWise.php | 0 .../Calculation/Engineering/Compare.php | 0 .../Calculation/Engineering/Complex.php | 0 .../Engineering/ComplexFunctions.php | 0 .../Engineering/ComplexOperations.php | 0 .../Calculation/Engineering/Constants.php | 0 .../Calculation/Engineering/ConvertBase.php | 0 .../Calculation/Engineering/ConvertBinary.php | 0 .../Engineering/ConvertDecimal.php | 0 .../Calculation/Engineering/ConvertHex.php | 0 .../Calculation/Engineering/ConvertOctal.php | 0 .../Calculation/Engineering/ConvertUOM.php | 0 .../Engineering/EngineeringValidations.php | 0 .../Calculation/Engineering/Erf.php | 0 .../Calculation/Engineering/ErfC.php | 0 .../PhpSpreadsheet/Calculation/Exception.php | 0 .../Calculation/ExceptionHandler.php | 0 .../PhpSpreadsheet/Calculation/Financial.php | 0 .../Calculation/Financial/Amortization.php | 0 .../CashFlow/CashFlowValidations.php | 0 .../Financial/CashFlow/Constant/Periodic.php | 0 .../CashFlow/Constant/Periodic/Cumulative.php | 0 .../CashFlow/Constant/Periodic/Interest.php | 0 .../Periodic/InterestAndPrincipal.php | 0 .../CashFlow/Constant/Periodic/Payments.php | 0 .../Calculation/Financial/CashFlow/Single.php | 0 .../CashFlow/Variable/NonPeriodic.php | 0 .../Financial/CashFlow/Variable/Periodic.php | 0 .../Calculation/Financial/Constants.php | 0 .../Calculation/Financial/Coupons.php | 0 .../Calculation/Financial/Depreciation.php | 0 .../Calculation/Financial/Dollar.php | 0 .../Financial/FinancialValidations.php | 0 .../Calculation/Financial/Helpers.php | 0 .../Calculation/Financial/InterestRate.php | 0 .../Financial/Securities/AccruedInterest.php | 0 .../Financial/Securities/Price.php | 0 .../Financial/Securities/Rates.php | 0 .../Securities/SecurityValidations.php | 0 .../Financial/Securities/Yields.php | 0 .../Calculation/Financial/TreasuryBill.php | 0 .../Calculation/FormulaParser.php | 0 .../Calculation/FormulaToken.php | 0 .../PhpSpreadsheet/Calculation/Functions.php | 0 .../Calculation/Information/ErrorValue.php | 0 .../Calculation/Information/ExcelError.php | 0 .../Calculation/Information/Value.php | 0 .../Calculation/Internal/MakeMatrix.php | 0 .../Calculation/Internal/WildcardMatch.php | 0 .../PhpSpreadsheet/Calculation/Logical.php | 0 .../Calculation/Logical/Boolean.php | 0 .../Calculation/Logical/Conditional.php | 0 .../Calculation/Logical/Operations.php | 0 .../PhpSpreadsheet/Calculation/LookupRef.php | 0 .../Calculation/LookupRef/Address.php | 0 .../Calculation/LookupRef/ExcelMatch.php | 0 .../Calculation/LookupRef/Filter.php | 0 .../Calculation/LookupRef/Formula.php | 0 .../Calculation/LookupRef/HLookup.php | 0 .../Calculation/LookupRef/Helpers.php | 0 .../Calculation/LookupRef/Hyperlink.php | 0 .../Calculation/LookupRef/Indirect.php | 0 .../Calculation/LookupRef/Lookup.php | 0 .../Calculation/LookupRef/LookupBase.php | 0 .../LookupRef/LookupRefValidations.php | 0 .../Calculation/LookupRef/Matrix.php | 0 .../Calculation/LookupRef/Offset.php | 0 .../LookupRef/RowColumnInformation.php | 0 .../Calculation/LookupRef/Selection.php | 0 .../Calculation/LookupRef/Sort.php | 0 .../Calculation/LookupRef/Unique.php | 0 .../Calculation/LookupRef/VLookup.php | 0 .../PhpSpreadsheet/Calculation/MathTrig.php | 0 .../Calculation/MathTrig/Absolute.php | 0 .../Calculation/MathTrig/Angle.php | 0 .../Calculation/MathTrig/Arabic.php | 0 .../Calculation/MathTrig/Base.php | 0 .../Calculation/MathTrig/Ceiling.php | 0 .../Calculation/MathTrig/Combinations.php | 0 .../Calculation/MathTrig/Exp.php | 0 .../Calculation/MathTrig/Factorial.php | 0 .../Calculation/MathTrig/Floor.php | 0 .../Calculation/MathTrig/Gcd.php | 0 .../Calculation/MathTrig/Helpers.php | 0 .../Calculation/MathTrig/IntClass.php | 0 .../Calculation/MathTrig/Lcm.php | 0 .../Calculation/MathTrig/Logarithms.php | 0 .../Calculation/MathTrig/MatrixFunctions.php | 0 .../Calculation/MathTrig/Operations.php | 0 .../Calculation/MathTrig/Random.php | 0 .../Calculation/MathTrig/Roman.php | 0 .../Calculation/MathTrig/Round.php | 0 .../Calculation/MathTrig/SeriesSum.php | 0 .../Calculation/MathTrig/Sign.php | 0 .../Calculation/MathTrig/Sqrt.php | 0 .../Calculation/MathTrig/Subtotal.php | 0 .../Calculation/MathTrig/Sum.php | 0 .../Calculation/MathTrig/SumSquares.php | 0 .../Calculation/MathTrig/Trig/Cosecant.php | 0 .../Calculation/MathTrig/Trig/Cosine.php | 0 .../Calculation/MathTrig/Trig/Cotangent.php | 0 .../Calculation/MathTrig/Trig/Secant.php | 0 .../Calculation/MathTrig/Trig/Sine.php | 0 .../Calculation/MathTrig/Trig/Tangent.php | 0 .../Calculation/MathTrig/Trunc.php | 0 .../Calculation/Statistical.php | 0 .../Calculation/Statistical/AggregateBase.php | 0 .../Calculation/Statistical/Averages.php | 0 .../Calculation/Statistical/Averages/Mean.php | 0 .../Calculation/Statistical/Conditional.php | 0 .../Calculation/Statistical/Confidence.php | 0 .../Calculation/Statistical/Counts.php | 0 .../Calculation/Statistical/Deviations.php | 0 .../Statistical/Distributions/Beta.php | 0 .../Statistical/Distributions/Binomial.php | 0 .../Statistical/Distributions/ChiSquared.php | 0 .../Distributions/DistributionValidations.php | 0 .../Statistical/Distributions/Exponential.php | 0 .../Statistical/Distributions/F.php | 0 .../Statistical/Distributions/Fisher.php | 0 .../Statistical/Distributions/Gamma.php | 0 .../Statistical/Distributions/GammaBase.php | 0 .../Distributions/HyperGeometric.php | 0 .../Statistical/Distributions/LogNormal.php | 0 .../Distributions/NewtonRaphson.php | 0 .../Statistical/Distributions/Normal.php | 0 .../Statistical/Distributions/Poisson.php | 0 .../Distributions/StandardNormal.php | 0 .../Statistical/Distributions/StudentT.php | 0 .../Statistical/Distributions/Weibull.php | 0 .../Calculation/Statistical/MaxMinBase.php | 0 .../Calculation/Statistical/Maximum.php | 0 .../Calculation/Statistical/Minimum.php | 0 .../Calculation/Statistical/Percentiles.php | 0 .../Calculation/Statistical/Permutations.php | 0 .../Calculation/Statistical/Size.php | 0 .../Statistical/StandardDeviations.php | 0 .../Calculation/Statistical/Standardize.php | 0 .../Statistical/StatisticalValidations.php | 0 .../Calculation/Statistical/Trends.php | 0 .../Calculation/Statistical/VarianceBase.php | 0 .../Calculation/Statistical/Variances.php | 0 .../PhpSpreadsheet/Calculation/TextData.php | 0 .../Calculation/TextData/CaseConvert.php | 0 .../Calculation/TextData/CharacterConvert.php | 0 .../Calculation/TextData/Concatenate.php | 0 .../Calculation/TextData/Extract.php | 0 .../Calculation/TextData/Format.php | 0 .../Calculation/TextData/Helpers.php | 0 .../Calculation/TextData/Replace.php | 0 .../Calculation/TextData/Search.php | 0 .../Calculation/TextData/Text.php | 0 .../Calculation/TextData/Trim.php | 0 .../Calculation/Token/Stack.php | 0 .../src/PhpSpreadsheet/Calculation/Web.php | 0 .../Calculation/Web/Service.php | 0 .../Calculation/locale/Translations.xlsx | Bin .../Calculation/locale/bg/config | 0 .../Calculation/locale/bg/functions | 0 .../Calculation/locale/cs/config | 0 .../Calculation/locale/cs/functions | 0 .../Calculation/locale/da/config | 0 .../Calculation/locale/da/functions | 0 .../Calculation/locale/de/config | 0 .../Calculation/locale/de/functions | 0 .../Calculation/locale/en/uk/config | 0 .../Calculation/locale/es/config | 0 .../Calculation/locale/es/functions | 0 .../Calculation/locale/fi/config | 0 .../Calculation/locale/fi/functions | 0 .../Calculation/locale/fr/config | 0 .../Calculation/locale/fr/functions | 0 .../Calculation/locale/hu/config | 0 .../Calculation/locale/hu/functions | 0 .../Calculation/locale/it/config | 0 .../Calculation/locale/it/functions | 0 .../Calculation/locale/nb/config | 0 .../Calculation/locale/nb/functions | 0 .../Calculation/locale/nl/config | 0 .../Calculation/locale/nl/functions | 0 .../Calculation/locale/pl/config | 0 .../Calculation/locale/pl/functions | 0 .../Calculation/locale/pt/br/config | 0 .../Calculation/locale/pt/br/functions | 0 .../Calculation/locale/pt/config | 0 .../Calculation/locale/pt/functions | 0 .../Calculation/locale/ru/config | 0 .../Calculation/locale/ru/functions | 0 .../Calculation/locale/sv/config | 0 .../Calculation/locale/sv/functions | 0 .../Calculation/locale/tr/config | 0 .../Calculation/locale/tr/functions | 0 .../src/PhpSpreadsheet/Cell/AddressHelper.php | 0 .../src/PhpSpreadsheet/Cell/AddressRange.php | 0 .../Cell/AdvancedValueBinder.php | 0 .../src/PhpSpreadsheet/Cell/Cell.php | 0 .../src/PhpSpreadsheet/Cell/CellAddress.php | 0 .../src/PhpSpreadsheet/Cell/CellRange.php | 0 .../src/PhpSpreadsheet/Cell/ColumnRange.php | 0 .../src/PhpSpreadsheet/Cell/Coordinate.php | 0 .../src/PhpSpreadsheet/Cell/DataType.php | 0 .../PhpSpreadsheet/Cell/DataValidation.php | 0 .../src/PhpSpreadsheet/Cell/DataValidator.php | 0 .../Cell/DefaultValueBinder.php | 0 .../src/PhpSpreadsheet/Cell/Hyperlink.php | 0 .../src/PhpSpreadsheet/Cell/IValueBinder.php | 0 .../src/PhpSpreadsheet/Cell/IgnoredErrors.php | 0 .../src/PhpSpreadsheet/Cell/RowRange.php | 0 .../PhpSpreadsheet/Cell/StringValueBinder.php | 0 .../PhpSpreadsheet/CellReferenceHelper.php | 0 .../src/PhpSpreadsheet/Chart/Axis.php | 0 .../src/PhpSpreadsheet/Chart/AxisText.php | 0 .../src/PhpSpreadsheet/Chart/Chart.php | 0 .../src/PhpSpreadsheet/Chart/ChartColor.php | 0 .../src/PhpSpreadsheet/Chart/DataSeries.php | 0 .../PhpSpreadsheet/Chart/DataSeriesValues.php | 0 .../src/PhpSpreadsheet/Chart/Exception.php | 0 .../src/PhpSpreadsheet/Chart/GridLines.php | 0 .../src/PhpSpreadsheet/Chart/Layout.php | 0 .../src/PhpSpreadsheet/Chart/Legend.php | 0 .../src/PhpSpreadsheet/Chart/PlotArea.php | 0 .../src/PhpSpreadsheet/Chart/Properties.php | 0 .../Chart/Renderer/IRenderer.php | 0 .../PhpSpreadsheet/Chart/Renderer/JpGraph.php | 0 .../Chart/Renderer/JpGraphRendererBase.php | 0 .../Chart/Renderer/MtJpGraphRenderer.php | 0 .../Chart/Renderer/PHP Charting Libraries.txt | 0 .../src/PhpSpreadsheet/Chart/Title.php | 0 .../src/PhpSpreadsheet/Chart/TrendLine.php | 0 .../src/PhpSpreadsheet/Collection/Cells.php | 0 .../Collection/CellsFactory.php | 0 .../Collection/Memory/SimpleCache1.php | 0 .../Collection/Memory/SimpleCache3.php | 0 .../src/PhpSpreadsheet/Comment.php | 0 .../src/PhpSpreadsheet/DefinedName.php | 0 .../PhpSpreadsheet/Document/Properties.php | 0 .../src/PhpSpreadsheet/Document/Security.php | 0 .../src/PhpSpreadsheet/Exception.php | 0 .../src/PhpSpreadsheet/HashTable.php | 0 .../src/PhpSpreadsheet/Helper/Dimension.php | 0 .../src/PhpSpreadsheet/Helper/Downloader.php | 0 .../src/PhpSpreadsheet/Helper/Handler.php | 0 .../src/PhpSpreadsheet/Helper/Html.php | 0 .../src/PhpSpreadsheet/Helper/Sample.php | 0 .../src/PhpSpreadsheet/Helper/Size.php | 0 .../src/PhpSpreadsheet/Helper/TextGrid.php | 0 .../src/PhpSpreadsheet/IComparable.php | 0 .../src/PhpSpreadsheet/IOFactory.php | 0 .../src/PhpSpreadsheet/NamedFormula.php | 0 .../src/PhpSpreadsheet/NamedRange.php | 0 .../src/PhpSpreadsheet/Reader/BaseReader.php | 0 .../src/PhpSpreadsheet/Reader/Csv.php | 0 .../PhpSpreadsheet/Reader/Csv/Delimiter.php | 0 .../Reader/DefaultReadFilter.php | 0 .../src/PhpSpreadsheet/Reader/Exception.php | 0 .../src/PhpSpreadsheet/Reader/Gnumeric.php | 0 .../Reader/Gnumeric/PageSetup.php | 0 .../Reader/Gnumeric/Properties.php | 0 .../PhpSpreadsheet/Reader/Gnumeric/Styles.php | 0 .../src/PhpSpreadsheet/Reader/Html.php | 0 .../src/PhpSpreadsheet/Reader/IReadFilter.php | 0 .../src/PhpSpreadsheet/Reader/IReader.php | 0 .../src/PhpSpreadsheet/Reader/Ods.php | 0 .../PhpSpreadsheet/Reader/Ods/AutoFilter.php | 0 .../PhpSpreadsheet/Reader/Ods/BaseLoader.php | 0 .../Reader/Ods/DefinedNames.php | 0 .../Reader/Ods/FormulaTranslator.php | 0 .../Reader/Ods/PageSettings.php | 0 .../PhpSpreadsheet/Reader/Ods/Properties.php | 0 .../Reader/Security/XmlScanner.php | 0 .../src/PhpSpreadsheet/Reader/Slk.php | 0 .../src/PhpSpreadsheet/Reader/Xlsx.php | 0 .../PhpSpreadsheet/Reader/Xlsx/AutoFilter.php | 0 .../Reader/Xlsx/BaseParserClass.php | 0 .../src/PhpSpreadsheet/Reader/Xlsx/Chart.php | 0 .../Reader/Xlsx/ColumnAndRowAttributes.php | 0 .../Reader/Xlsx/ConditionalStyles.php | 0 .../Reader/Xlsx/DataValidations.php | 0 .../PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php | 0 .../PhpSpreadsheet/Reader/Xlsx/Namespaces.php | 0 .../PhpSpreadsheet/Reader/Xlsx/PageSetup.php | 0 .../PhpSpreadsheet/Reader/Xlsx/Properties.php | 0 .../Reader/Xlsx/SharedFormula.php | 0 .../Reader/Xlsx/SheetViewOptions.php | 0 .../PhpSpreadsheet/Reader/Xlsx/SheetViews.php | 0 .../src/PhpSpreadsheet/Reader/Xlsx/Styles.php | 0 .../Reader/Xlsx/TableReader.php | 0 .../src/PhpSpreadsheet/Reader/Xlsx/Theme.php | 0 .../Reader/Xlsx/WorkbookView.php | 0 .../src/PhpSpreadsheet/Reader/Xml.php | 0 .../Reader/Xml/DataValidations.php | 0 .../Reader/Xml/PageSettings.php | 0 .../PhpSpreadsheet/Reader/Xml/Properties.php | 0 .../src/PhpSpreadsheet/Reader/Xml/Style.php | 0 .../Reader/Xml/Style/Alignment.php | 0 .../Reader/Xml/Style/Border.php | 0 .../PhpSpreadsheet/Reader/Xml/Style/Fill.php | 0 .../PhpSpreadsheet/Reader/Xml/Style/Font.php | 0 .../Reader/Xml/Style/NumberFormat.php | 0 .../Reader/Xml/Style/StyleBase.php | 0 .../src/PhpSpreadsheet/ReferenceHelper.php | 0 .../PhpSpreadsheet/RichText/ITextElement.php | 0 .../src/PhpSpreadsheet/RichText/RichText.php | 0 .../src/PhpSpreadsheet/RichText/Run.php | 0 .../PhpSpreadsheet/RichText/TextElement.php | 0 .../src/PhpSpreadsheet/Settings.php | 0 .../src/PhpSpreadsheet/Spreadsheet.php | 0 .../src/PhpSpreadsheet/Style/Alignment.php | 0 .../src/PhpSpreadsheet/Style/Border.php | 0 .../src/PhpSpreadsheet/Style/Borders.php | 0 .../src/PhpSpreadsheet/Style/Color.php | 0 .../src/PhpSpreadsheet/Style/Conditional.php | 0 .../ConditionalFormatting/CellMatcher.php | 0 .../CellStyleAssessor.php | 0 .../ConditionalDataBar.php | 0 .../ConditionalDataBarExtension.php | 0 .../ConditionalFormatValueObject.php | 0 .../ConditionalFormattingRuleExtension.php | 0 .../ConditionalFormatting/StyleMerger.php | 0 .../Style/ConditionalFormatting/Wizard.php | 0 .../ConditionalFormatting/Wizard/Blanks.php | 0 .../Wizard/CellValue.php | 0 .../Wizard/DateValue.php | 0 .../Wizard/Duplicates.php | 0 .../ConditionalFormatting/Wizard/Errors.php | 0 .../Wizard/Expression.php | 0 .../Wizard/TextValue.php | 0 .../Wizard/WizardAbstract.php | 0 .../Wizard/WizardInterface.php | 0 .../src/PhpSpreadsheet/Style/Fill.php | 0 .../src/PhpSpreadsheet/Style/Font.php | 0 .../src/PhpSpreadsheet/Style/NumberFormat.php | 0 .../Style/NumberFormat/BaseFormatter.php | 0 .../Style/NumberFormat/DateFormatter.php | 0 .../Style/NumberFormat/Formatter.php | 0 .../Style/NumberFormat/FractionFormatter.php | 0 .../Style/NumberFormat/NumberFormatter.php | 0 .../NumberFormat/PercentageFormatter.php | 0 .../Style/NumberFormat/Wizard/Accounting.php | 0 .../Style/NumberFormat/Wizard/Currency.php | 0 .../Style/NumberFormat/Wizard/Date.php | 0 .../Style/NumberFormat/Wizard/DateTime.php | 0 .../NumberFormat/Wizard/DateTimeWizard.php | 0 .../Style/NumberFormat/Wizard/Duration.php | 0 .../Style/NumberFormat/Wizard/Locale.php | 0 .../Style/NumberFormat/Wizard/Number.php | 0 .../Style/NumberFormat/Wizard/NumberBase.php | 0 .../Style/NumberFormat/Wizard/Percentage.php | 0 .../Style/NumberFormat/Wizard/Scientific.php | 0 .../Style/NumberFormat/Wizard/Time.php | 0 .../Style/NumberFormat/Wizard/Wizard.php | 0 .../src/PhpSpreadsheet/Style/Protection.php | 0 .../src/PhpSpreadsheet/Style/RgbTint.php | 0 .../src/PhpSpreadsheet/Style/Style.php | 0 .../src/PhpSpreadsheet/Style/Supervisor.php | 0 .../src/PhpSpreadsheet/Theme.php | 0 .../PhpSpreadsheet/Worksheet/AutoFilter.php | 0 .../Worksheet/AutoFilter/Column.php | 0 .../Worksheet/AutoFilter/Column/Rule.php | 0 .../src/PhpSpreadsheet/Worksheet/AutoFit.php | 0 .../PhpSpreadsheet/Worksheet/BaseDrawing.php | 0 .../PhpSpreadsheet/Worksheet/CellIterator.php | 0 .../src/PhpSpreadsheet/Worksheet/Column.php | 0 .../Worksheet/ColumnCellIterator.php | 0 .../Worksheet/ColumnDimension.php | 0 .../Worksheet/ColumnIterator.php | 0 .../PhpSpreadsheet/Worksheet/Dimension.php | 0 .../src/PhpSpreadsheet/Worksheet/Drawing.php | 0 .../Worksheet/Drawing/Shadow.php | 0 .../PhpSpreadsheet/Worksheet/HeaderFooter.php | 0 .../Worksheet/HeaderFooterDrawing.php | 0 .../src/PhpSpreadsheet/Worksheet/Iterator.php | 0 .../Worksheet/MemoryDrawing.php | 0 .../PhpSpreadsheet/Worksheet/PageBreak.php | 0 .../PhpSpreadsheet/Worksheet/PageMargins.php | 0 .../PhpSpreadsheet/Worksheet/PageSetup.php | 0 .../PhpSpreadsheet/Worksheet/Protection.php | 0 .../src/PhpSpreadsheet/Worksheet/Row.php | 0 .../Worksheet/RowCellIterator.php | 0 .../PhpSpreadsheet/Worksheet/RowDimension.php | 0 .../PhpSpreadsheet/Worksheet/RowIterator.php | 0 .../PhpSpreadsheet/Worksheet/SheetView.php | 0 .../src/PhpSpreadsheet/Worksheet/Table.php | 0 .../PhpSpreadsheet/Worksheet/Table/Column.php | 0 .../Worksheet/Table/TableStyle.php | 0 .../PhpSpreadsheet/Worksheet/Validations.php | 0 .../PhpSpreadsheet/Worksheet/Worksheet.php | 0 .../src/PhpSpreadsheet/Writer/BaseWriter.php | 0 .../src/PhpSpreadsheet/Writer/Csv.php | 0 .../src/PhpSpreadsheet/Writer/Exception.php | 0 .../src/PhpSpreadsheet/Writer/Html.php | 0 .../src/PhpSpreadsheet/Writer/IWriter.php | 0 .../src/PhpSpreadsheet/Writer/Ods.php | 0 .../PhpSpreadsheet/Writer/Ods/AutoFilters.php | 0 .../Writer/Ods/Cell/Comment.php | 0 .../PhpSpreadsheet/Writer/Ods/Cell/Style.php | 0 .../src/PhpSpreadsheet/Writer/Ods/Content.php | 0 .../src/PhpSpreadsheet/Writer/Ods/Formula.php | 0 .../src/PhpSpreadsheet/Writer/Ods/Meta.php | 0 .../src/PhpSpreadsheet/Writer/Ods/MetaInf.php | 0 .../PhpSpreadsheet/Writer/Ods/Mimetype.php | 0 .../Writer/Ods/NamedExpressions.php | 0 .../PhpSpreadsheet/Writer/Ods/Settings.php | 0 .../src/PhpSpreadsheet/Writer/Ods/Styles.php | 0 .../PhpSpreadsheet/Writer/Ods/Thumbnails.php | 0 .../PhpSpreadsheet/Writer/Ods/WriterPart.php | 0 .../src/PhpSpreadsheet/Writer/Pdf.php | 0 .../src/PhpSpreadsheet/Writer/Pdf/Dompdf.php | 0 .../src/PhpSpreadsheet/Writer/Pdf/Mpdf.php | 0 .../src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php | 0 .../src/PhpSpreadsheet/Writer/Xlsx.php | 0 .../PhpSpreadsheet/Writer/Xlsx/AutoFilter.php | 0 .../src/PhpSpreadsheet/Writer/Xlsx/Chart.php | 0 .../PhpSpreadsheet/Writer/Xlsx/Comments.php | 0 .../Writer/Xlsx/ContentTypes.php | 0 .../Writer/Xlsx/DefinedNames.php | 0 .../PhpSpreadsheet/Writer/Xlsx/DocProps.php | 0 .../PhpSpreadsheet/Writer/Xlsx/Drawing.php | 0 .../Writer/Xlsx/FunctionPrefix.php | 0 .../src/PhpSpreadsheet/Writer/Xlsx/Rels.php | 0 .../PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php | 0 .../PhpSpreadsheet/Writer/Xlsx/RelsVBA.php | 0 .../Writer/Xlsx/StringTable.php | 0 .../src/PhpSpreadsheet/Writer/Xlsx/Style.php | 0 .../src/PhpSpreadsheet/Writer/Xlsx/Table.php | 0 .../src/PhpSpreadsheet/Writer/Xlsx/Theme.php | 0 .../PhpSpreadsheet/Writer/Xlsx/Workbook.php | 0 .../PhpSpreadsheet/Writer/Xlsx/Worksheet.php | 0 .../PhpSpreadsheet/Writer/Xlsx/WriterPart.php | 0 .../src/PhpSpreadsheet/Writer/ZipStream0.php | 0 .../src/PhpSpreadsheet/Writer/ZipStream2.php | 0 .../src/PhpSpreadsheet/Writer/ZipStream3.php | 0 lib/phpspreadsheet/vendor/autoload.php | 44 +- .../vendor/composer/ClassLoader.php | 585 --------------- .../vendor/composer/InstalledVersions.php | 359 --------- lib/phpspreadsheet/vendor/composer/LICENSE | 21 - .../vendor/composer/autoload_classmap.php | 10 - .../vendor/composer/autoload_namespaces.php | 9 - .../vendor/composer/autoload_psr4.php | 15 - .../vendor/composer/autoload_real.php | 38 - .../vendor/composer/autoload_static.php | 68 -- .../vendor/composer/installed.json | 450 ------------ .../vendor/composer/installed.php | 110 --- .../vendor/composer/platform_check.php | 26 - .../vendor/markbaker/matrix/examples/test.php | 33 - .../src/PhpSpreadsheet/Shared/CodePage.php | 114 --- .../src/PhpSpreadsheet/Shared/Date.php | 556 -------------- .../src/PhpSpreadsheet/Shared/Drawing.php | 177 ----- .../src/PhpSpreadsheet/Shared/Escher.php | 64 -- .../Shared/Escher/DgContainer.php | 65 -- .../Escher/DgContainer/SpgrContainer.php | 75 -- .../DgContainer/SpgrContainer/SpContainer.php | 369 ---------- .../Shared/Escher/DggContainer.php | 175 ----- .../Escher/DggContainer/BstoreContainer.php | 32 - .../DggContainer/BstoreContainer/BSE.php | 88 --- .../DggContainer/BstoreContainer/BSE/Blip.php | 58 -- .../src/PhpSpreadsheet/Shared/File.php | 203 ------ .../src/PhpSpreadsheet/Shared/Font.php | 675 ----------------- .../src/PhpSpreadsheet/Shared/IntOrFloat.php | 21 - .../PhpSpreadsheet/Shared/PasswordHasher.php | 109 --- .../PhpSpreadsheet/Shared/StringHelper.php | 684 ------------------ .../src/PhpSpreadsheet/Shared/TimeZone.php | 77 -- .../PhpSpreadsheet/Shared/Trend/BestFit.php | 501 ------------- .../Shared/Trend/ExponentialBestFit.php | 119 --- .../Shared/Trend/LinearBestFit.php | 80 -- .../Shared/Trend/LogarithmicBestFit.php | 87 --- .../Shared/Trend/PolynomialBestFit.php | 219 ------ .../Shared/Trend/PowerBestFit.php | 109 --- .../src/PhpSpreadsheet/Shared/Trend/Trend.php | 130 ---- .../src/PhpSpreadsheet/Shared/XMLWriter.php | 104 --- 556 files changed, 37 insertions(+), 6651 deletions(-) rename lib/phpspreadsheet/{vendor => }/markbaker/complex/README.md (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/complex/classes/src/Complex.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/complex/classes/src/Exception.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/complex/classes/src/Functions.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/complex/classes/src/Operations.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/complex/composer.json (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/complex/examples/complexTest.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/complex/examples/testFunctions.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/complex/examples/testOperations.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/complex/license.md (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/README.md (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/buildPhar.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Builder.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Decomposition/Decomposition.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Decomposition/LU.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Decomposition/QR.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Div0Exception.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Exception.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Functions.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Matrix.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Operations.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Operators/Addition.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Operators/DirectSum.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Operators/Division.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Operators/Multiplication.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Operators/Operator.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/classes/src/Operators/Subtraction.php (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/composer.json (100%) rename lib/phpspreadsheet/{vendor => }/markbaker/matrix/license.md (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/CHANGELOG.md (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/CONTRIBUTING.md (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/LICENSE (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/README.md (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/composer.json (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/phpunit10.xml.dist (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/ArrayEnabled.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/BinaryComparison.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Category.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DAverage.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCount.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCountA.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DGet.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMax.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMin.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DProduct.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDev.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDevP.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DSum.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVar.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVarP.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTime.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/BranchPruner.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/Operand.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselI.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselK.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselY.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Compare.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Constants.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Erf.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Amortization.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Constants.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Coupons.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Depreciation.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Dollar.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/FinancialValidations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Helpers.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaParser.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaToken.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Functions.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ErrorValue.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ExcelError.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/Value.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Boolean.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Conditional.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Operations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Address.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Filter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Formula.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupRefValidations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Selection.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Sort.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Unique.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Absolute.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Angle.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Base.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Exp.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Random.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Roman.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Round.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sign.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Secant.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Confidence.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Counts.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Deviations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StudentT.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Maximum.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Percentiles.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Size.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Standardize.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Trends.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Variances.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Helpers.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Replace.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Search.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Text.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Trim.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Token/Stack.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Web.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/Web/Service.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/Translations.xlsx (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressRange.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/CellAddress.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/CellRange.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/ColumnRange.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/IgnoredErrors.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/RowRange.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/CellReferenceHelper.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/ChartColor.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/GridLines.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Chart/TrendLine.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache3.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Comment.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/DefinedName.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Exception.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/HashTable.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Helper/Dimension.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Helper/Downloader.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Helper/Handler.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Helper/Size.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Helper/TextGrid.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/IComparable.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/IOFactory.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv/Delimiter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Exception.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/IReader.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/BaseLoader.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/PageSettings.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/Properties.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/DataValidations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Border.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/RichText/RichText.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/RichText/Run.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Settings.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellStyleAssessor.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/StyleMerger.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Blanks.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/DateValue.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardInterface.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Accounting.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Currency.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Date.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTime.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTimeWizard.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Duration.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Locale.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Number.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/NumberBase.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Percentage.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Scientific.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Time.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Wizard.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/Protection.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/RgbTint.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Theme.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFit.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageBreak.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/Column.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/TableStyle.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Validations.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/AutoFilters.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Formula.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/WriterPart.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Table.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream0.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream2.php (100%) rename lib/phpspreadsheet/{vendor/phpoffice => }/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream3.php (100%) delete mode 100644 lib/phpspreadsheet/vendor/composer/ClassLoader.php delete mode 100644 lib/phpspreadsheet/vendor/composer/InstalledVersions.php delete mode 100644 lib/phpspreadsheet/vendor/composer/LICENSE delete mode 100644 lib/phpspreadsheet/vendor/composer/autoload_classmap.php delete mode 100644 lib/phpspreadsheet/vendor/composer/autoload_namespaces.php delete mode 100644 lib/phpspreadsheet/vendor/composer/autoload_psr4.php delete mode 100644 lib/phpspreadsheet/vendor/composer/autoload_real.php delete mode 100644 lib/phpspreadsheet/vendor/composer/autoload_static.php delete mode 100644 lib/phpspreadsheet/vendor/composer/installed.json delete mode 100644 lib/phpspreadsheet/vendor/composer/installed.php delete mode 100644 lib/phpspreadsheet/vendor/composer/platform_check.php delete mode 100644 lib/phpspreadsheet/vendor/markbaker/matrix/examples/test.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Drawing.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php delete mode 100644 lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php diff --git a/lib/classes/component.php b/lib/classes/component.php index be3e7b2e321..beeba5b815a 100644 --- a/lib/classes/component.php +++ b/lib/classes/component.php @@ -141,6 +141,9 @@ class component { \JmesPath::class => 'lib/jmespath/src', \Laravel\SerializableClosure::class => 'lib/laravel/serializable-closure/src', \DI::class => 'lib/php-di/php-di/src', + \Complex::class => 'lib/phpspreadsheet/markbaker/classes/src', + \Matrix::class => 'lib/phpspreadsheet/markbaker/classes/src', + \PhpOffice\PhpSpreadsheet::class => 'lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet', \Invoker::class => 'lib/php-di/invoker/src', ]; diff --git a/lib/excellib.class.php b/lib/excellib.class.php index 62bda64bffe..3899b0aeb27 100644 --- a/lib/excellib.class.php +++ b/lib/excellib.class.php @@ -24,20 +24,18 @@ defined('MOODLE_INTERNAL') || die(); -require_once("$CFG->libdir/phpspreadsheet/vendor/autoload.php"); - -use \PhpOffice\PhpSpreadsheet\Spreadsheet; -use \PhpOffice\PhpSpreadsheet\IOFactory; -use \PhpOffice\PhpSpreadsheet\Cell\Coordinate; -use \PhpOffice\PhpSpreadsheet\Cell\DataType; -use \PhpOffice\PhpSpreadsheet\Shared\Date; -use \PhpOffice\PhpSpreadsheet\Style\Alignment; -use \PhpOffice\PhpSpreadsheet\Style\Border; -use \PhpOffice\PhpSpreadsheet\Style\Fill; -use \PhpOffice\PhpSpreadsheet\Style\Font; -use \PhpOffice\PhpSpreadsheet\Style\NumberFormat; -use \PhpOffice\PhpSpreadsheet\Worksheet\Drawing; -use \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\IOFactory; +use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Cell\DataType; +use PhpOffice\PhpSpreadsheet\Shared\Date; +use PhpOffice\PhpSpreadsheet\Style\Alignment; +use PhpOffice\PhpSpreadsheet\Style\Border; +use PhpOffice\PhpSpreadsheet\Style\Fill; +use PhpOffice\PhpSpreadsheet\Style\Font; +use PhpOffice\PhpSpreadsheet\Style\NumberFormat; +use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; /** * Define and operate over one Moodle Workbook. diff --git a/lib/phpspreadsheet/vendor/markbaker/complex/README.md b/lib/phpspreadsheet/markbaker/complex/README.md similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/complex/README.md rename to lib/phpspreadsheet/markbaker/complex/README.md diff --git a/lib/phpspreadsheet/vendor/markbaker/complex/classes/src/Complex.php b/lib/phpspreadsheet/markbaker/complex/classes/src/Complex.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/complex/classes/src/Complex.php rename to lib/phpspreadsheet/markbaker/complex/classes/src/Complex.php diff --git a/lib/phpspreadsheet/vendor/markbaker/complex/classes/src/Exception.php b/lib/phpspreadsheet/markbaker/complex/classes/src/Exception.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/complex/classes/src/Exception.php rename to lib/phpspreadsheet/markbaker/complex/classes/src/Exception.php diff --git a/lib/phpspreadsheet/vendor/markbaker/complex/classes/src/Functions.php b/lib/phpspreadsheet/markbaker/complex/classes/src/Functions.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/complex/classes/src/Functions.php rename to lib/phpspreadsheet/markbaker/complex/classes/src/Functions.php diff --git a/lib/phpspreadsheet/vendor/markbaker/complex/classes/src/Operations.php b/lib/phpspreadsheet/markbaker/complex/classes/src/Operations.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/complex/classes/src/Operations.php rename to lib/phpspreadsheet/markbaker/complex/classes/src/Operations.php diff --git a/lib/phpspreadsheet/vendor/markbaker/complex/composer.json b/lib/phpspreadsheet/markbaker/complex/composer.json similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/complex/composer.json rename to lib/phpspreadsheet/markbaker/complex/composer.json diff --git a/lib/phpspreadsheet/vendor/markbaker/complex/examples/complexTest.php b/lib/phpspreadsheet/markbaker/complex/examples/complexTest.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/complex/examples/complexTest.php rename to lib/phpspreadsheet/markbaker/complex/examples/complexTest.php diff --git a/lib/phpspreadsheet/vendor/markbaker/complex/examples/testFunctions.php b/lib/phpspreadsheet/markbaker/complex/examples/testFunctions.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/complex/examples/testFunctions.php rename to lib/phpspreadsheet/markbaker/complex/examples/testFunctions.php diff --git a/lib/phpspreadsheet/vendor/markbaker/complex/examples/testOperations.php b/lib/phpspreadsheet/markbaker/complex/examples/testOperations.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/complex/examples/testOperations.php rename to lib/phpspreadsheet/markbaker/complex/examples/testOperations.php diff --git a/lib/phpspreadsheet/vendor/markbaker/complex/license.md b/lib/phpspreadsheet/markbaker/complex/license.md similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/complex/license.md rename to lib/phpspreadsheet/markbaker/complex/license.md diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/README.md b/lib/phpspreadsheet/markbaker/matrix/README.md similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/README.md rename to lib/phpspreadsheet/markbaker/matrix/README.md diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/buildPhar.php b/lib/phpspreadsheet/markbaker/matrix/buildPhar.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/buildPhar.php rename to lib/phpspreadsheet/markbaker/matrix/buildPhar.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Builder.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Builder.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Builder.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Builder.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Decomposition/Decomposition.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Decomposition/Decomposition.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Decomposition/Decomposition.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Decomposition/Decomposition.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Decomposition/LU.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Decomposition/LU.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Decomposition/LU.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Decomposition/LU.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Decomposition/QR.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Decomposition/QR.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Decomposition/QR.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Decomposition/QR.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Div0Exception.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Div0Exception.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Div0Exception.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Div0Exception.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Exception.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Exception.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Exception.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Exception.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Functions.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Functions.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Functions.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Functions.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Matrix.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Matrix.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Matrix.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Matrix.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Operations.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Operations.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Operations.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Operations.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Operators/Addition.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Operators/Addition.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Operators/Addition.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Operators/Addition.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Operators/DirectSum.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Operators/DirectSum.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Operators/DirectSum.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Operators/DirectSum.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Operators/Division.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Operators/Division.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Operators/Division.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Operators/Division.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Operators/Multiplication.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Operators/Multiplication.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Operators/Multiplication.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Operators/Multiplication.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Operators/Operator.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Operators/Operator.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Operators/Operator.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Operators/Operator.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Operators/Subtraction.php b/lib/phpspreadsheet/markbaker/matrix/classes/src/Operators/Subtraction.php similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/classes/src/Operators/Subtraction.php rename to lib/phpspreadsheet/markbaker/matrix/classes/src/Operators/Subtraction.php diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/composer.json b/lib/phpspreadsheet/markbaker/matrix/composer.json similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/composer.json rename to lib/phpspreadsheet/markbaker/matrix/composer.json diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/license.md b/lib/phpspreadsheet/markbaker/matrix/license.md similarity index 100% rename from lib/phpspreadsheet/vendor/markbaker/matrix/license.md rename to lib/phpspreadsheet/markbaker/matrix/license.md diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/CHANGELOG.md b/lib/phpspreadsheet/phpspreadsheet/CHANGELOG.md similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/CHANGELOG.md rename to lib/phpspreadsheet/phpspreadsheet/CHANGELOG.md diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/CONTRIBUTING.md b/lib/phpspreadsheet/phpspreadsheet/CONTRIBUTING.md similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/CONTRIBUTING.md rename to lib/phpspreadsheet/phpspreadsheet/CONTRIBUTING.md diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/LICENSE b/lib/phpspreadsheet/phpspreadsheet/LICENSE similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/LICENSE rename to lib/phpspreadsheet/phpspreadsheet/LICENSE diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/README.md b/lib/phpspreadsheet/phpspreadsheet/README.md similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/README.md rename to lib/phpspreadsheet/phpspreadsheet/README.md diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/composer.json b/lib/phpspreadsheet/phpspreadsheet/composer.json similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/composer.json rename to lib/phpspreadsheet/phpspreadsheet/composer.json diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/phpunit10.xml.dist b/lib/phpspreadsheet/phpspreadsheet/phpunit10.xml.dist similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/phpunit10.xml.dist rename to lib/phpspreadsheet/phpspreadsheet/phpunit10.xml.dist diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ArrayEnabled.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/ArrayEnabled.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ArrayEnabled.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/ArrayEnabled.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/BinaryComparison.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/BinaryComparison.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/BinaryComparison.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/BinaryComparison.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Category.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Category.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Category.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Category.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DAverage.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DAverage.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DAverage.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DAverage.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCount.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCount.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCount.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCount.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCountA.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCountA.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCountA.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCountA.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DGet.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DGet.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DGet.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DGet.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMax.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMax.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMax.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMax.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMin.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMin.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMin.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMin.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DProduct.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DProduct.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DProduct.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DProduct.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDev.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDev.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDev.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDev.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDevP.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDevP.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDevP.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDevP.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DSum.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DSum.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DSum.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DSum.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVar.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVar.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVar.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVar.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVarP.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVarP.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVarP.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVarP.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTime.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTime.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTime.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTime.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/BranchPruner.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/BranchPruner.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/BranchPruner.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/BranchPruner.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/Operand.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/Operand.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/Operand.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/Operand.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselI.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselI.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselI.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselI.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselK.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselK.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselK.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselK.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselY.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselY.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselY.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselY.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Compare.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Compare.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Compare.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Compare.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Constants.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Constants.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Constants.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Constants.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Erf.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Erf.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Erf.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Erf.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Amortization.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Amortization.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Amortization.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Amortization.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Constants.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Constants.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Constants.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Constants.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Coupons.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Coupons.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Coupons.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Coupons.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Depreciation.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Depreciation.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Depreciation.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Depreciation.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Dollar.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Dollar.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Dollar.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Dollar.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/FinancialValidations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/FinancialValidations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/FinancialValidations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/FinancialValidations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Helpers.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Helpers.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Helpers.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Helpers.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaParser.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaParser.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaParser.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaParser.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaToken.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaToken.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaToken.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaToken.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Functions.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Functions.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Functions.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Functions.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ErrorValue.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ErrorValue.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ErrorValue.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ErrorValue.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ExcelError.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ExcelError.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ExcelError.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/ExcelError.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/Value.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/Value.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/Value.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Information/Value.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Boolean.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Boolean.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Boolean.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Boolean.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Conditional.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Conditional.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Conditional.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Conditional.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Operations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Operations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Operations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Logical/Operations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Address.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Address.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Address.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Address.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Filter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Filter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Filter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Filter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Formula.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Formula.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Formula.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Formula.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupRefValidations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupRefValidations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupRefValidations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupRefValidations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Matrix.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Selection.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Selection.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Selection.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Selection.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Sort.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Sort.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Sort.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Sort.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Unique.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Unique.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Unique.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Unique.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Absolute.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Absolute.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Absolute.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Absolute.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Angle.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Angle.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Angle.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Angle.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Arabic.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Base.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Base.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Base.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Base.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Combinations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Exp.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Exp.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Exp.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Exp.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Random.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Random.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Random.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Random.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Roman.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Roman.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Roman.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Roman.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Round.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Round.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Round.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Round.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sign.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sign.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sign.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sign.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Secant.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Secant.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Secant.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Secant.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Trunc.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Conditional.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Confidence.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Confidence.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Confidence.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Confidence.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Counts.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Counts.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Counts.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Counts.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Deviations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Deviations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Deviations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Deviations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StudentT.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StudentT.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StudentT.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/StudentT.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Maximum.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Maximum.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Maximum.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Maximum.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Percentiles.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Percentiles.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Percentiles.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Percentiles.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Permutations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Size.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Size.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Size.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Size.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Standardize.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Standardize.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Standardize.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Standardize.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Trends.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Trends.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Trends.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Trends.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Variances.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Variances.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Variances.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Variances.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/CaseConvert.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Concatenate.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Helpers.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Helpers.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Helpers.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Helpers.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Replace.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Replace.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Replace.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Replace.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Search.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Search.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Search.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Search.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Text.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Text.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Text.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Text.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Trim.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Trim.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Trim.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Trim.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Token/Stack.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Token/Stack.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Token/Stack.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Token/Stack.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Web.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Web.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Web.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Web.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Web/Service.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Web/Service.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Web/Service.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/Web/Service.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/Translations.xlsx b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/Translations.xlsx similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/Translations.xlsx rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/Translations.xlsx diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/bg/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/cs/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/da/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/de/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/en/uk/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/es/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fi/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/fr/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/hu/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/it/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nb/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/nl/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pl/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/br/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/pt/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/ru/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/sv/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/config diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Calculation/locale/tr/functions diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressHelper.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressRange.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressRange.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressRange.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/AddressRange.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/CellAddress.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/CellAddress.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/CellAddress.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/CellAddress.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/CellRange.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/CellRange.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/CellRange.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/CellRange.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/ColumnRange.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/ColumnRange.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/ColumnRange.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/ColumnRange.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/DataType.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidation.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/DefaultValueBinder.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/Hyperlink.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IgnoredErrors.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/IgnoredErrors.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IgnoredErrors.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/IgnoredErrors.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/RowRange.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/RowRange.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/RowRange.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/RowRange.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Cell/StringValueBinder.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/CellReferenceHelper.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/CellReferenceHelper.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/CellReferenceHelper.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/CellReferenceHelper.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/ChartColor.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/ChartColor.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/ChartColor.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/ChartColor.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeries.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/DataSeriesValues.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Exception.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/GridLines.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/GridLines.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/GridLines.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/GridLines.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Properties.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraph.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/Title.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/TrendLine.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/TrendLine.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/TrendLine.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Chart/TrendLine.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache3.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache3.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache3.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache3.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Comment.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Comment.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Comment.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Comment.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/DefinedName.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/DefinedName.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/DefinedName.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/DefinedName.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Exception.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Exception.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/HashTable.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/HashTable.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/HashTable.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/HashTable.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Dimension.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Helper/Dimension.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Dimension.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Helper/Dimension.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Downloader.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Helper/Downloader.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Downloader.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Helper/Downloader.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Handler.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Helper/Handler.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Handler.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Helper/Handler.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Size.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Helper/Size.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Size.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Helper/Size.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/TextGrid.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Helper/TextGrid.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/TextGrid.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Helper/TextGrid.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/IComparable.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/IComparable.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IOFactory.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/IOFactory.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IOFactory.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/IOFactory.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv/Delimiter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv/Delimiter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv/Delimiter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv/Delimiter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/DefaultReadFilter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Exception.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Exception.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Exception.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Exception.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Properties.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/Styles.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReader.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/IReader.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReader.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/IReader.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/AutoFilter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/BaseLoader.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/BaseLoader.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/BaseLoader.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/BaseLoader.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/PageSettings.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/PageSettings.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/PageSettings.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/PageSettings.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/Properties.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/Properties.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/Properties.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/Properties.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Slk.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/PageSetup.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/DataValidations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/DataValidations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/DataValidations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/DataValidations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Alignment.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Border.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Border.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Border.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Border.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/RichText.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/RichText/RichText.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/RichText.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/RichText/RichText.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/Run.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/RichText/Run.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/Run.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/RichText/Run.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Settings.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Settings.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Settings.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Settings.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellStyleAssessor.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellStyleAssessor.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellStyleAssessor.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellStyleAssessor.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/StyleMerger.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/StyleMerger.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/StyleMerger.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/StyleMerger.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Blanks.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Blanks.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Blanks.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Blanks.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/DateValue.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/DateValue.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/DateValue.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/DateValue.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardInterface.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardInterface.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardInterface.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardInterface.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Accounting.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Accounting.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Accounting.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Accounting.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Currency.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Currency.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Currency.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Currency.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Date.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Date.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Date.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Date.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTime.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTime.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTime.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTime.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTimeWizard.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTimeWizard.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTimeWizard.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTimeWizard.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Duration.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Duration.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Duration.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Duration.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Locale.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Locale.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Locale.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Locale.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Number.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Number.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Number.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Number.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/NumberBase.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/NumberBase.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/NumberBase.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/NumberBase.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Percentage.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Percentage.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Percentage.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Percentage.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Scientific.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Scientific.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Scientific.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Scientific.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Time.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Time.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Time.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Time.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Wizard.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Wizard.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Wizard.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Wizard.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Protection.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Protection.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Protection.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Protection.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/RgbTint.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/RgbTint.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/RgbTint.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/RgbTint.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Theme.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Theme.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Theme.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Theme.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFit.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFit.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFit.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFit.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageBreak.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageBreak.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageBreak.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageBreak.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/Column.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/Column.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/Column.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/Column.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/TableStyle.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/TableStyle.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/TableStyle.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/TableStyle.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Validations.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Validations.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Validations.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Validations.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/AutoFilters.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/AutoFilters.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/AutoFilters.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/AutoFilters.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Formula.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Formula.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Formula.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Formula.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/WriterPart.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/WriterPart.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/WriterPart.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/WriterPart.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Table.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Table.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Table.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Table.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream0.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream0.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream0.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream0.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream2.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream2.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream2.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream2.php diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream3.php b/lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream3.php similarity index 100% rename from lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream3.php rename to lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream3.php diff --git a/lib/phpspreadsheet/vendor/autoload.php b/lib/phpspreadsheet/vendor/autoload.php index 887dca7fcad..84d63ac8f2f 100644 --- a/lib/phpspreadsheet/vendor/autoload.php +++ b/lib/phpspreadsheet/vendor/autoload.php @@ -1,25 +1,25 @@ . -// autoload.php @generated by Composer +/** + * Legacy autoloader for phpspreadsheet. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ -if (PHP_VERSION_ID < 50600) { - if (!headers_sent()) { - header('HTTP/1.1 500 Internal Server Error'); - } - $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; - if (!ini_get('display_errors')) { - if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { - fwrite(STDERR, $err); - } elseif (!headers_sent()) { - echo $err; - } - } - trigger_error( - $err, - E_USER_ERROR - ); -} - -require_once __DIR__ . '/composer/autoload_real.php'; - -return ComposerAutoloaderInitf14832faa9ea8f0ad137e596f5daa06a::getLoader(); +// TODO MDL-82653 This file is deprecated. A message to this effect will be added in Moodle 5.0. diff --git a/lib/phpspreadsheet/vendor/composer/ClassLoader.php b/lib/phpspreadsheet/vendor/composer/ClassLoader.php deleted file mode 100644 index a72151c77c8..00000000000 --- a/lib/phpspreadsheet/vendor/composer/ClassLoader.php +++ /dev/null @@ -1,585 +0,0 @@ - - * 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 https://www.php-fig.org/psr/psr-0/ - * @see https://www.php-fig.org/psr/psr-4/ - */ -class ClassLoader -{ - /** @var \Closure(string):void */ - private static $includeFile; - - /** @var ?string */ - private $vendorDir; - - // PSR-4 - /** - * @var array[] - * @psalm-var array> - */ - private $prefixLengthsPsr4 = array(); - /** - * @var array[] - * @psalm-var array> - */ - private $prefixDirsPsr4 = array(); - /** - * @var array[] - * @psalm-var array - */ - private $fallbackDirsPsr4 = array(); - - // PSR-0 - /** - * @var array[] - * @psalm-var array> - */ - private $prefixesPsr0 = array(); - /** - * @var array[] - * @psalm-var array - */ - private $fallbackDirsPsr0 = array(); - - /** @var bool */ - private $useIncludePath = false; - - /** - * @var string[] - * @psalm-var array - */ - private $classMap = array(); - - /** @var bool */ - private $classMapAuthoritative = false; - - /** - * @var bool[] - * @psalm-var array - */ - private $missingClasses = array(); - - /** @var ?string */ - private $apcuPrefix; - - /** - * @var self[] - */ - private static $registeredLoaders = array(); - - /** - * @param ?string $vendorDir - */ - public function __construct($vendorDir = null) - { - $this->vendorDir = $vendorDir; - self::initializeIncludeClosure(); - } - - /** - * @return string[] - */ - public function getPrefixes() - { - if (!empty($this->prefixesPsr0)) { - return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); - } - - return array(); - } - - /** - * @return array[] - * @psalm-return array> - */ - public function getPrefixesPsr4() - { - return $this->prefixDirsPsr4; - } - - /** - * @return array[] - * @psalm-return array - */ - public function getFallbackDirs() - { - return $this->fallbackDirsPsr0; - } - - /** - * @return array[] - * @psalm-return array - */ - public function getFallbackDirsPsr4() - { - return $this->fallbackDirsPsr4; - } - - /** - * @return string[] Array of classname => path - * @psalm-return array - */ - public function getClassMap() - { - return $this->classMap; - } - - /** - * @param string[] $classMap Class to filename map - * @psalm-param array $classMap - * - * @return void - */ - 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 string[]|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories - * - * @return void - */ - 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 string[]|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories - * - * @throws \InvalidArgumentException - * - * @return void - */ - 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 string[]|string $paths The PSR-0 base directories - * - * @return void - */ - 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 string[]|string $paths The PSR-4 base directories - * - * @throws \InvalidArgumentException - * - * @return void - */ - 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 - * - * @return void - */ - 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 - * - * @return void - */ - 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 - * - * @return void - */ - public function setApcuPrefix($apcuPrefix) - { - $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $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 - * - * @return void - */ - public function register($prepend = false) - { - spl_autoload_register(array($this, 'loadClass'), true, $prepend); - - if (null === $this->vendorDir) { - return; - } - - if ($prepend) { - self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; - } else { - unset(self::$registeredLoaders[$this->vendorDir]); - self::$registeredLoaders[$this->vendorDir] = $this; - } - } - - /** - * Unregisters this instance as an autoloader. - * - * @return void - */ - public function unregister() - { - spl_autoload_unregister(array($this, 'loadClass')); - - if (null !== $this->vendorDir) { - unset(self::$registeredLoaders[$this->vendorDir]); - } - } - - /** - * Loads the given class or interface. - * - * @param string $class The name of the class - * @return true|null True if loaded, null otherwise - */ - public function loadClass($class) - { - if ($file = $this->findFile($class)) { - $includeFile = self::$includeFile; - $includeFile($file); - - return true; - } - - return null; - } - - /** - * 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; - } - - /** - * Returns the currently registered loaders indexed by their corresponding vendor directories. - * - * @return self[] - */ - public static function getRegisteredLoaders() - { - return self::$registeredLoaders; - } - - /** - * @param string $class - * @param string $ext - * @return string|false - */ - 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])) { - $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); - foreach ($this->prefixDirsPsr4[$search] as $dir) { - if (file_exists($file = $dir . $pathEnd)) { - 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; - } - - /** - * @return void - */ - private static function initializeIncludeClosure() - { - if (self::$includeFile !== null) { - return; - } - - /** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - * - * @param string $file - * @return void - */ - self::$includeFile = \Closure::bind(static function($file) { - include $file; - }, null, null); - } -} diff --git a/lib/phpspreadsheet/vendor/composer/InstalledVersions.php b/lib/phpspreadsheet/vendor/composer/InstalledVersions.php deleted file mode 100644 index 51e734a774b..00000000000 --- a/lib/phpspreadsheet/vendor/composer/InstalledVersions.php +++ /dev/null @@ -1,359 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer; - -use Composer\Autoload\ClassLoader; -use Composer\Semver\VersionParser; - -/** - * This class is copied in every Composer installed project and available to all - * - * See also https://getcomposer.org/doc/07-runtime.md#installed-versions - * - * To require its presence, you can require `composer-runtime-api ^2.0` - * - * @final - */ -class InstalledVersions -{ - /** - * @var mixed[]|null - * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null - */ - private static $installed; - - /** - * @var bool|null - */ - private static $canGetVendors; - - /** - * @var array[] - * @psalm-var array}> - */ - private static $installedByVendor = array(); - - /** - * Returns a list of all package names which are present, either by being installed, replaced or provided - * - * @return string[] - * @psalm-return list - */ - public static function getInstalledPackages() - { - $packages = array(); - foreach (self::getInstalled() as $installed) { - $packages[] = array_keys($installed['versions']); - } - - if (1 === \count($packages)) { - return $packages[0]; - } - - return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); - } - - /** - * Returns a list of all package names with a specific type e.g. 'library' - * - * @param string $type - * @return string[] - * @psalm-return list - */ - public static function getInstalledPackagesByType($type) - { - $packagesByType = array(); - - foreach (self::getInstalled() as $installed) { - foreach ($installed['versions'] as $name => $package) { - if (isset($package['type']) && $package['type'] === $type) { - $packagesByType[] = $name; - } - } - } - - return $packagesByType; - } - - /** - * Checks whether the given package is installed - * - * This also returns true if the package name is provided or replaced by another package - * - * @param string $packageName - * @param bool $includeDevRequirements - * @return bool - */ - public static function isInstalled($packageName, $includeDevRequirements = true) - { - foreach (self::getInstalled() as $installed) { - if (isset($installed['versions'][$packageName])) { - return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; - } - } - - return false; - } - - /** - * Checks whether the given package satisfies a version constraint - * - * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: - * - * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') - * - * @param VersionParser $parser Install composer/semver to have access to this class and functionality - * @param string $packageName - * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package - * @return bool - */ - public static function satisfies(VersionParser $parser, $packageName, $constraint) - { - $constraint = $parser->parseConstraints((string) $constraint); - $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); - - return $provided->matches($constraint); - } - - /** - * Returns a version constraint representing all the range(s) which are installed for a given package - * - * It is easier to use this via isInstalled() with the $constraint argument if you need to check - * whether a given version of a package is installed, and not just whether it exists - * - * @param string $packageName - * @return string Version constraint usable with composer/semver - */ - public static function getVersionRanges($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - $ranges = array(); - if (isset($installed['versions'][$packageName]['pretty_version'])) { - $ranges[] = $installed['versions'][$packageName]['pretty_version']; - } - if (array_key_exists('aliases', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); - } - if (array_key_exists('replaced', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); - } - if (array_key_exists('provided', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); - } - - return implode(' || ', $ranges); - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present - */ - public static function getVersion($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['version'])) { - return null; - } - - return $installed['versions'][$packageName]['version']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present - */ - public static function getPrettyVersion($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['pretty_version'])) { - return null; - } - - return $installed['versions'][$packageName]['pretty_version']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference - */ - public static function getReference($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['reference'])) { - return null; - } - - return $installed['versions'][$packageName]['reference']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. - */ - public static function getInstallPath($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @return array - * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} - */ - public static function getRootPackage() - { - $installed = self::getInstalled(); - - return $installed[0]['root']; - } - - /** - * Returns the raw installed.php data for custom implementations - * - * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. - * @return array[] - * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} - */ - public static function getRawData() - { - @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); - - if (null === self::$installed) { - // only require the installed.php file if this file is loaded from its dumped location, - // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 - if (substr(__DIR__, -8, 1) !== 'C') { - self::$installed = include __DIR__ . '/installed.php'; - } else { - self::$installed = array(); - } - } - - return self::$installed; - } - - /** - * Returns the raw data of all installed.php which are currently loaded for custom implementations - * - * @return array[] - * @psalm-return list}> - */ - public static function getAllRawData() - { - return self::getInstalled(); - } - - /** - * Lets you reload the static array from another file - * - * This is only useful for complex integrations in which a project needs to use - * this class but then also needs to execute another project's autoloader in process, - * and wants to ensure both projects have access to their version of installed.php. - * - * A typical case would be PHPUnit, where it would need to make sure it reads all - * the data it needs from this class, then call reload() with - * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure - * the project in which it runs can then also use this class safely, without - * interference between PHPUnit's dependencies and the project's dependencies. - * - * @param array[] $data A vendor/composer/installed.php data set - * @return void - * - * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data - */ - public static function reload($data) - { - self::$installed = $data; - self::$installedByVendor = array(); - } - - /** - * @return array[] - * @psalm-return list}> - */ - private static function getInstalled() - { - if (null === self::$canGetVendors) { - self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); - } - - $installed = array(); - - if (self::$canGetVendors) { - foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { - if (isset(self::$installedByVendor[$vendorDir])) { - $installed[] = self::$installedByVendor[$vendorDir]; - } elseif (is_file($vendorDir.'/composer/installed.php')) { - /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ - $required = require $vendorDir.'/composer/installed.php'; - $installed[] = self::$installedByVendor[$vendorDir] = $required; - if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { - self::$installed = $installed[count($installed) - 1]; - } - } - } - } - - if (null === self::$installed) { - // only require the installed.php file if this file is loaded from its dumped location, - // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 - if (substr(__DIR__, -8, 1) !== 'C') { - /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ - $required = require __DIR__ . '/installed.php'; - self::$installed = $required; - } else { - self::$installed = array(); - } - } - - if (self::$installed !== array()) { - $installed[] = self::$installed; - } - - return $installed; - } -} diff --git a/lib/phpspreadsheet/vendor/composer/LICENSE b/lib/phpspreadsheet/vendor/composer/LICENSE deleted file mode 100644 index f27399a042d..00000000000 --- a/lib/phpspreadsheet/vendor/composer/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - -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/lib/phpspreadsheet/vendor/composer/autoload_classmap.php b/lib/phpspreadsheet/vendor/composer/autoload_classmap.php deleted file mode 100644 index 0fb0a2c194b..00000000000 --- a/lib/phpspreadsheet/vendor/composer/autoload_classmap.php +++ /dev/null @@ -1,10 +0,0 @@ - $vendorDir . '/composer/InstalledVersions.php', -); diff --git a/lib/phpspreadsheet/vendor/composer/autoload_namespaces.php b/lib/phpspreadsheet/vendor/composer/autoload_namespaces.php deleted file mode 100644 index 15a2ff3ad6d..00000000000 --- a/lib/phpspreadsheet/vendor/composer/autoload_namespaces.php +++ /dev/null @@ -1,9 +0,0 @@ - array($vendorDir . '/psr/simple-cache/src'), - 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'), - 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), - 'PhpOffice\\PhpSpreadsheet\\' => array($vendorDir . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet'), - 'Matrix\\' => array($vendorDir . '/markbaker/matrix/classes/src'), - 'Complex\\' => array($vendorDir . '/markbaker/complex/classes/src'), -); diff --git a/lib/phpspreadsheet/vendor/composer/autoload_real.php b/lib/phpspreadsheet/vendor/composer/autoload_real.php deleted file mode 100644 index d51084dc0ec..00000000000 --- a/lib/phpspreadsheet/vendor/composer/autoload_real.php +++ /dev/null @@ -1,38 +0,0 @@ -register(true); - - return $loader; - } -} diff --git a/lib/phpspreadsheet/vendor/composer/autoload_static.php b/lib/phpspreadsheet/vendor/composer/autoload_static.php deleted file mode 100644 index e90be9b6b69..00000000000 --- a/lib/phpspreadsheet/vendor/composer/autoload_static.php +++ /dev/null @@ -1,68 +0,0 @@ - - array ( - 'Psr\\SimpleCache\\' => 16, - 'Psr\\Http\\Message\\' => 17, - 'Psr\\Http\\Client\\' => 16, - 'PhpOffice\\PhpSpreadsheet\\' => 25, - ), - 'M' => - array ( - 'Matrix\\' => 7, - ), - 'C' => - array ( - 'Complex\\' => 8, - ), - ); - - public static $prefixDirsPsr4 = array ( - 'Psr\\SimpleCache\\' => - array ( - 0 => __DIR__ . '/..' . '/psr/simple-cache/src', - ), - 'Psr\\Http\\Message\\' => - array ( - 0 => __DIR__ . '/..' . '/psr/http-message/src', - 1 => __DIR__ . '/..' . '/psr/http-factory/src', - ), - 'Psr\\Http\\Client\\' => - array ( - 0 => __DIR__ . '/..' . '/psr/http-client/src', - ), - 'PhpOffice\\PhpSpreadsheet\\' => - array ( - 0 => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet', - ), - 'Matrix\\' => - array ( - 0 => __DIR__ . '/..' . '/markbaker/matrix/classes/src', - ), - 'Complex\\' => - array ( - 0 => __DIR__ . '/..' . '/markbaker/complex/classes/src', - ), - ); - - public static $classMap = array ( - 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', - ); - - public static function getInitializer(ClassLoader $loader) - { - return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInitf14832faa9ea8f0ad137e596f5daa06a::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInitf14832faa9ea8f0ad137e596f5daa06a::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInitf14832faa9ea8f0ad137e596f5daa06a::$classMap; - - }, null, ClassLoader::class); - } -} diff --git a/lib/phpspreadsheet/vendor/composer/installed.json b/lib/phpspreadsheet/vendor/composer/installed.json deleted file mode 100644 index f37a4542a31..00000000000 --- a/lib/phpspreadsheet/vendor/composer/installed.json +++ /dev/null @@ -1,450 +0,0 @@ -{ - "packages": [ - { - "name": "markbaker/complex", - "version": "3.0.2", - "version_normalized": "3.0.2.0", - "source": { - "type": "git", - "url": "https://github.com/MarkBaker/PHPComplex.git", - "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", - "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "dev-master", - "phpcompatibility/php-compatibility": "^9.3", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "squizlabs/php_codesniffer": "^3.7" - }, - "time": "2022-12-06T16:21:08+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "Complex\\": "classes/src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mark Baker", - "email": "mark@lange.demon.co.uk" - } - ], - "description": "PHP Class for working with complex numbers", - "homepage": "https://github.com/MarkBaker/PHPComplex", - "keywords": [ - "complex", - "mathematics" - ], - "support": { - "issues": "https://github.com/MarkBaker/PHPComplex/issues", - "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" - }, - "install-path": "../markbaker/complex" - }, - { - "name": "markbaker/matrix", - "version": "3.0.1", - "version_normalized": "3.0.1.0", - "source": { - "type": "git", - "url": "https://github.com/MarkBaker/PHPMatrix.git", - "reference": "728434227fe21be27ff6d86621a1b13107a2562c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", - "reference": "728434227fe21be27ff6d86621a1b13107a2562c", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "dev-master", - "phpcompatibility/php-compatibility": "^9.3", - "phpdocumentor/phpdocumentor": "2.*", - "phploc/phploc": "^4.0", - "phpmd/phpmd": "2.*", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", - "sebastian/phpcpd": "^4.0", - "squizlabs/php_codesniffer": "^3.7" - }, - "time": "2022-12-02T22:17:43+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "Matrix\\": "classes/src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mark Baker", - "email": "mark@demon-angel.eu" - } - ], - "description": "PHP Class for working with matrices", - "homepage": "https://github.com/MarkBaker/PHPMatrix", - "keywords": [ - "mathematics", - "matrix", - "vector" - ], - "support": { - "issues": "https://github.com/MarkBaker/PHPMatrix/issues", - "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" - }, - "install-path": "../markbaker/matrix" - }, - { - "name": "phpoffice/phpspreadsheet", - "version": "1.29.0", - "version_normalized": "1.29.0.0", - "source": { - "type": "git", - "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", - "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fde2ccf55eaef7e86021ff1acce26479160a0fa0", - "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-dom": "*", - "ext-fileinfo": "*", - "ext-gd": "*", - "ext-iconv": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "ext-xml": "*", - "ext-xmlreader": "*", - "ext-xmlwriter": "*", - "ext-zip": "*", - "ext-zlib": "*", - "ezyang/htmlpurifier": "^4.15", - "maennchen/zipstream-php": "^2.1 || ^3.0", - "markbaker/complex": "^3.0", - "markbaker/matrix": "^3.0", - "php": "^7.4 || ^8.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", - "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "dev-main", - "dompdf/dompdf": "^1.0 || ^2.0", - "friendsofphp/php-cs-fixer": "^3.2", - "mitoteam/jpgraph": "^10.3", - "mpdf/mpdf": "^8.1.1", - "phpcompatibility/php-compatibility": "^9.3", - "phpstan/phpstan": "^1.1", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^8.5 || ^9.0 || ^10.0", - "squizlabs/php_codesniffer": "^3.7", - "tecnickcom/tcpdf": "^6.5" - }, - "suggest": { - "dompdf/dompdf": "Option for rendering PDF with PDF Writer", - "ext-intl": "PHP Internationalization Functions", - "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", - "mpdf/mpdf": "Option for rendering PDF with PDF Writer", - "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" - }, - "time": "2023-06-14T22:48:31+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Maarten Balliauw", - "homepage": "https://blog.maartenballiauw.be" - }, - { - "name": "Mark Baker", - "homepage": "https://markbakeruk.net" - }, - { - "name": "Franck Lefevre", - "homepage": "https://rootslabs.net" - }, - { - "name": "Erik Tilt" - }, - { - "name": "Adrien Crivelli" - } - ], - "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", - "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", - "keywords": [ - "OpenXML", - "excel", - "gnumeric", - "ods", - "php", - "spreadsheet", - "xls", - "xlsx" - ], - "support": { - "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", - "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.0" - }, - "install-path": "../phpoffice/phpspreadsheet" - }, - { - "name": "psr/http-client", - "version": "1.0.2", - "version_normalized": "1.0.2.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-client.git", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", - "shasum": "" - }, - "require": { - "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "time": "2023-04-10T20:12:12+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Psr\\Http\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP clients", - "homepage": "https://github.com/php-fig/http-client", - "keywords": [ - "http", - "http-client", - "psr", - "psr-18" - ], - "support": { - "source": "https://github.com/php-fig/http-client/tree/1.0.2" - }, - "install-path": "../psr/http-client" - }, - { - "name": "psr/http-factory", - "version": "1.0.2", - "version_normalized": "1.0.2.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", - "shasum": "" - }, - "require": { - "php": ">=7.0.0", - "psr/http-message": "^1.0 || ^2.0" - }, - "time": "2023-04-10T20:10:41+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interfaces for PSR-7 HTTP message factories", - "keywords": [ - "factory", - "http", - "message", - "psr", - "psr-17", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-factory/tree/1.0.2" - }, - "install-path": "../psr/http-factory" - }, - { - "name": "psr/http-message", - "version": "2.0", - "version_normalized": "2.0.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "time": "2023-04-04T09:54:51+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Psr\\Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", - "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" - ], - "support": { - "source": "https://github.com/php-fig/http-message/tree/2.0" - }, - "install-path": "../psr/http-message" - }, - { - "name": "psr/simple-cache", - "version": "3.0.0", - "version_normalized": "3.0.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", - "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "time": "2021-10-29T13:26:27+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Psr\\SimpleCache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interfaces for simple caching", - "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" - ], - "support": { - "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" - }, - "install-path": "../psr/simple-cache" - } - ], - "dev": true, - "dev-package-names": [] -} diff --git a/lib/phpspreadsheet/vendor/composer/installed.php b/lib/phpspreadsheet/vendor/composer/installed.php deleted file mode 100644 index e8b22835b1d..00000000000 --- a/lib/phpspreadsheet/vendor/composer/installed.php +++ /dev/null @@ -1,110 +0,0 @@ - array( - 'name' => '__root__', - 'pretty_version' => '1.0.0+no-version-set', - 'version' => '1.0.0.0', - 'reference' => NULL, - 'type' => 'library', - 'install_path' => __DIR__ . '/../../', - 'aliases' => array(), - 'dev' => true, - ), - 'versions' => array( - '__root__' => array( - 'pretty_version' => '1.0.0+no-version-set', - 'version' => '1.0.0.0', - 'reference' => NULL, - 'type' => 'library', - 'install_path' => __DIR__ . '/../../', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'ezyang/htmlpurifier' => array( - 'dev_requirement' => false, - 'replaced' => array( - 0 => '*', - ), - ), - 'maennchen/zipstream-php' => array( - 'dev_requirement' => false, - 'replaced' => array( - 0 => '*', - ), - ), - 'markbaker/complex' => array( - 'pretty_version' => '3.0.2', - 'version' => '3.0.2.0', - 'reference' => '95c56caa1cf5c766ad6d65b6344b807c1e8405b9', - 'type' => 'library', - 'install_path' => __DIR__ . '/../markbaker/complex', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'markbaker/matrix' => array( - 'pretty_version' => '3.0.1', - 'version' => '3.0.1.0', - 'reference' => '728434227fe21be27ff6d86621a1b13107a2562c', - 'type' => 'library', - 'install_path' => __DIR__ . '/../markbaker/matrix', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'myclabs/php-enum' => array( - 'dev_requirement' => false, - 'replaced' => array( - 0 => '*', - ), - ), - 'phpoffice/phpspreadsheet' => array( - 'pretty_version' => '1.29.0', - 'version' => '1.29.0.0', - 'reference' => 'fde2ccf55eaef7e86021ff1acce26479160a0fa0', - 'type' => 'library', - 'install_path' => __DIR__ . '/../phpoffice/phpspreadsheet', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'psr/http-client' => array( - 'pretty_version' => '1.0.2', - 'version' => '1.0.2.0', - 'reference' => '0955afe48220520692d2d09f7ab7e0f93ffd6a31', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/http-client', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'psr/http-factory' => array( - 'pretty_version' => '1.0.2', - 'version' => '1.0.2.0', - 'reference' => 'e616d01114759c4c489f93b099585439f795fe35', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/http-factory', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'psr/http-message' => array( - 'pretty_version' => '2.0', - 'version' => '2.0.0.0', - 'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/http-message', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'psr/simple-cache' => array( - 'pretty_version' => '3.0.0', - 'version' => '3.0.0.0', - 'reference' => '764e0b3939f5ca87cb904f570ef9be2d78a07865', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/simple-cache', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'symfony/polyfill-mbstring' => array( - 'dev_requirement' => false, - 'replaced' => array( - 0 => '*', - ), - ), - ), -); diff --git a/lib/phpspreadsheet/vendor/composer/platform_check.php b/lib/phpspreadsheet/vendor/composer/platform_check.php deleted file mode 100644 index adfb472fbdd..00000000000 --- a/lib/phpspreadsheet/vendor/composer/platform_check.php +++ /dev/null @@ -1,26 +0,0 @@ -= 80000)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.0". You are running ' . PHP_VERSION . '.'; -} - -if ($issues) { - if (!headers_sent()) { - header('HTTP/1.1 500 Internal Server Error'); - } - if (!ini_get('display_errors')) { - if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { - fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); - } elseif (!headers_sent()) { - echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; - } - } - trigger_error( - 'Composer detected issues in your platform: ' . implode(' ', $issues), - E_USER_ERROR - ); -} diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/examples/test.php b/lib/phpspreadsheet/vendor/markbaker/matrix/examples/test.php deleted file mode 100644 index 071dae910c0..00000000000 --- a/lib/phpspreadsheet/vendor/markbaker/matrix/examples/test.php +++ /dev/null @@ -1,33 +0,0 @@ -solve($target); - -echo 'X', PHP_EOL; -var_export($X->toArray()); -echo PHP_EOL; - -$resolve = $matrix->multiply($X); - -echo 'Resolve', PHP_EOL; -var_export($resolve->toArray()); -echo PHP_EOL; diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php deleted file mode 100644 index 8718a6135ed..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/CodePage.php +++ /dev/null @@ -1,114 +0,0 @@ - 'CP1252', // CodePage is not always correctly set when the xls file was saved by Apple's Numbers program - 367 => 'ASCII', // ASCII - 437 => 'CP437', // OEM US - //720 => 'notsupported', // OEM Arabic - 737 => 'CP737', // OEM Greek - 775 => 'CP775', // OEM Baltic - 850 => 'CP850', // OEM Latin I - 852 => 'CP852', // OEM Latin II (Central European) - 855 => 'CP855', // OEM Cyrillic - 857 => 'CP857', // OEM Turkish - 858 => 'CP858', // OEM Multilingual Latin I with Euro - 860 => 'CP860', // OEM Portugese - 861 => 'CP861', // OEM Icelandic - 862 => 'CP862', // OEM Hebrew - 863 => 'CP863', // OEM Canadian (French) - 864 => 'CP864', // OEM Arabic - 865 => 'CP865', // OEM Nordic - 866 => 'CP866', // OEM Cyrillic (Russian) - 869 => 'CP869', // OEM Greek (Modern) - 874 => 'CP874', // ANSI Thai - 932 => 'CP932', // ANSI Japanese Shift-JIS - 936 => 'CP936', // ANSI Chinese Simplified GBK - 949 => 'CP949', // ANSI Korean (Wansung) - 950 => 'CP950', // ANSI Chinese Traditional BIG5 - 1200 => 'UTF-16LE', // UTF-16 (BIFF8) - 1250 => 'CP1250', // ANSI Latin II (Central European) - 1251 => 'CP1251', // ANSI Cyrillic - 1252 => 'CP1252', // ANSI Latin I (BIFF4-BIFF7) - 1253 => 'CP1253', // ANSI Greek - 1254 => 'CP1254', // ANSI Turkish - 1255 => 'CP1255', // ANSI Hebrew - 1256 => 'CP1256', // ANSI Arabic - 1257 => 'CP1257', // ANSI Baltic - 1258 => 'CP1258', // ANSI Vietnamese - 1361 => 'CP1361', // ANSI Korean (Johab) - 10000 => 'MAC', // Apple Roman - 10001 => 'CP932', // Macintosh Japanese - 10002 => 'CP950', // Macintosh Chinese Traditional - 10003 => 'CP1361', // Macintosh Korean - 10004 => 'MACARABIC', // Apple Arabic - 10005 => 'MACHEBREW', // Apple Hebrew - 10006 => 'MACGREEK', // Macintosh Greek - 10007 => 'MACCYRILLIC', // Macintosh Cyrillic - 10008 => 'CP936', // Macintosh - Simplified Chinese (GB 2312) - 10010 => 'MACROMANIA', // Macintosh Romania - 10017 => 'MACUKRAINE', // Macintosh Ukraine - 10021 => 'MACTHAI', // Macintosh Thai - 10029 => ['MACCENTRALEUROPE', 'MAC-CENTRALEUROPE'], // Macintosh Central Europe - 10079 => 'MACICELAND', // Macintosh Icelandic - 10081 => 'MACTURKISH', // Macintosh Turkish - 10082 => 'MACCROATIAN', // Macintosh Croatian - 21010 => 'UTF-16LE', // UTF-16 (BIFF8) This isn't correct, but some Excel writer libraries erroneously use Codepage 21010 for UTF-16LE - 32768 => 'MAC', // Apple Roman - //32769 => 'unsupported', // ANSI Latin I (BIFF2-BIFF3) - 65000 => 'UTF-7', // Unicode (UTF-7) - 65001 => 'UTF-8', // Unicode (UTF-8) - 99999 => ['unsupported'], // Unicode (UTF-8) - ]; - - public static function validate(string $codePage): bool - { - return in_array($codePage, self::$pageArray, true); - } - - /** - * Convert Microsoft Code Page Identifier to Code Page Name which iconv - * and mbstring understands. - * - * @param int $codePage Microsoft Code Page Indentifier - * - * @return string Code Page Name - */ - public static function numberToName(int $codePage): string - { - if (array_key_exists($codePage, self::$pageArray)) { - $value = self::$pageArray[$codePage]; - if (is_array($value)) { - foreach ($value as $encoding) { - if (@iconv('UTF-8', $encoding, ' ') !== false) { - self::$pageArray[$codePage] = $encoding; - - return $encoding; - } - } - - throw new PhpSpreadsheetException("Code page $codePage not implemented on this system."); - } else { - return $value; - } - } - if ($codePage == 720 || $codePage == 32769) { - throw new PhpSpreadsheetException("Code page $codePage not supported."); // OEM Arabic - } - - throw new PhpSpreadsheetException('Unknown codepage: ' . $codePage); - } - - public static function getEncodings(): array - { - return self::$pageArray; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php deleted file mode 100644 index 4f196731131..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php +++ /dev/null @@ -1,556 +0,0 @@ - 'January', - 'Feb' => 'February', - 'Mar' => 'March', - 'Apr' => 'April', - 'May' => 'May', - 'Jun' => 'June', - 'Jul' => 'July', - 'Aug' => 'August', - 'Sep' => 'September', - 'Oct' => 'October', - 'Nov' => 'November', - 'Dec' => 'December', - ]; - - /** - * @var string[] - */ - public static $numberSuffixes = [ - 'st', - 'nd', - 'rd', - 'th', - ]; - - /** - * Base calendar year to use for calculations - * Value is either CALENDAR_WINDOWS_1900 (1900) or CALENDAR_MAC_1904 (1904). - * - * @var int - */ - protected static $excelCalendar = self::CALENDAR_WINDOWS_1900; - - /** - * Default timezone to use for DateTime objects. - * - * @var null|DateTimeZone - */ - protected static $defaultTimeZone; - - /** - * Set the Excel calendar (Windows 1900 or Mac 1904). - * - * @param int $baseYear Excel base date (1900 or 1904) - * - * @return bool Success or failure - */ - public static function setExcelCalendar($baseYear) - { - if ( - ($baseYear == self::CALENDAR_WINDOWS_1900) || - ($baseYear == self::CALENDAR_MAC_1904) - ) { - self::$excelCalendar = $baseYear; - - return true; - } - - return false; - } - - /** - * Return the Excel calendar (Windows 1900 or Mac 1904). - * - * @return int Excel base date (1900 or 1904) - */ - public static function getExcelCalendar() - { - return self::$excelCalendar; - } - - /** - * Set the Default timezone to use for dates. - * - * @param null|DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions - * - * @return bool Success or failure - */ - public static function setDefaultTimezone($timeZone) - { - try { - $timeZone = self::validateTimeZone($timeZone); - self::$defaultTimeZone = $timeZone; - $retval = true; - } catch (PhpSpreadsheetException $e) { - $retval = false; - } - - return $retval; - } - - /** - * Return the Default timezone, or UTC if default not set. - */ - public static function getDefaultTimezone(): DateTimeZone - { - return self::$defaultTimeZone ?? new DateTimeZone('UTC'); - } - - /** - * Return the Default timezone, or local timezone if default is not set. - */ - public static function getDefaultOrLocalTimezone(): DateTimeZone - { - return self::$defaultTimeZone ?? new DateTimeZone(date_default_timezone_get()); - } - - /** - * Return the Default timezone even if null. - */ - public static function getDefaultTimezoneOrNull(): ?DateTimeZone - { - return self::$defaultTimeZone; - } - - /** - * Validate a timezone. - * - * @param null|DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object - * - * @return ?DateTimeZone The timezone as a timezone object - */ - private static function validateTimeZone($timeZone) - { - if ($timeZone instanceof DateTimeZone || $timeZone === null) { - return $timeZone; - } - if (in_array($timeZone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC))) { - return new DateTimeZone($timeZone); - } - - throw new PhpSpreadsheetException('Invalid timezone'); - } - - /** - * @param mixed $value Converts a date/time in ISO-8601 standard format date string to an Excel - * serialized timestamp. - * See https://en.wikipedia.org/wiki/ISO_8601 for details of the ISO-8601 standard format. - * - * @return float|int - */ - public static function convertIsoDate($value) - { - if (!is_string($value)) { - throw new Exception('Non-string value supplied for Iso Date conversion'); - } - - $date = new DateTime($value); - $dateErrors = DateTime::getLastErrors(); - - if (is_array($dateErrors) && ($dateErrors['warning_count'] > 0 || $dateErrors['error_count'] > 0)) { - throw new Exception("Invalid string $value supplied for datatype Date"); - } - - $newValue = SharedDate::PHPToExcel($date); - if ($newValue === false) { - throw new Exception("Invalid string $value supplied for datatype Date"); - } - - if (preg_match('/^\\s*\\d?\\d:\\d\\d(:\\d\\d([.]\\d+)?)?\\s*(am|pm)?\\s*$/i', $value) == 1) { - $newValue = fmod($newValue, 1.0); - } - - return $newValue; - } - - /** - * Convert a MS serialized datetime value from Excel to a PHP Date/Time object. - * - * @param float|int $excelTimestamp MS Excel serialized date/time value - * @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp, - * if you don't want to treat it as a UTC value - * Use the default (UTC) unless you absolutely need a conversion - * - * @return DateTime PHP date/time object - */ - public static function excelToDateTimeObject($excelTimestamp, $timeZone = null) - { - $timeZone = ($timeZone === null) ? self::getDefaultTimezone() : self::validateTimeZone($timeZone); - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { - if ($excelTimestamp < 1 && self::$excelCalendar === self::CALENDAR_WINDOWS_1900) { - // Unix timestamp base date - $baseDate = new DateTime('1970-01-01', $timeZone); - } else { - // MS Excel calendar base dates - if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) { - // Allow adjustment for 1900 Leap Year in MS Excel - $baseDate = ($excelTimestamp < 60) ? new DateTime('1899-12-31', $timeZone) : new DateTime('1899-12-30', $timeZone); - } else { - $baseDate = new DateTime('1904-01-01', $timeZone); - } - } - } else { - $baseDate = new DateTime('1899-12-30', $timeZone); - } - - $days = floor($excelTimestamp); - $partDay = $excelTimestamp - $days; - $hours = floor($partDay * 24); - $partDay = $partDay * 24 - $hours; - $minutes = floor($partDay * 60); - $partDay = $partDay * 60 - $minutes; - $seconds = round($partDay * 60); - - if ($days >= 0) { - $days = '+' . $days; - } - $interval = $days . ' days'; - - return $baseDate->modify($interval) - ->setTime((int) $hours, (int) $minutes, (int) $seconds); - } - - /** - * Convert a MS serialized datetime value from Excel to a unix timestamp. - * The use of Unix timestamps, and therefore this function, is discouraged. - * They are not Y2038-safe on a 32-bit system, and have no timezone info. - * - * @param float|int $excelTimestamp MS Excel serialized date/time value - * @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp, - * if you don't want to treat it as a UTC value - * Use the default (UTC) unless you absolutely need a conversion - * - * @return int Unix timetamp for this date/time - */ - public static function excelToTimestamp($excelTimestamp, $timeZone = null) - { - return (int) self::excelToDateTimeObject($excelTimestamp, $timeZone) - ->format('U'); - } - - /** - * Convert a date from PHP to an MS Excel serialized date/time value. - * - * @param mixed $dateValue PHP DateTime object or a string - Unix timestamp is also permitted, but discouraged; - * not Y2038-safe on a 32-bit system, and no timezone info - * - * @return false|float Excel date/time value - * or boolean FALSE on failure - */ - public static function PHPToExcel($dateValue) - { - if ((is_object($dateValue)) && ($dateValue instanceof DateTimeInterface)) { - return self::dateTimeToExcel($dateValue); - } elseif (is_numeric($dateValue)) { - return self::timestampToExcel($dateValue); - } elseif (is_string($dateValue)) { - return self::stringToExcel($dateValue); - } - - return false; - } - - /** - * Convert a PHP DateTime object to an MS Excel serialized date/time value. - * - * @param DateTimeInterface $dateValue PHP DateTime object - * - * @return float MS Excel serialized date/time value - */ - public static function dateTimeToExcel(DateTimeInterface $dateValue) - { - return self::formattedPHPToExcel( - (int) $dateValue->format('Y'), - (int) $dateValue->format('m'), - (int) $dateValue->format('d'), - (int) $dateValue->format('H'), - (int) $dateValue->format('i'), - (int) $dateValue->format('s') - ); - } - - /** - * Convert a Unix timestamp to an MS Excel serialized date/time value. - * The use of Unix timestamps, and therefore this function, is discouraged. - * They are not Y2038-safe on a 32-bit system, and have no timezone info. - * - * @param float|int|string $unixTimestamp Unix Timestamp - * - * @return false|float MS Excel serialized date/time value - */ - public static function timestampToExcel($unixTimestamp) - { - if (!is_numeric($unixTimestamp)) { - return false; - } - - return self::dateTimeToExcel(new DateTime('@' . $unixTimestamp)); - } - - /** - * formattedPHPToExcel. - * - * @param int $year - * @param int $month - * @param int $day - * @param int $hours - * @param int $minutes - * @param int $seconds - * - * @return float Excel date/time value - */ - public static function formattedPHPToExcel($year, $month, $day, $hours = 0, $minutes = 0, $seconds = 0) - { - if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) { - // - // Fudge factor for the erroneous fact that the year 1900 is treated as a Leap Year in MS Excel - // This affects every date following 28th February 1900 - // - $excel1900isLeapYear = true; - if (($year == 1900) && ($month <= 2)) { - $excel1900isLeapYear = false; - } - $myexcelBaseDate = 2415020; - } else { - $myexcelBaseDate = 2416481; - $excel1900isLeapYear = false; - } - - // Julian base date Adjustment - if ($month > 2) { - $month -= 3; - } else { - $month += 9; - --$year; - } - - // Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0) - $century = (int) substr((string) $year, 0, 2); - $decade = (int) substr((string) $year, 2, 2); - $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myexcelBaseDate + $excel1900isLeapYear; - - $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400; - - return (float) $excelDate + $excelTime; - } - - /** - * Is a given cell a date/time? - * - * @param mixed $value - * - * @return bool - */ - public static function isDateTime(Cell $cell, $value = null, bool $dateWithoutTimeOkay = true) - { - $result = false; - $worksheet = $cell->getWorksheetOrNull(); - $spreadsheet = ($worksheet === null) ? null : $worksheet->getParent(); - if ($worksheet !== null && $spreadsheet !== null) { - $index = $spreadsheet->getActiveSheetIndex(); - $selected = $worksheet->getSelectedCells(); - - try { - $result = is_numeric($value ?? $cell->getCalculatedValue()) && - self::isDateTimeFormat( - $worksheet->getStyle( - $cell->getCoordinate() - )->getNumberFormat(), - $dateWithoutTimeOkay - ); - } catch (Exception $e) { - // Result is already false, so no need to actually do anything here - } - $worksheet->setSelectedCells($selected); - $spreadsheet->setActiveSheetIndex($index); - } - - return $result; - } - - /** - * Is a given NumberFormat code a date/time format code? - * - * @return bool - */ - public static function isDateTimeFormat(NumberFormat $excelFormatCode, bool $dateWithoutTimeOkay = true) - { - return self::isDateTimeFormatCode((string) $excelFormatCode->getFormatCode(), $dateWithoutTimeOkay); - } - - private const POSSIBLE_DATETIME_FORMAT_CHARACTERS = 'eymdHs'; - private const POSSIBLE_TIME_FORMAT_CHARACTERS = 'Hs'; // note - no 'm' due to ambiguity - - /** - * Is a given number format code a date/time? - * - * @param string $excelFormatCode - * - * @return bool - */ - public static function isDateTimeFormatCode($excelFormatCode, bool $dateWithoutTimeOkay = true) - { - if (strtolower($excelFormatCode) === strtolower(NumberFormat::FORMAT_GENERAL)) { - // "General" contains an epoch letter 'e', so we trap for it explicitly here (case-insensitive check) - return false; - } - if (preg_match('/[0#]E[+-]0/i', $excelFormatCode)) { - // Scientific format - return false; - } - - // Switch on formatcode - if (in_array($excelFormatCode, NumberFormat::DATE_TIME_OR_DATETIME_ARRAY, true)) { - return $dateWithoutTimeOkay || in_array($excelFormatCode, NumberFormat::TIME_OR_DATETIME_ARRAY); - } - - // Typically number, currency or accounting (or occasionally fraction) formats - if ((substr($excelFormatCode, 0, 1) == '_') || (substr($excelFormatCode, 0, 2) == '0 ')) { - return false; - } - // Some "special formats" provided in German Excel versions were detected as date time value, - // so filter them out here - "\C\H\-00000" (Switzerland) and "\D-00000" (Germany). - if (\strpos($excelFormatCode, '-00000') !== false) { - return false; - } - $possibleFormatCharacters = $dateWithoutTimeOkay ? self::POSSIBLE_DATETIME_FORMAT_CHARACTERS : self::POSSIBLE_TIME_FORMAT_CHARACTERS; - // Try checking for any of the date formatting characters that don't appear within square braces - if (preg_match('/(^|\])[^\[]*[' . $possibleFormatCharacters . ']/i', $excelFormatCode)) { - // We might also have a format mask containing quoted strings... - // we don't want to test for any of our characters within the quoted blocks - if (strpos($excelFormatCode, '"') !== false) { - $segMatcher = false; - foreach (explode('"', $excelFormatCode) as $subVal) { - // Only test in alternate array entries (the non-quoted blocks) - $segMatcher = $segMatcher === false; - if ( - $segMatcher && - (preg_match('/(^|\])[^\[]*[' . $possibleFormatCharacters . ']/i', $subVal)) - ) { - return true; - } - } - - return false; - } - - return true; - } - - // No date... - return false; - } - - /** - * Convert a date/time string to Excel time. - * - * @param string $dateValue Examples: '2009-12-31', '2009-12-31 15:59', '2009-12-31 15:59:10' - * - * @return false|float Excel date/time serial value - */ - public static function stringToExcel($dateValue) - { - if (strlen($dateValue) < 2) { - return false; - } - if (!preg_match('/^(\d{1,4}[ \.\/\-][A-Z]{3,9}([ \.\/\-]\d{1,4})?|[A-Z]{3,9}[ \.\/\-]\d{1,4}([ \.\/\-]\d{1,4})?|\d{1,4}[ \.\/\-]\d{1,4}([ \.\/\-]\d{1,4})?)( \d{1,2}:\d{1,2}(:\d{1,2})?)?$/iu', $dateValue)) { - return false; - } - - $dateValueNew = DateTimeExcel\DateValue::fromString($dateValue); - - if (!is_float($dateValueNew)) { - return false; - } - - if (strpos($dateValue, ':') !== false) { - $timeValue = DateTimeExcel\TimeValue::fromString($dateValue); - if (!is_float($timeValue)) { - return false; - } - $dateValueNew += $timeValue; - } - - return $dateValueNew; - } - - /** - * Converts a month name (either a long or a short name) to a month number. - * - * @param string $monthName Month name or abbreviation - * - * @return int|string Month number (1 - 12), or the original string argument if it isn't a valid month name - */ - public static function monthStringToNumber($monthName) - { - $monthIndex = 1; - foreach (self::$monthNames as $shortMonthName => $longMonthName) { - if (($monthName === $longMonthName) || ($monthName === $shortMonthName)) { - return $monthIndex; - } - ++$monthIndex; - } - - return $monthName; - } - - /** - * Strips an ordinal from a numeric value. - * - * @param string $day Day number with an ordinal - * - * @return int|string The integer value with any ordinal stripped, or the original string argument if it isn't a valid numeric - */ - public static function dayStringToNumber($day) - { - $strippedDayValue = (str_replace(self::$numberSuffixes, '', $day)); - if (is_numeric($strippedDayValue)) { - return (int) $strippedDayValue; - } - - return $day; - } - - public static function dateTimeFromTimestamp(string $date, ?DateTimeZone $timeZone = null): DateTime - { - $dtobj = DateTime::createFromFormat('U', $date) ?: new DateTime(); - $dtobj->setTimeZone($timeZone ?? self::getDefaultOrLocalTimezone()); - - return $dtobj; - } - - public static function formattedDateTimeFromTimestamp(string $date, string $format, ?DateTimeZone $timeZone = null): string - { - $dtobj = self::dateTimeFromTimestamp($date, $timeZone); - - return $dtobj->format($format); - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Drawing.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Drawing.php deleted file mode 100644 index f69310fc617..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Drawing.php +++ /dev/null @@ -1,177 +0,0 @@ -getName(); - $size = $defaultFont->getSize(); - - if (isset(Font::$defaultColumnWidths[$name][$size])) { - // Exact width can be determined - return $pixelValue * Font::$defaultColumnWidths[$name][$size]['width'] - / Font::$defaultColumnWidths[$name][$size]['px']; - } - - // We don't have data for this particular font and size, use approximation by - // extrapolating from Calibri 11 - return $pixelValue * 11 * Font::$defaultColumnWidths['Calibri'][11]['width'] - / Font::$defaultColumnWidths['Calibri'][11]['px'] / $size; - } - - /** - * Convert column width from (intrinsic) Excel units to pixels. - * - * @param float $cellWidth Value in cell dimension - * @param \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont Default font of the workbook - * - * @return int Value in pixels - */ - public static function cellDimensionToPixels($cellWidth, \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont) - { - // Font name and size - $name = $defaultFont->getName(); - $size = $defaultFont->getSize(); - - if (isset(Font::$defaultColumnWidths[$name][$size])) { - // Exact width can be determined - $colWidth = $cellWidth * Font::$defaultColumnWidths[$name][$size]['px'] - / Font::$defaultColumnWidths[$name][$size]['width']; - } else { - // We don't have data for this particular font and size, use approximation by - // extrapolating from Calibri 11 - $colWidth = $cellWidth * $size * Font::$defaultColumnWidths['Calibri'][11]['px'] - / Font::$defaultColumnWidths['Calibri'][11]['width'] / 11; - } - - // Round pixels to closest integer - $colWidth = (int) round($colWidth); - - return $colWidth; - } - - /** - * Convert pixels to points. - * - * @param int $pixelValue Value in pixels - * - * @return float Value in points - */ - public static function pixelsToPoints($pixelValue) - { - return $pixelValue * 0.75; - } - - /** - * Convert points to pixels. - * - * @param int $pointValue Value in points - * - * @return int Value in pixels - */ - public static function pointsToPixels($pointValue) - { - if ($pointValue != 0) { - return (int) ceil($pointValue / 0.75); - } - - return 0; - } - - /** - * Convert degrees to angle. - * - * @param int $degrees Degrees - * - * @return int Angle - */ - public static function degreesToAngle($degrees) - { - return (int) round($degrees * 60000); - } - - /** - * Convert angle to degrees. - * - * @param int|SimpleXMLElement $angle Angle - * - * @return int Degrees - */ - public static function angleToDegrees($angle) - { - $angle = (int) $angle; - if ($angle != 0) { - return (int) round($angle / 60000); - } - - return 0; - } - - /** - * Create a new image from file. By alexander at alexauto dot nl. - * - * @see http://www.php.net/manual/en/function.imagecreatefromwbmp.php#86214 - * - * @param string $bmpFilename Path to Windows DIB (BMP) image - * - * @return GdImage|resource - * - * @deprecated 1.26 use Php function imagecreatefrombmp instead - * - * @codeCoverageIgnore - */ - public static function imagecreatefrombmp($bmpFilename) - { - $retVal = @imagecreatefrombmp($bmpFilename); - if ($retVal === false) { - throw new ReaderException("Unable to create image from $bmpFilename"); - } - - return $retVal; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php deleted file mode 100644 index 466e7e8254c..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php +++ /dev/null @@ -1,64 +0,0 @@ -dggContainer; - } - - /** - * Set Drawing Group Container. - * - * @param Escher\DggContainer $dggContainer - * - * @return Escher\DggContainer - */ - public function setDggContainer($dggContainer) - { - return $this->dggContainer = $dggContainer; - } - - /** - * Get Drawing Container. - * - * @return ?Escher\DgContainer - */ - public function getDgContainer() - { - return $this->dgContainer; - } - - /** - * Set Drawing Container. - * - * @param Escher\DgContainer $dgContainer - * - * @return Escher\DgContainer - */ - public function setDgContainer($dgContainer) - { - return $this->dgContainer = $dgContainer; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php deleted file mode 100644 index 51c6860cbe7..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer.php +++ /dev/null @@ -1,65 +0,0 @@ -dgId; - } - - public function setDgId(int $value): void - { - $this->dgId = $value; - } - - public function getLastSpId(): ?int - { - return $this->lastSpId; - } - - public function setLastSpId(int $value): void - { - $this->lastSpId = $value; - } - - public function getSpgrContainer(): ?DgContainer\SpgrContainer - { - return $this->spgrContainer; - } - - public function getSpgrContainerOrThrow(): DgContainer\SpgrContainer - { - if ($this->spgrContainer !== null) { - return $this->spgrContainer; - } - - throw new SpreadsheetException('spgrContainer is unexpectedly null'); - } - - /** @param DgContainer\SpgrContainer $spgrContainer */ - public function setSpgrContainer($spgrContainer): DgContainer\SpgrContainer - { - return $this->spgrContainer = $spgrContainer; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php deleted file mode 100644 index 260df9cd4c0..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php +++ /dev/null @@ -1,75 +0,0 @@ -parent = $parent; - } - - /** - * Get the parent Shape Group Container if any. - */ - public function getParent(): ?self - { - return $this->parent; - } - - /** - * Add a child. This will be either spgrContainer or spContainer. - * - * @param mixed $child - */ - public function addChild($child): void - { - $this->children[] = $child; - $child->setParent($this); - } - - /** - * Get collection of Shape Containers. - */ - public function getChildren(): array - { - return $this->children; - } - - /** - * Recursively get all spContainers within this spgrContainer. - * - * @return SpgrContainer\SpContainer[] - */ - public function getAllSpContainers() - { - $allSpContainers = []; - - foreach ($this->children as $child) { - if ($child instanceof self) { - $allSpContainers = array_merge($allSpContainers, $child->getAllSpContainers()); - } else { - $allSpContainers[] = $child; - } - } - - return $allSpContainers; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php deleted file mode 100644 index 8a81ff57974..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php +++ /dev/null @@ -1,369 +0,0 @@ -parent = $parent; - } - - /** - * Get the parent Shape Group Container. - * - * @return SpgrContainer - */ - public function getParent() - { - return $this->parent; - } - - /** - * Set whether this is a group shape. - * - * @param bool $value - */ - public function setSpgr($value): void - { - $this->spgr = $value; - } - - /** - * Get whether this is a group shape. - * - * @return bool - */ - public function getSpgr() - { - return $this->spgr; - } - - /** - * Set the shape type. - * - * @param int $value - */ - public function setSpType($value): void - { - $this->spType = $value; - } - - /** - * Get the shape type. - * - * @return int - */ - public function getSpType() - { - return $this->spType; - } - - /** - * Set the shape flag. - * - * @param int $value - */ - public function setSpFlag($value): void - { - $this->spFlag = $value; - } - - /** - * Get the shape flag. - * - * @return int - */ - public function getSpFlag() - { - return $this->spFlag; - } - - /** - * Set the shape index. - * - * @param int $value - */ - public function setSpId($value): void - { - $this->spId = $value; - } - - /** - * Get the shape index. - * - * @return int - */ - public function getSpId() - { - return $this->spId; - } - - /** - * Set an option for the Shape Group Container. - * - * @param int $property The number specifies the option - * @param mixed $value - */ - public function setOPT($property, $value): void - { - $this->OPT[$property] = $value; - } - - /** - * Get an option for the Shape Group Container. - * - * @param int $property The number specifies the option - * - * @return mixed - */ - public function getOPT($property) - { - if (isset($this->OPT[$property])) { - return $this->OPT[$property]; - } - - return null; - } - - /** - * Get the collection of options. - * - * @return array - */ - public function getOPTCollection() - { - return $this->OPT; - } - - /** - * Set cell coordinates of upper-left corner of shape. - * - * @param string $value eg: 'A1' - */ - public function setStartCoordinates($value): void - { - $this->startCoordinates = $value; - } - - /** - * Get cell coordinates of upper-left corner of shape. - * - * @return string - */ - public function getStartCoordinates() - { - return $this->startCoordinates; - } - - /** - * Set offset in x-direction of upper-left corner of shape measured in 1/1024 of column width. - * - * @param int $startOffsetX - */ - public function setStartOffsetX($startOffsetX): void - { - $this->startOffsetX = $startOffsetX; - } - - /** - * Get offset in x-direction of upper-left corner of shape measured in 1/1024 of column width. - * - * @return int - */ - public function getStartOffsetX() - { - return $this->startOffsetX; - } - - /** - * Set offset in y-direction of upper-left corner of shape measured in 1/256 of row height. - * - * @param int $startOffsetY - */ - public function setStartOffsetY($startOffsetY): void - { - $this->startOffsetY = $startOffsetY; - } - - /** - * Get offset in y-direction of upper-left corner of shape measured in 1/256 of row height. - * - * @return int - */ - public function getStartOffsetY() - { - return $this->startOffsetY; - } - - /** - * Set cell coordinates of bottom-right corner of shape. - * - * @param string $value eg: 'A1' - */ - public function setEndCoordinates($value): void - { - $this->endCoordinates = $value; - } - - /** - * Get cell coordinates of bottom-right corner of shape. - * - * @return string - */ - public function getEndCoordinates() - { - return $this->endCoordinates; - } - - /** - * Set offset in x-direction of bottom-right corner of shape measured in 1/1024 of column width. - * - * @param int $endOffsetX - */ - public function setEndOffsetX($endOffsetX): void - { - $this->endOffsetX = $endOffsetX; - } - - /** - * Get offset in x-direction of bottom-right corner of shape measured in 1/1024 of column width. - * - * @return int - */ - public function getEndOffsetX() - { - return $this->endOffsetX; - } - - /** - * Set offset in y-direction of bottom-right corner of shape measured in 1/256 of row height. - * - * @param int $endOffsetY - */ - public function setEndOffsetY($endOffsetY): void - { - $this->endOffsetY = $endOffsetY; - } - - /** - * Get offset in y-direction of bottom-right corner of shape measured in 1/256 of row height. - * - * @return int - */ - public function getEndOffsetY() - { - return $this->endOffsetY; - } - - /** - * Get the nesting level of this spContainer. This is the number of spgrContainers between this spContainer and - * the dgContainer. A value of 1 = immediately within first spgrContainer - * Higher nesting level occurs if and only if spContainer is part of a shape group. - * - * @return int Nesting level - */ - public function getNestingLevel() - { - $nestingLevel = 0; - - $parent = $this->getParent(); - while ($parent instanceof SpgrContainer) { - ++$nestingLevel; - $parent = $parent->getParent(); - } - - return $nestingLevel; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php deleted file mode 100644 index ba5e7980b29..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer.php +++ /dev/null @@ -1,175 +0,0 @@ -spIdMax; - } - - /** - * Set maximum shape index of all shapes in all drawings (plus one). - * - * @param int $value - */ - public function setSpIdMax($value): void - { - $this->spIdMax = $value; - } - - /** - * Get total number of drawings saved. - * - * @return int - */ - public function getCDgSaved() - { - return $this->cDgSaved; - } - - /** - * Set total number of drawings saved. - * - * @param int $value - */ - public function setCDgSaved($value): void - { - $this->cDgSaved = $value; - } - - /** - * Get total number of shapes saved (including group shapes). - * - * @return int - */ - public function getCSpSaved() - { - return $this->cSpSaved; - } - - /** - * Set total number of shapes saved (including group shapes). - * - * @param int $value - */ - public function setCSpSaved($value): void - { - $this->cSpSaved = $value; - } - - /** - * Get BLIP Store Container. - * - * @return ?DggContainer\BstoreContainer - */ - public function getBstoreContainer() - { - return $this->bstoreContainer; - } - - /** - * Set BLIP Store Container. - * - * @param DggContainer\BstoreContainer $bstoreContainer - */ - public function setBstoreContainer($bstoreContainer): void - { - $this->bstoreContainer = $bstoreContainer; - } - - /** - * Set an option for the drawing group. - * - * @param int $property The number specifies the option - * @param mixed $value - */ - public function setOPT($property, $value): void - { - $this->OPT[$property] = $value; - } - - /** - * Get an option for the drawing group. - * - * @param int $property The number specifies the option - * - * @return mixed - */ - public function getOPT($property) - { - if (isset($this->OPT[$property])) { - return $this->OPT[$property]; - } - - return null; - } - - /** - * Get identifier clusters. - * - * @return array - */ - public function getIDCLs() - { - return $this->IDCLs; - } - - /** - * Set identifier clusters. [ => , ...]. - * - * @param array $IDCLs - */ - public function setIDCLs($IDCLs): void - { - $this->IDCLs = $IDCLs; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php deleted file mode 100644 index 7203b66bea2..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php +++ /dev/null @@ -1,32 +0,0 @@ -BSECollection[] = $BSE; - $BSE->setParent($this); - } - - /** - * Get the collection of BLIP Store Entries. - * - * @return BstoreContainer\BSE[] - */ - public function getBSECollection() - { - return $this->BSECollection; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php deleted file mode 100644 index 328ac6b6c24..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php +++ /dev/null @@ -1,88 +0,0 @@ -parent = $parent; - } - - /** - * Get the BLIP. - * - * @return ?BSE\Blip - */ - public function getBlip() - { - return $this->blip; - } - - /** - * Set the BLIP. - */ - public function setBlip(BSE\Blip $blip): void - { - $this->blip = $blip; - $blip->setParent($this); - } - - /** - * Get the BLIP type. - * - * @return int - */ - public function getBlipType() - { - return $this->blipType; - } - - /** - * Set the BLIP type. - * - * @param int $blipType - */ - public function setBlipType($blipType): void - { - $this->blipType = $blipType; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php deleted file mode 100644 index 03b261f8feb..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php +++ /dev/null @@ -1,58 +0,0 @@ -data; - } - - /** - * Set the raw image data. - * - * @param string $data - */ - public function setData($data): void - { - $this->data = $data; - } - - /** - * Set parent BSE. - */ - public function setParent(BSE $parent): void - { - $this->parent = $parent; - } - - /** - * Get parent BSE. - */ - public function getParent(): BSE - { - return $this->parent; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php deleted file mode 100644 index ca22e6ca2be..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php +++ /dev/null @@ -1,203 +0,0 @@ -open($zipFile); - if ($res === true) { - $returnValue = ($zip->getFromName($archiveFile) !== false); - $zip->close(); - - return $returnValue; - } - } - - return false; - } - - return file_exists($filename); - } - - /** - * Returns canonicalized absolute pathname, also for ZIP archives. - */ - public static function realpath(string $filename): string - { - // Returnvalue - $returnValue = ''; - - // Try using realpath() - if (file_exists($filename)) { - $returnValue = realpath($filename) ?: ''; - } - - // Found something? - if ($returnValue === '') { - $pathArray = explode('/', $filename); - while (in_array('..', $pathArray) && $pathArray[0] != '..') { - $iMax = count($pathArray); - for ($i = 0; $i < $iMax; ++$i) { - if ($pathArray[$i] == '..' && $i > 0) { - unset($pathArray[$i], $pathArray[$i - 1]); - - break; - } - } - } - $returnValue = implode('/', $pathArray); - } - - // Return - return $returnValue; - } - - /** - * Get the systems temporary directory. - */ - public static function sysGetTempDir(): string - { - // Moodle hack! - if (function_exists('make_temp_directory')) { - $temp = make_temp_directory('phpspreadsheet'); - return realpath(dirname($temp)); - } - - $path = sys_get_temp_dir(); - if (self::$useUploadTempDirectory) { - // use upload-directory when defined to allow running on environments having very restricted - // open_basedir configs - if (ini_get('upload_tmp_dir') !== false) { - if ($temp = ini_get('upload_tmp_dir')) { - if (file_exists($temp)) { - $path = $temp; - } - } - } - } - - return realpath($path) ?: ''; - } - - public static function temporaryFilename(): string - { - $filename = tempnam(self::sysGetTempDir(), 'phpspreadsheet'); - if ($filename === false) { - throw new Exception('Could not create temporary file'); - } - - return $filename; - } - - /** - * Assert that given path is an existing file and is readable, otherwise throw exception. - */ - public static function assertFile(string $filename, string $zipMember = ''): void - { - if (!is_file($filename)) { - throw new ReaderException('File "' . $filename . '" does not exist.'); - } - - if (!is_readable($filename)) { - throw new ReaderException('Could not open "' . $filename . '" for reading.'); - } - - if ($zipMember !== '') { - $zipfile = "zip://$filename#$zipMember"; - if (!self::fileExists($zipfile)) { - // Has the file been saved with Windoze directory separators rather than unix? - $zipfile = "zip://$filename#" . str_replace('/', '\\', $zipMember); - if (!self::fileExists($zipfile)) { - throw new ReaderException("Could not find zip member $zipfile"); - } - } - } - } - - /** - * Same as assertFile, except return true/false and don't throw Exception. - */ - public static function testFileNoThrow(string $filename, ?string $zipMember = null): bool - { - if (!is_file($filename)) { - return false; - } - if (!is_readable($filename)) { - return false; - } - if ($zipMember === null) { - return true; - } - // validate zip, but don't check specific member - if ($zipMember === '') { - return self::validateZipFirst4($filename); - } - - $zipfile = "zip://$filename#$zipMember"; - if (self::fileExists($zipfile)) { - return true; - } - - // Has the file been saved with Windoze directory separators rather than unix? - $zipfile = "zip://$filename#" . str_replace('/', '\\', $zipMember); - - return self::fileExists($zipfile); - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php deleted file mode 100644 index 90c1992a3ab..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php +++ /dev/null @@ -1,675 +0,0 @@ - [ - 'x' => self::ARIAL, - 'xb' => self::ARIAL_BOLD, - 'xi' => self::ARIAL_ITALIC, - 'xbi' => self::ARIAL_BOLD_ITALIC, - ], - 'Calibri' => [ - 'x' => self::CALIBRI, - 'xb' => self::CALIBRI_BOLD, - 'xi' => self::CALIBRI_ITALIC, - 'xbi' => self::CALIBRI_BOLD_ITALIC, - ], - 'Comic Sans MS' => [ - 'x' => self::COMIC_SANS_MS, - 'xb' => self::COMIC_SANS_MS_BOLD, - 'xi' => self::COMIC_SANS_MS, - 'xbi' => self::COMIC_SANS_MS_BOLD, - ], - 'Courier New' => [ - 'x' => self::COURIER_NEW, - 'xb' => self::COURIER_NEW_BOLD, - 'xi' => self::COURIER_NEW_ITALIC, - 'xbi' => self::COURIER_NEW_BOLD_ITALIC, - ], - 'Georgia' => [ - 'x' => self::GEORGIA, - 'xb' => self::GEORGIA_BOLD, - 'xi' => self::GEORGIA_ITALIC, - 'xbi' => self::GEORGIA_BOLD_ITALIC, - ], - 'Impact' => [ - 'x' => self::IMPACT, - 'xb' => self::IMPACT, - 'xi' => self::IMPACT, - 'xbi' => self::IMPACT, - ], - 'Liberation Sans' => [ - 'x' => self::LIBERATION_SANS, - 'xb' => self::LIBERATION_SANS_BOLD, - 'xi' => self::LIBERATION_SANS_ITALIC, - 'xbi' => self::LIBERATION_SANS_BOLD_ITALIC, - ], - 'Lucida Console' => [ - 'x' => self::LUCIDA_CONSOLE, - 'xb' => self::LUCIDA_CONSOLE, - 'xi' => self::LUCIDA_CONSOLE, - 'xbi' => self::LUCIDA_CONSOLE, - ], - 'Lucida Sans Unicode' => [ - 'x' => self::LUCIDA_SANS_UNICODE, - 'xb' => self::LUCIDA_SANS_UNICODE, - 'xi' => self::LUCIDA_SANS_UNICODE, - 'xbi' => self::LUCIDA_SANS_UNICODE, - ], - 'Microsoft Sans Serif' => [ - 'x' => self::MICROSOFT_SANS_SERIF, - 'xb' => self::MICROSOFT_SANS_SERIF, - 'xi' => self::MICROSOFT_SANS_SERIF, - 'xbi' => self::MICROSOFT_SANS_SERIF, - ], - 'Palatino Linotype' => [ - 'x' => self::PALATINO_LINOTYPE, - 'xb' => self::PALATINO_LINOTYPE_BOLD, - 'xi' => self::PALATINO_LINOTYPE_ITALIC, - 'xbi' => self::PALATINO_LINOTYPE_BOLD_ITALIC, - ], - 'Symbol' => [ - 'x' => self::SYMBOL, - 'xb' => self::SYMBOL, - 'xi' => self::SYMBOL, - 'xbi' => self::SYMBOL, - ], - 'Tahoma' => [ - 'x' => self::TAHOMA, - 'xb' => self::TAHOMA_BOLD, - 'xi' => self::TAHOMA, - 'xbi' => self::TAHOMA_BOLD, - ], - 'Times New Roman' => [ - 'x' => self::TIMES_NEW_ROMAN, - 'xb' => self::TIMES_NEW_ROMAN_BOLD, - 'xi' => self::TIMES_NEW_ROMAN_ITALIC, - 'xbi' => self::TIMES_NEW_ROMAN_BOLD_ITALIC, - ], - 'Trebuchet MS' => [ - 'x' => self::TREBUCHET_MS, - 'xb' => self::TREBUCHET_MS_BOLD, - 'xi' => self::TREBUCHET_MS_ITALIC, - 'xbi' => self::TREBUCHET_MS_BOLD_ITALIC, - ], - 'Verdana' => [ - 'x' => self::VERDANA, - 'xb' => self::VERDANA_BOLD, - 'xi' => self::VERDANA_ITALIC, - 'xbi' => self::VERDANA_BOLD_ITALIC, - ], - ]; - - /** - * Array that can be used to supplement FONT_FILE_NAMES for calculating exact width. - * - * @var array - */ - private static $extraFontArray = []; - - public static function setExtraFontArray(array $extraFontArray): void - { - self::$extraFontArray = $extraFontArray; - } - - public static function getExtraFontArray(): array - { - return self::$extraFontArray; - } - - /** - * AutoSize method. - * - * @var string - */ - private static $autoSizeMethod = self::AUTOSIZE_METHOD_APPROX; - - /** - * Path to folder containing TrueType font .ttf files. - * - * @var string - */ - private static $trueTypeFontPath = ''; - - /** - * How wide is a default column for a given default font and size? - * Empirical data found by inspecting real Excel files and reading off the pixel width - * in Microsoft Office Excel 2007. - * Added height in points. - */ - public const DEFAULT_COLUMN_WIDTHS = [ - 'Arial' => [ - 1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], - 2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], - 3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0], - - 4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75], - 5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25], - 6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25], - 7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0], - 8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25], - 9 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.0], - 10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75], - ], - 'Calibri' => [ - 1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], - 2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], - 3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.00], - 4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75], - 5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25], - 6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25], - 7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0], - 8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25], - 9 => ['px' => 56, 'width' => 9.33203125, 'height' => 12.0], - 10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75], - 11 => ['px' => 64, 'width' => 9.14062500, 'height' => 15.0], - ], - 'Verdana' => [ - 1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], - 2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], - 3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0], - 4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75], - 5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25], - 6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25], - 7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0], - 8 => ['px' => 64, 'width' => 9.14062500, 'height' => 10.5], - 9 => ['px' => 72, 'width' => 9.00000000, 'height' => 11.25], - 10 => ['px' => 72, 'width' => 9.00000000, 'height' => 12.75], - ], - ]; - - /** - * List of column widths. Replaced by constant; - * previously it was public and updateable, allowing - * user to make inappropriate alterations. - * - * @deprecated 1.25.0 Use DEFAULT_COLUMN_WIDTHS constant instead. - * - * @var array - */ - public static $defaultColumnWidths = self::DEFAULT_COLUMN_WIDTHS; - - /** - * Set autoSize method. - * - * @param string $method see self::AUTOSIZE_METHOD_* - * - * @return bool Success or failure - */ - public static function setAutoSizeMethod($method) - { - if (!in_array($method, self::AUTOSIZE_METHODS)) { - return false; - } - self::$autoSizeMethod = $method; - - return true; - } - - /** - * Get autoSize method. - * - * @return string - */ - public static function getAutoSizeMethod() - { - return self::$autoSizeMethod; - } - - /** - * Set the path to the folder containing .ttf files. There should be a trailing slash. - * Typical locations on variout some platforms: - *
    - *
  • C:/Windows/Fonts/
  • - *
  • /usr/share/fonts/truetype/
  • - *
  • ~/.fonts/
  • - *
. - * - * @param string $folderPath - */ - public static function setTrueTypeFontPath($folderPath): void - { - self::$trueTypeFontPath = $folderPath; - } - - /** - * Get the path to the folder containing .ttf files. - * - * @return string - */ - public static function getTrueTypeFontPath() - { - return self::$trueTypeFontPath; - } - - /** - * Calculate an (approximate) OpenXML column width, based on font size and text contained. - * - * @param FontStyle $font Font object - * @param null|RichText|string $cellText Text to calculate width - * @param int $rotation Rotation angle - * @param null|FontStyle $defaultFont Font object - * @param bool $filterAdjustment Add space for Autofilter or Table dropdown - */ - public static function calculateColumnWidth( - FontStyle $font, - $cellText = '', - $rotation = 0, - ?FontStyle $defaultFont = null, - bool $filterAdjustment = false, - int $indentAdjustment = 0 - ): float { - // If it is rich text, use plain text - if ($cellText instanceof RichText) { - $cellText = $cellText->getPlainText(); - } - - // Special case if there are one or more newline characters ("\n") - $cellText = (string) $cellText; - if (strpos($cellText, "\n") !== false) { - $lineTexts = explode("\n", $cellText); - $lineWidths = []; - foreach ($lineTexts as $lineText) { - $lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont, $filterAdjustment); - } - - return max($lineWidths); // width of longest line in cell - } - - // Try to get the exact text width in pixels - $approximate = self::$autoSizeMethod === self::AUTOSIZE_METHOD_APPROX; - $columnWidth = 0; - if (!$approximate) { - try { - $columnWidthAdjust = ceil( - self::getTextWidthPixelsExact( - str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))), - $font, - 0 - ) * 1.07 - ); - - // Width of text in pixels excl. padding - // and addition because Excel adds some padding, just use approx width of 'n' glyph - $columnWidth = self::getTextWidthPixelsExact($cellText, $font, $rotation) + $columnWidthAdjust; - } catch (PhpSpreadsheetException $e) { - $approximate = true; - } - } - - if ($approximate) { - $columnWidthAdjust = self::getTextWidthPixelsApprox( - str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))), - $font, - 0 - ); - // Width of text in pixels excl. padding, approximation - // and addition because Excel adds some padding, just use approx width of 'n' glyph - $columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust; - } - - // Convert from pixel width to column width - $columnWidth = Drawing::pixelsToCellDimension((int) $columnWidth, $defaultFont ?? new FontStyle()); - - // Return - return round($columnWidth, 4); - } - - /** - * Get GD text width in pixels for a string of text in a certain font at a certain rotation angle. - */ - public static function getTextWidthPixelsExact(string $text, FontStyle $font, int $rotation = 0): float - { - // font size should really be supplied in pixels in GD2, - // but since GD2 seems to assume 72dpi, pixels and points are the same - $fontFile = self::getTrueTypeFontFileFromFont($font); - $textBox = imagettfbbox($font->getSize() ?? 10.0, $rotation, $fontFile, $text); - if ($textBox === false) { - // @codeCoverageIgnoreStart - throw new PhpSpreadsheetException('imagettfbbox failed'); - // @codeCoverageIgnoreEnd - } - - // Get corners positions - $lowerLeftCornerX = $textBox[0]; - $lowerRightCornerX = $textBox[2]; - $upperRightCornerX = $textBox[4]; - $upperLeftCornerX = $textBox[6]; - - // Consider the rotation when calculating the width - return round(max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX), 4); - } - - /** - * Get approximate width in pixels for a string of text in a certain font at a certain rotation angle. - * - * @param string $columnText - * @param int $rotation - * - * @return int Text width in pixels (no padding added) - */ - public static function getTextWidthPixelsApprox($columnText, FontStyle $font, $rotation = 0) - { - $fontName = $font->getName(); - $fontSize = $font->getSize(); - - // Calculate column width in pixels. - // We assume fixed glyph width, but count double for "fullwidth" characters. - // Result varies with font name and size. - switch ($fontName) { - case 'Arial': - // value 8 was set because of experience in different exports at Arial 10 font. - $columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText)); - $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size - - break; - case 'Verdana': - // value 8 was found via interpolation by inspecting real Excel files with Verdana 10 font. - $columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText)); - $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size - - break; - default: - // just assume Calibri - // value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font. - $columnWidth = (int) (8.26 * StringHelper::countCharactersDbcs($columnText)); - $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size - - break; - } - - // Calculate approximate rotated column width - if ($rotation !== 0) { - if ($rotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) { - // stacked text - $columnWidth = 4; // approximation - } else { - // rotated text - $columnWidth = $columnWidth * cos(deg2rad($rotation)) - + $fontSize * abs(sin(deg2rad($rotation))) / 5; // approximation - } - } - - // pixel width is an integer - return (int) $columnWidth; - } - - /** - * Calculate an (approximate) pixel size, based on a font points size. - * - * @param int $fontSizeInPoints Font size (in points) - * - * @return int Font size (in pixels) - */ - public static function fontSizeToPixels($fontSizeInPoints) - { - return (int) ((4 / 3) * $fontSizeInPoints); - } - - /** - * Calculate an (approximate) pixel size, based on inch size. - * - * @param int $sizeInInch Font size (in inch) - * - * @return int Size (in pixels) - */ - public static function inchSizeToPixels($sizeInInch) - { - return $sizeInInch * 96; - } - - /** - * Calculate an (approximate) pixel size, based on centimeter size. - * - * @param int $sizeInCm Font size (in centimeters) - * - * @return float Size (in pixels) - */ - public static function centimeterSizeToPixels($sizeInCm) - { - return $sizeInCm * 37.795275591; - } - - /** - * Returns the font path given the font. - * - * @return string Path to TrueType font file - */ - public static function getTrueTypeFontFileFromFont(FontStyle $font, bool $checkPath = true) - { - if ($checkPath && (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath))) { - throw new PhpSpreadsheetException('Valid directory to TrueType Font files not specified'); - } - - $name = $font->getName(); - $fontArray = array_merge(self::FONT_FILE_NAMES, self::$extraFontArray); - if (!isset($fontArray[$name])) { - throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file'); - } - $bold = $font->getBold(); - $italic = $font->getItalic(); - $index = 'x'; - if ($bold) { - $index .= 'b'; - } - if ($italic) { - $index .= 'i'; - } - $fontFile = $fontArray[$name][$index]; - - $separator = ''; - if (mb_strlen(self::$trueTypeFontPath) > 1 && mb_substr(self::$trueTypeFontPath, -1) !== '/' && mb_substr(self::$trueTypeFontPath, -1) !== '\\') { - $separator = DIRECTORY_SEPARATOR; - } - $fontFileAbsolute = preg_match('~^([A-Za-z]:)?[/\\\\]~', $fontFile) === 1; - if (!$fontFileAbsolute) { - $fontFile = self::$trueTypeFontPath . $separator . $fontFile; - } - - // Check if file actually exists - if ($checkPath && !file_exists($fontFile) && !$fontFileAbsolute) { - $alternateName = $name; - if ($index !== 'x' && $fontArray[$name][$index] !== $fontArray[$name]['x']) { - // Bold but no italic: - // Comic Sans - // Tahoma - // Neither bold nor italic: - // Impact - // Lucida Console - // Lucida Sans Unicode - // Microsoft Sans Serif - // Symbol - if ($index === 'xb') { - $alternateName .= ' Bold'; - } elseif ($index === 'xi') { - $alternateName .= ' Italic'; - } elseif ($fontArray[$name]['xb'] === $fontArray[$name]['xbi']) { - $alternateName .= ' Bold'; - } else { - $alternateName .= ' Bold Italic'; - } - } - $fontFile = self::$trueTypeFontPath . $separator . $alternateName . '.ttf'; - if (!file_exists($fontFile)) { - throw new PhpSpreadsheetException('TrueType Font file not found'); - } - } - - return $fontFile; - } - - public const CHARSET_FROM_FONT_NAME = [ - 'EucrosiaUPC' => self::CHARSET_ANSI_THAI, - 'Wingdings' => self::CHARSET_SYMBOL, - 'Wingdings 2' => self::CHARSET_SYMBOL, - 'Wingdings 3' => self::CHARSET_SYMBOL, - ]; - - /** - * Returns the associated charset for the font name. - * - * @param string $fontName Font name - * - * @return int Character set code - */ - public static function getCharsetFromFontName($fontName) - { - return self::CHARSET_FROM_FONT_NAME[$fontName] ?? self::CHARSET_ANSI_LATIN; - } - - /** - * Get the effective column width for columns without a column dimension or column with width -1 - * For example, for Calibri 11 this is 9.140625 (64 px). - * - * @param FontStyle $font The workbooks default font - * @param bool $returnAsPixels true = return column width in pixels, false = return in OOXML units - * - * @return mixed Column width - */ - public static function getDefaultColumnWidthByFont(FontStyle $font, $returnAsPixels = false) - { - if (isset(self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()])) { - // Exact width can be determined - $columnWidth = $returnAsPixels ? - self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['px'] - : self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['width']; - } else { - // We don't have data for this particular font and size, use approximation by - // extrapolating from Calibri 11 - $columnWidth = $returnAsPixels ? - self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['px'] - : self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['width']; - $columnWidth = $columnWidth * $font->getSize() / 11; - - // Round pixels to closest integer - if ($returnAsPixels) { - $columnWidth = (int) round($columnWidth); - } - } - - return $columnWidth; - } - - /** - * Get the effective row height for rows without a row dimension or rows with height -1 - * For example, for Calibri 11 this is 15 points. - * - * @param FontStyle $font The workbooks default font - * - * @return float Row height in points - */ - public static function getDefaultRowHeightByFont(FontStyle $font) - { - $name = $font->getName(); - $size = $font->getSize(); - if (isset(self::DEFAULT_COLUMN_WIDTHS[$name][$size])) { - $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][$size]['height']; - } elseif ($name === 'Arial' || $name === 'Verdana') { - $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][10]['height'] * $size / 10.0; - } else { - $rowHeight = self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['height'] * $size / 11.0; - } - - return $rowHeight; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php deleted file mode 100644 index 060f09c8831..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php +++ /dev/null @@ -1,21 +0,0 @@ - 'md2', - Protection::ALGORITHM_MD4 => 'md4', - Protection::ALGORITHM_MD5 => 'md5', - Protection::ALGORITHM_SHA_1 => 'sha1', - Protection::ALGORITHM_SHA_256 => 'sha256', - Protection::ALGORITHM_SHA_384 => 'sha384', - Protection::ALGORITHM_SHA_512 => 'sha512', - Protection::ALGORITHM_RIPEMD_128 => 'ripemd128', - Protection::ALGORITHM_RIPEMD_160 => 'ripemd160', - Protection::ALGORITHM_WHIRLPOOL => 'whirlpool', - ]; - - if (array_key_exists($algorithmName, $mapping)) { - return $mapping[$algorithmName]; - } - - throw new SpException('Unsupported password algorithm: ' . $algorithmName); - } - - /** - * Create a password hash from a given string. - * - * This method is based on the spec at: - * https://interoperability.blob.core.windows.net/files/MS-OFFCRYPTO/[MS-OFFCRYPTO].pdf - * 2.3.7.1 Binary Document Password Verifier Derivation Method 1 - * - * It replaces a method based on the algorithm provided by - * Daniel Rentz of OpenOffice and the PEAR package - * Spreadsheet_Excel_Writer by Xavier Noguer . - * - * Scrutinizer will squawk at the use of bitwise operations here, - * but it should ultimately pass. - * - * @param string $password Password to hash - */ - private static function defaultHashPassword(string $password): string - { - $verifier = 0; - $pwlen = strlen($password); - $passwordArray = pack('c', $pwlen) . $password; - for ($i = $pwlen; $i >= 0; --$i) { - $intermediate1 = (($verifier & 0x4000) === 0) ? 0 : 1; - $intermediate2 = 2 * $verifier; - $intermediate2 = $intermediate2 & 0x7fff; - $intermediate3 = $intermediate1 | $intermediate2; - $verifier = $intermediate3 ^ ord($passwordArray[$i]); - } - $verifier ^= 0xCE4B; - - return strtoupper(dechex($verifier)); - } - - /** - * Create a password hash from a given string by a specific algorithm. - * - * 2.4.2.4 ISO Write Protection Method - * - * @see https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/1357ea58-646e-4483-92ef-95d718079d6f - * - * @param string $password Password to hash - * @param string $algorithm Hash algorithm used to compute the password hash value - * @param string $salt Pseudorandom string - * @param int $spinCount Number of times to iterate on a hash of a password - * - * @return string Hashed password - */ - public static function hashPassword(string $password, string $algorithm = '', string $salt = '', int $spinCount = 10000): string - { - if (strlen($password) > self::MAX_PASSWORD_LENGTH) { - throw new SpException('Password exceeds ' . self::MAX_PASSWORD_LENGTH . ' characters'); - } - $phpAlgorithm = self::getAlgorithm($algorithm); - if (!$phpAlgorithm) { - return self::defaultHashPassword($password); - } - - $saltValue = base64_decode($salt); - $encodedPassword = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8'); - - $hashValue = hash($phpAlgorithm, $saltValue . /** @scrutinizer ignore-type */ $encodedPassword, true); - for ($i = 0; $i < $spinCount; ++$i) { - $hashValue = hash($phpAlgorithm, $hashValue . pack('L', $i), true); - } - - return base64_encode($hashValue); - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php deleted file mode 100644 index c6c198e203c..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php +++ /dev/null @@ -1,684 +0,0 @@ - chr(0), - "\x1B 1" => chr(1), - "\x1B 2" => chr(2), - "\x1B 3" => chr(3), - "\x1B 4" => chr(4), - "\x1B 5" => chr(5), - "\x1B 6" => chr(6), - "\x1B 7" => chr(7), - "\x1B 8" => chr(8), - "\x1B 9" => chr(9), - "\x1B :" => chr(10), - "\x1B ;" => chr(11), - "\x1B <" => chr(12), - "\x1B =" => chr(13), - "\x1B >" => chr(14), - "\x1B ?" => chr(15), - "\x1B!0" => chr(16), - "\x1B!1" => chr(17), - "\x1B!2" => chr(18), - "\x1B!3" => chr(19), - "\x1B!4" => chr(20), - "\x1B!5" => chr(21), - "\x1B!6" => chr(22), - "\x1B!7" => chr(23), - "\x1B!8" => chr(24), - "\x1B!9" => chr(25), - "\x1B!:" => chr(26), - "\x1B!;" => chr(27), - "\x1B!<" => chr(28), - "\x1B!=" => chr(29), - "\x1B!>" => chr(30), - "\x1B!?" => chr(31), - "\x1B'?" => chr(127), - "\x1B(0" => '€', // 128 in CP1252 - "\x1B(2" => '‚', // 130 in CP1252 - "\x1B(3" => 'ƒ', // 131 in CP1252 - "\x1B(4" => '„', // 132 in CP1252 - "\x1B(5" => '…', // 133 in CP1252 - "\x1B(6" => '†', // 134 in CP1252 - "\x1B(7" => '‡', // 135 in CP1252 - "\x1B(8" => 'ˆ', // 136 in CP1252 - "\x1B(9" => '‰', // 137 in CP1252 - "\x1B(:" => 'Š', // 138 in CP1252 - "\x1B(;" => '‹', // 139 in CP1252 - "\x1BNj" => 'Œ', // 140 in CP1252 - "\x1B(>" => 'Ž', // 142 in CP1252 - "\x1B)1" => '‘', // 145 in CP1252 - "\x1B)2" => '’', // 146 in CP1252 - "\x1B)3" => '“', // 147 in CP1252 - "\x1B)4" => '”', // 148 in CP1252 - "\x1B)5" => '•', // 149 in CP1252 - "\x1B)6" => '–', // 150 in CP1252 - "\x1B)7" => '—', // 151 in CP1252 - "\x1B)8" => '˜', // 152 in CP1252 - "\x1B)9" => '™', // 153 in CP1252 - "\x1B):" => 'š', // 154 in CP1252 - "\x1B);" => '›', // 155 in CP1252 - "\x1BNz" => 'œ', // 156 in CP1252 - "\x1B)>" => 'ž', // 158 in CP1252 - "\x1B)?" => 'Ÿ', // 159 in CP1252 - "\x1B*0" => ' ', // 160 in CP1252 - "\x1BN!" => '¡', // 161 in CP1252 - "\x1BN\"" => '¢', // 162 in CP1252 - "\x1BN#" => '£', // 163 in CP1252 - "\x1BN(" => '¤', // 164 in CP1252 - "\x1BN%" => '¥', // 165 in CP1252 - "\x1B*6" => '¦', // 166 in CP1252 - "\x1BN'" => '§', // 167 in CP1252 - "\x1BNH " => '¨', // 168 in CP1252 - "\x1BNS" => '©', // 169 in CP1252 - "\x1BNc" => 'ª', // 170 in CP1252 - "\x1BN+" => '«', // 171 in CP1252 - "\x1B*<" => '¬', // 172 in CP1252 - "\x1B*=" => '­', // 173 in CP1252 - "\x1BNR" => '®', // 174 in CP1252 - "\x1B*?" => '¯', // 175 in CP1252 - "\x1BN0" => '°', // 176 in CP1252 - "\x1BN1" => '±', // 177 in CP1252 - "\x1BN2" => '²', // 178 in CP1252 - "\x1BN3" => '³', // 179 in CP1252 - "\x1BNB " => '´', // 180 in CP1252 - "\x1BN5" => 'µ', // 181 in CP1252 - "\x1BN6" => '¶', // 182 in CP1252 - "\x1BN7" => '·', // 183 in CP1252 - "\x1B+8" => '¸', // 184 in CP1252 - "\x1BNQ" => '¹', // 185 in CP1252 - "\x1BNk" => 'º', // 186 in CP1252 - "\x1BN;" => '»', // 187 in CP1252 - "\x1BN<" => '¼', // 188 in CP1252 - "\x1BN=" => '½', // 189 in CP1252 - "\x1BN>" => '¾', // 190 in CP1252 - "\x1BN?" => '¿', // 191 in CP1252 - "\x1BNAA" => 'À', // 192 in CP1252 - "\x1BNBA" => 'Á', // 193 in CP1252 - "\x1BNCA" => 'Â', // 194 in CP1252 - "\x1BNDA" => 'Ã', // 195 in CP1252 - "\x1BNHA" => 'Ä', // 196 in CP1252 - "\x1BNJA" => 'Å', // 197 in CP1252 - "\x1BNa" => 'Æ', // 198 in CP1252 - "\x1BNKC" => 'Ç', // 199 in CP1252 - "\x1BNAE" => 'È', // 200 in CP1252 - "\x1BNBE" => 'É', // 201 in CP1252 - "\x1BNCE" => 'Ê', // 202 in CP1252 - "\x1BNHE" => 'Ë', // 203 in CP1252 - "\x1BNAI" => 'Ì', // 204 in CP1252 - "\x1BNBI" => 'Í', // 205 in CP1252 - "\x1BNCI" => 'Î', // 206 in CP1252 - "\x1BNHI" => 'Ï', // 207 in CP1252 - "\x1BNb" => 'Ð', // 208 in CP1252 - "\x1BNDN" => 'Ñ', // 209 in CP1252 - "\x1BNAO" => 'Ò', // 210 in CP1252 - "\x1BNBO" => 'Ó', // 211 in CP1252 - "\x1BNCO" => 'Ô', // 212 in CP1252 - "\x1BNDO" => 'Õ', // 213 in CP1252 - "\x1BNHO" => 'Ö', // 214 in CP1252 - "\x1B-7" => '×', // 215 in CP1252 - "\x1BNi" => 'Ø', // 216 in CP1252 - "\x1BNAU" => 'Ù', // 217 in CP1252 - "\x1BNBU" => 'Ú', // 218 in CP1252 - "\x1BNCU" => 'Û', // 219 in CP1252 - "\x1BNHU" => 'Ü', // 220 in CP1252 - "\x1B-=" => 'Ý', // 221 in CP1252 - "\x1BNl" => 'Þ', // 222 in CP1252 - "\x1BN{" => 'ß', // 223 in CP1252 - "\x1BNAa" => 'à', // 224 in CP1252 - "\x1BNBa" => 'á', // 225 in CP1252 - "\x1BNCa" => 'â', // 226 in CP1252 - "\x1BNDa" => 'ã', // 227 in CP1252 - "\x1BNHa" => 'ä', // 228 in CP1252 - "\x1BNJa" => 'å', // 229 in CP1252 - "\x1BNq" => 'æ', // 230 in CP1252 - "\x1BNKc" => 'ç', // 231 in CP1252 - "\x1BNAe" => 'è', // 232 in CP1252 - "\x1BNBe" => 'é', // 233 in CP1252 - "\x1BNCe" => 'ê', // 234 in CP1252 - "\x1BNHe" => 'ë', // 235 in CP1252 - "\x1BNAi" => 'ì', // 236 in CP1252 - "\x1BNBi" => 'í', // 237 in CP1252 - "\x1BNCi" => 'î', // 238 in CP1252 - "\x1BNHi" => 'ï', // 239 in CP1252 - "\x1BNs" => 'ð', // 240 in CP1252 - "\x1BNDn" => 'ñ', // 241 in CP1252 - "\x1BNAo" => 'ò', // 242 in CP1252 - "\x1BNBo" => 'ó', // 243 in CP1252 - "\x1BNCo" => 'ô', // 244 in CP1252 - "\x1BNDo" => 'õ', // 245 in CP1252 - "\x1BNHo" => 'ö', // 246 in CP1252 - "\x1B/7" => '÷', // 247 in CP1252 - "\x1BNy" => 'ø', // 248 in CP1252 - "\x1BNAu" => 'ù', // 249 in CP1252 - "\x1BNBu" => 'ú', // 250 in CP1252 - "\x1BNCu" => 'û', // 251 in CP1252 - "\x1BNHu" => 'ü', // 252 in CP1252 - "\x1B/=" => 'ý', // 253 in CP1252 - "\x1BN|" => 'þ', // 254 in CP1252 - "\x1BNHy" => 'ÿ', // 255 in CP1252 - ]; - } - - /** - * Get whether iconv extension is available. - * - * @return bool - */ - public static function getIsIconvEnabled() - { - if (isset(self::$isIconvEnabled)) { - return self::$isIconvEnabled; - } - - // Assume no problems with iconv - self::$isIconvEnabled = true; - - // Fail if iconv doesn't exist - if (!function_exists('iconv')) { - self::$isIconvEnabled = false; - } elseif (!@iconv('UTF-8', 'UTF-16LE', 'x')) { - // Sometimes iconv is not working, and e.g. iconv('UTF-8', 'UTF-16LE', 'x') just returns false, - self::$isIconvEnabled = false; - } elseif (defined('PHP_OS') && @stristr(PHP_OS, 'AIX') && defined('ICONV_IMPL') && (@strcasecmp(ICONV_IMPL, 'unknown') == 0) && defined('ICONV_VERSION') && (@strcasecmp(ICONV_VERSION, 'unknown') == 0)) { - // CUSTOM: IBM AIX iconv() does not work - self::$isIconvEnabled = false; - } - - // Deactivate iconv default options if they fail (as seen on IMB i) - if (self::$isIconvEnabled && !@iconv('UTF-8', 'UTF-16LE' . self::$iconvOptions, 'x')) { - self::$iconvOptions = ''; - } - - return self::$isIconvEnabled; - } - - private static function buildCharacterSets(): void - { - if (empty(self::$controlCharacters)) { - self::buildControlCharacters(); - } - - if (empty(self::$SYLKCharacters)) { - self::buildSYLKCharacters(); - } - } - - /** - * Convert from OpenXML escaped control character to PHP control character. - * - * Excel 2007 team: - * ---------------- - * That's correct, control characters are stored directly in the shared-strings table. - * We do encode characters that cannot be represented in XML using the following escape sequence: - * _xHHHH_ where H represents a hexadecimal character in the character's value... - * So you could end up with something like _x0008_ in a string (either in a cell value () - * element or in the shared string element. - * - * @param string $textValue Value to unescape - * - * @return string - */ - public static function controlCharacterOOXML2PHP($textValue) - { - self::buildCharacterSets(); - - return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $textValue); - } - - /** - * Convert from PHP control character to OpenXML escaped control character. - * - * Excel 2007 team: - * ---------------- - * That's correct, control characters are stored directly in the shared-strings table. - * We do encode characters that cannot be represented in XML using the following escape sequence: - * _xHHHH_ where H represents a hexadecimal character in the character's value... - * So you could end up with something like _x0008_ in a string (either in a cell value () - * element or in the shared string element. - * - * @param string $textValue Value to escape - * - * @return string - */ - public static function controlCharacterPHP2OOXML($textValue) - { - self::buildCharacterSets(); - - return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $textValue); - } - - /** - * Try to sanitize UTF8, replacing invalid sequences with Unicode substitution characters. - */ - public static function sanitizeUTF8(string $textValue): string - { - $textValue = str_replace(["\xef\xbf\xbe", "\xef\xbf\xbf"], "\xef\xbf\xbd", $textValue); - $subst = mb_substitute_character(); // default is question mark - mb_substitute_character(65533); // Unicode substitution character - // Phpstan does not think this can return false. - $returnValue = mb_convert_encoding($textValue, 'UTF-8', 'UTF-8'); - mb_substitute_character(/** @scrutinizer ignore-type */ $subst); - - return self::returnString($returnValue); - } - - /** - * Strictly to satisfy Scrutinizer. - * - * @param mixed $value - */ - private static function returnString($value): string - { - return is_string($value) ? $value : ''; - } - - /** - * Check if a string contains UTF8 data. - */ - public static function isUTF8(string $textValue): bool - { - return $textValue === self::sanitizeUTF8($textValue); - } - - /** - * Formats a numeric value as a string for output in various output writers forcing - * point as decimal separator in case locale is other than English. - * - * @param float|int|string $numericValue - */ - public static function formatNumber($numericValue): string - { - if (is_float($numericValue)) { - return str_replace(',', '.', (string) $numericValue); - } - - return (string) $numericValue; - } - - /** - * Converts a UTF-8 string into BIFF8 Unicode string data (8-bit string length) - * Writes the string using uncompressed notation, no rich text, no Asian phonetics - * If mbstring extension is not available, ASCII is assumed, and compressed notation is used - * although this will give wrong results for non-ASCII strings - * see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3. - * - * @param string $textValue UTF-8 encoded string - * @param mixed[] $arrcRuns Details of rich text runs in $value - */ - public static function UTF8toBIFF8UnicodeShort(string $textValue, array $arrcRuns = []): string - { - // character count - $ln = self::countCharacters($textValue, 'UTF-8'); - // option flags - if (empty($arrcRuns)) { - $data = pack('CC', $ln, 0x0001); - // characters - $data .= self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8'); - } else { - $data = pack('vC', $ln, 0x09); - $data .= pack('v', count($arrcRuns)); - // characters - $data .= self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8'); - foreach ($arrcRuns as $cRun) { - $data .= pack('v', $cRun['strlen']); - $data .= pack('v', $cRun['fontidx']); - } - } - - return $data; - } - - /** - * Converts a UTF-8 string into BIFF8 Unicode string data (16-bit string length) - * Writes the string using uncompressed notation, no rich text, no Asian phonetics - * If mbstring extension is not available, ASCII is assumed, and compressed notation is used - * although this will give wrong results for non-ASCII strings - * see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3. - * - * @param string $textValue UTF-8 encoded string - */ - public static function UTF8toBIFF8UnicodeLong(string $textValue): string - { - // character count - $ln = self::countCharacters($textValue, 'UTF-8'); - - // characters - $chars = self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8'); - - return pack('vC', $ln, 0x0001) . $chars; - } - - /** - * Convert string from one encoding to another. - * - * @param string $to Encoding to convert to, e.g. 'UTF-8' - * @param string $from Encoding to convert from, e.g. 'UTF-16LE' - */ - public static function convertEncoding(string $textValue, string $to, string $from): string - { - if (self::getIsIconvEnabled()) { - $result = iconv($from, $to . self::$iconvOptions, $textValue); - if (false !== $result) { - return $result; - } - } - - return self::returnString(mb_convert_encoding($textValue, $to, $from)); - } - - /** - * Get character count. - * - * @param string $encoding Encoding - * - * @return int Character count - */ - public static function countCharacters(string $textValue, string $encoding = 'UTF-8'): int - { - return mb_strlen($textValue, $encoding); - } - - /** - * Get character count using mb_strwidth rather than mb_strlen. - * - * @param string $encoding Encoding - * - * @return int Character count - */ - public static function countCharactersDbcs(string $textValue, string $encoding = 'UTF-8'): int - { - return mb_strwidth($textValue, $encoding); - } - - /** - * Get a substring of a UTF-8 encoded string. - * - * @param string $textValue UTF-8 encoded string - * @param int $offset Start offset - * @param ?int $length Maximum number of characters in substring - */ - public static function substring(string $textValue, int $offset, ?int $length = 0): string - { - return mb_substr($textValue, $offset, $length, 'UTF-8'); - } - - /** - * Convert a UTF-8 encoded string to upper case. - * - * @param string $textValue UTF-8 encoded string - */ - public static function strToUpper(string $textValue): string - { - return mb_convert_case($textValue, MB_CASE_UPPER, 'UTF-8'); - } - - /** - * Convert a UTF-8 encoded string to lower case. - * - * @param string $textValue UTF-8 encoded string - */ - public static function strToLower(string $textValue): string - { - return mb_convert_case($textValue, MB_CASE_LOWER, 'UTF-8'); - } - - /** - * Convert a UTF-8 encoded string to title/proper case - * (uppercase every first character in each word, lower case all other characters). - * - * @param string $textValue UTF-8 encoded string - */ - public static function strToTitle(string $textValue): string - { - return mb_convert_case($textValue, MB_CASE_TITLE, 'UTF-8'); - } - - public static function mbIsUpper(string $character): bool - { - return mb_strtolower($character, 'UTF-8') !== $character; - } - - /** - * Splits a UTF-8 string into an array of individual characters. - */ - public static function mbStrSplit(string $string): array - { - // Split at all position not after the start: ^ - // and not before the end: $ - $split = preg_split('/(? $v) { - $textValue = str_replace($k, $v, $textValue); - } - - return $textValue; - } - - /** - * Retrieve any leading numeric part of a string, or return the full string if no leading numeric - * (handles basic integer or float, but not exponent or non decimal). - * - * @param string $textValue - * - * @return mixed string or only the leading numeric part of the string - */ - public static function testStringAsNumeric($textValue) - { - if (is_numeric($textValue)) { - return $textValue; - } - $v = (float) $textValue; - - return (is_numeric(substr($textValue, 0, strlen((string) $v)))) ? $v : $textValue; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php deleted file mode 100644 index 324e3424dd2..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php +++ /dev/null @@ -1,77 +0,0 @@ -setTimeZone(new DateTimeZone($timezoneName)); - - return $dtobj->getOffset(); - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php deleted file mode 100644 index a8d7c93b7d6..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php +++ /dev/null @@ -1,501 +0,0 @@ -error; - } - - /** @return string */ - public function getBestFitType() - { - return $this->bestFitType; - } - - /** - * Return the Y-Value for a specified value of X. - * - * @param float $xValue X-Value - * - * @return float Y-Value - */ - abstract public function getValueOfYForX($xValue); - - /** - * Return the X-Value for a specified value of Y. - * - * @param float $yValue Y-Value - * - * @return float X-Value - */ - abstract public function getValueOfXForY($yValue); - - /** - * Return the original set of X-Values. - * - * @return float[] X-Values - */ - public function getXValues() - { - return $this->xValues; - } - - /** - * Return the Equation of the best-fit line. - * - * @param int $dp Number of places of decimal precision to display - * - * @return string - */ - abstract public function getEquation($dp = 0); - - /** - * Return the Slope of the line. - * - * @param int $dp Number of places of decimal precision to display - * - * @return float - */ - public function getSlope($dp = 0) - { - if ($dp != 0) { - return round($this->slope, $dp); - } - - return $this->slope; - } - - /** - * Return the standard error of the Slope. - * - * @param int $dp Number of places of decimal precision to display - * - * @return float - */ - public function getSlopeSE($dp = 0) - { - if ($dp != 0) { - return round($this->slopeSE, $dp); - } - - return $this->slopeSE; - } - - /** - * Return the Value of X where it intersects Y = 0. - * - * @param int $dp Number of places of decimal precision to display - * - * @return float - */ - public function getIntersect($dp = 0) - { - if ($dp != 0) { - return round($this->intersect, $dp); - } - - return $this->intersect; - } - - /** - * Return the standard error of the Intersect. - * - * @param int $dp Number of places of decimal precision to display - * - * @return float - */ - public function getIntersectSE($dp = 0) - { - if ($dp != 0) { - return round($this->intersectSE, $dp); - } - - return $this->intersectSE; - } - - /** - * Return the goodness of fit for this regression. - * - * @param int $dp Number of places of decimal precision to return - * - * @return float - */ - public function getGoodnessOfFit($dp = 0) - { - if ($dp != 0) { - return round($this->goodnessOfFit, $dp); - } - - return $this->goodnessOfFit; - } - - /** - * Return the goodness of fit for this regression. - * - * @param int $dp Number of places of decimal precision to return - * - * @return float - */ - public function getGoodnessOfFitPercent($dp = 0) - { - if ($dp != 0) { - return round($this->goodnessOfFit * 100, $dp); - } - - return $this->goodnessOfFit * 100; - } - - /** - * Return the standard deviation of the residuals for this regression. - * - * @param int $dp Number of places of decimal precision to return - * - * @return float - */ - public function getStdevOfResiduals($dp = 0) - { - if ($dp != 0) { - return round($this->stdevOfResiduals, $dp); - } - - return $this->stdevOfResiduals; - } - - /** - * @param int $dp Number of places of decimal precision to return - * - * @return float - */ - public function getSSRegression($dp = 0) - { - if ($dp != 0) { - return round($this->SSRegression, $dp); - } - - return $this->SSRegression; - } - - /** - * @param int $dp Number of places of decimal precision to return - * - * @return float - */ - public function getSSResiduals($dp = 0) - { - if ($dp != 0) { - return round($this->SSResiduals, $dp); - } - - return $this->SSResiduals; - } - - /** - * @param int $dp Number of places of decimal precision to return - * - * @return float - */ - public function getDFResiduals($dp = 0) - { - if ($dp != 0) { - return round($this->DFResiduals, $dp); - } - - return $this->DFResiduals; - } - - /** - * @param int $dp Number of places of decimal precision to return - * - * @return float - */ - public function getF($dp = 0) - { - if ($dp != 0) { - return round($this->f, $dp); - } - - return $this->f; - } - - /** - * @param int $dp Number of places of decimal precision to return - * - * @return float - */ - public function getCovariance($dp = 0) - { - if ($dp != 0) { - return round($this->covariance, $dp); - } - - return $this->covariance; - } - - /** - * @param int $dp Number of places of decimal precision to return - * - * @return float - */ - public function getCorrelation($dp = 0) - { - if ($dp != 0) { - return round($this->correlation, $dp); - } - - return $this->correlation; - } - - /** - * @return float[] - */ - public function getYBestFitValues() - { - return $this->yBestFitValues; - } - - /** @var mixed */ - private static $scrutinizerZeroPointZero = 0.0; - - /** - * @param mixed $x - * @param mixed $y - */ - private static function scrutinizerLooseCompare($x, $y): bool - { - return $x == $y; - } - - /** - * @param float $sumX - * @param float $sumY - * @param float $sumX2 - * @param float $sumY2 - * @param float $sumXY - * @param float $meanX - * @param float $meanY - * @param bool|int $const - */ - protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const): void - { - $SSres = $SScov = $SStot = $SSsex = 0.0; - foreach ($this->xValues as $xKey => $xValue) { - $bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue); - - $SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY); - if ($const === true) { - $SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY); - } else { - $SStot += $this->yValues[$xKey] * $this->yValues[$xKey]; - } - $SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY); - if ($const === true) { - $SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX); - } else { - $SSsex += $this->xValues[$xKey] * $this->xValues[$xKey]; - } - } - - $this->SSResiduals = $SSres; - $this->DFResiduals = $this->valueCount - 1 - ($const === true ? 1 : 0); - - if ($this->DFResiduals == 0.0) { - $this->stdevOfResiduals = 0.0; - } else { - $this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals); - } - // Scrutinizer thinks $SSres == $SStot is always true. It is wrong. - if ($SStot == self::$scrutinizerZeroPointZero || self::scrutinizerLooseCompare($SSres, $SStot)) { - $this->goodnessOfFit = 1; - } else { - $this->goodnessOfFit = 1 - ($SSres / $SStot); - } - - $this->SSRegression = $this->goodnessOfFit * $SStot; - $this->covariance = $SScov / $this->valueCount; - $this->correlation = ($this->valueCount * $sumXY - $sumX * $sumY) / sqrt(($this->valueCount * $sumX2 - $sumX ** 2) * ($this->valueCount * $sumY2 - $sumY ** 2)); - $this->slopeSE = $this->stdevOfResiduals / sqrt($SSsex); - $this->intersectSE = $this->stdevOfResiduals * sqrt(1 / ($this->valueCount - ($sumX * $sumX) / $sumX2)); - if ($this->SSResiduals != 0.0) { - if ($this->DFResiduals == 0.0) { - $this->f = 0.0; - } else { - $this->f = $this->SSRegression / ($this->SSResiduals / $this->DFResiduals); - } - } else { - if ($this->DFResiduals == 0.0) { - $this->f = 0.0; - } else { - $this->f = $this->SSRegression / $this->DFResiduals; - } - } - } - - /** @return float|int */ - private function sumSquares(array $values) - { - return array_sum( - array_map( - function ($value) { - return $value ** 2; - }, - $values - ) - ); - } - - /** - * @param float[] $yValues - * @param float[] $xValues - */ - protected function leastSquareFit(array $yValues, array $xValues, bool $const): void - { - // calculate sums - $sumValuesX = array_sum($xValues); - $sumValuesY = array_sum($yValues); - $meanValueX = $sumValuesX / $this->valueCount; - $meanValueY = $sumValuesY / $this->valueCount; - $sumSquaresX = $this->sumSquares($xValues); - $sumSquaresY = $this->sumSquares($yValues); - $mBase = $mDivisor = 0.0; - $xy_sum = 0.0; - for ($i = 0; $i < $this->valueCount; ++$i) { - $xy_sum += $xValues[$i] * $yValues[$i]; - - if ($const === true) { - $mBase += ($xValues[$i] - $meanValueX) * ($yValues[$i] - $meanValueY); - $mDivisor += ($xValues[$i] - $meanValueX) * ($xValues[$i] - $meanValueX); - } else { - $mBase += $xValues[$i] * $yValues[$i]; - $mDivisor += $xValues[$i] * $xValues[$i]; - } - } - - // calculate slope - $this->slope = $mBase / $mDivisor; - - // calculate intersect - $this->intersect = ($const === true) ? $meanValueY - ($this->slope * $meanValueX) : 0.0; - - $this->calculateGoodnessOfFit($sumValuesX, $sumValuesY, $sumSquaresX, $sumSquaresY, $xy_sum, $meanValueX, $meanValueY, $const); - } - - /** - * Define the regression. - * - * @param float[] $yValues The set of Y-values for this regression - * @param float[] $xValues The set of X-values for this regression - */ - public function __construct($yValues, $xValues = []) - { - // Calculate number of points - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - // Define X Values if necessary - if ($xValueCount === 0) { - $xValues = range(1, $yValueCount); - } elseif ($yValueCount !== $xValueCount) { - // Ensure both arrays of points are the same size - $this->error = true; - } - - $this->valueCount = $yValueCount; - $this->xValues = $xValues; - $this->yValues = $yValues; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php deleted file mode 100644 index eb8cd746d36..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php +++ /dev/null @@ -1,119 +0,0 @@ -getIntersect() * $this->getSlope() ** ($xValue - $this->xOffset); - } - - /** - * Return the X-Value for a specified value of Y. - * - * @param float $yValue Y-Value - * - * @return float X-Value - */ - public function getValueOfXForY($yValue) - { - return log(($yValue + $this->yOffset) / $this->getIntersect()) / log($this->getSlope()); - } - - /** - * Return the Equation of the best-fit line. - * - * @param int $dp Number of places of decimal precision to display - * - * @return string - */ - public function getEquation($dp = 0) - { - $slope = $this->getSlope($dp); - $intersect = $this->getIntersect($dp); - - return 'Y = ' . $intersect . ' * ' . $slope . '^X'; - } - - /** - * Return the Slope of the line. - * - * @param int $dp Number of places of decimal precision to display - * - * @return float - */ - public function getSlope($dp = 0) - { - if ($dp != 0) { - return round(exp($this->slope), $dp); - } - - return exp($this->slope); - } - - /** - * Return the Value of X where it intersects Y = 0. - * - * @param int $dp Number of places of decimal precision to display - * - * @return float - */ - public function getIntersect($dp = 0) - { - if ($dp != 0) { - return round(exp($this->intersect), $dp); - } - - return exp($this->intersect); - } - - /** - * Execute the regression and calculate the goodness of fit for a set of X and Y data values. - * - * @param float[] $yValues The set of Y-values for this regression - * @param float[] $xValues The set of X-values for this regression - */ - private function exponentialRegression(array $yValues, array $xValues, bool $const): void - { - $adjustedYValues = array_map( - function ($value) { - return ($value < 0.0) ? 0 - log(abs($value)) : log($value); - }, - $yValues - ); - - $this->leastSquareFit($adjustedYValues, $xValues, $const); - } - - /** - * Define the regression and calculate the goodness of fit for a set of X and Y data values. - * - * @param float[] $yValues The set of Y-values for this regression - * @param float[] $xValues The set of X-values for this regression - * @param bool $const - */ - public function __construct($yValues, $xValues = [], $const = true) - { - parent::__construct($yValues, $xValues); - - if (!$this->error) { - $this->exponentialRegression($yValues, $xValues, (bool) $const); - } - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php deleted file mode 100644 index 65d6b4ff44d..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php +++ /dev/null @@ -1,80 +0,0 @@ -getIntersect() + $this->getSlope() * $xValue; - } - - /** - * Return the X-Value for a specified value of Y. - * - * @param float $yValue Y-Value - * - * @return float X-Value - */ - public function getValueOfXForY($yValue) - { - return ($yValue - $this->getIntersect()) / $this->getSlope(); - } - - /** - * Return the Equation of the best-fit line. - * - * @param int $dp Number of places of decimal precision to display - * - * @return string - */ - public function getEquation($dp = 0) - { - $slope = $this->getSlope($dp); - $intersect = $this->getIntersect($dp); - - return 'Y = ' . $intersect . ' + ' . $slope . ' * X'; - } - - /** - * Execute the regression and calculate the goodness of fit for a set of X and Y data values. - * - * @param float[] $yValues The set of Y-values for this regression - * @param float[] $xValues The set of X-values for this regression - */ - private function linearRegression(array $yValues, array $xValues, bool $const): void - { - $this->leastSquareFit($yValues, $xValues, $const); - } - - /** - * Define the regression and calculate the goodness of fit for a set of X and Y data values. - * - * @param float[] $yValues The set of Y-values for this regression - * @param float[] $xValues The set of X-values for this regression - * @param bool $const - */ - public function __construct($yValues, $xValues = [], $const = true) - { - parent::__construct($yValues, $xValues); - - if (!$this->error) { - $this->linearRegression($yValues, $xValues, (bool) $const); - } - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php deleted file mode 100644 index 2366dc636aa..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php +++ /dev/null @@ -1,87 +0,0 @@ -getIntersect() + $this->getSlope() * log($xValue - $this->xOffset); - } - - /** - * Return the X-Value for a specified value of Y. - * - * @param float $yValue Y-Value - * - * @return float X-Value - */ - public function getValueOfXForY($yValue) - { - return exp(($yValue - $this->getIntersect()) / $this->getSlope()); - } - - /** - * Return the Equation of the best-fit line. - * - * @param int $dp Number of places of decimal precision to display - * - * @return string - */ - public function getEquation($dp = 0) - { - $slope = $this->getSlope($dp); - $intersect = $this->getIntersect($dp); - - return 'Y = ' . $slope . ' * log(' . $intersect . ' * X)'; - } - - /** - * Execute the regression and calculate the goodness of fit for a set of X and Y data values. - * - * @param float[] $yValues The set of Y-values for this regression - * @param float[] $xValues The set of X-values for this regression - */ - private function logarithmicRegression(array $yValues, array $xValues, bool $const): void - { - $adjustedYValues = array_map( - function ($value) { - return ($value < 0.0) ? 0 - log(abs($value)) : log($value); - }, - $yValues - ); - - $this->leastSquareFit($adjustedYValues, $xValues, $const); - } - - /** - * Define the regression and calculate the goodness of fit for a set of X and Y data values. - * - * @param float[] $yValues The set of Y-values for this regression - * @param float[] $xValues The set of X-values for this regression - * @param bool $const - */ - public function __construct($yValues, $xValues = [], $const = true) - { - parent::__construct($yValues, $xValues); - - if (!$this->error) { - $this->logarithmicRegression($yValues, $xValues, (bool) $const); - } - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php deleted file mode 100644 index 222a4230045..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php +++ /dev/null @@ -1,219 +0,0 @@ -slope is specified where an array is expected in several places. -// But it seems that it should always be float. -// This code is probably not exercised at all in unit tests. -class PolynomialBestFit extends BestFit -{ - /** - * Algorithm type to use for best-fit - * (Name of this Trend class). - * - * @var string - */ - protected $bestFitType = 'polynomial'; - - /** - * Polynomial order. - * - * @var int - */ - protected $order = 0; - - /** - * Return the order of this polynomial. - * - * @return int - */ - public function getOrder() - { - return $this->order; - } - - /** - * Return the Y-Value for a specified value of X. - * - * @param float $xValue X-Value - * - * @return float Y-Value - */ - public function getValueOfYForX($xValue) - { - $retVal = $this->getIntersect(); - $slope = $this->getSlope(); - // Phpstan and Scrutinizer are both correct - getSlope returns float, not array. - // @phpstan-ignore-next-line - foreach ($slope as $key => $value) { - if ($value != 0.0) { - $retVal += $value * $xValue ** ($key + 1); - } - } - - return $retVal; - } - - /** - * Return the X-Value for a specified value of Y. - * - * @param float $yValue Y-Value - * - * @return float X-Value - */ - public function getValueOfXForY($yValue) - { - return ($yValue - $this->getIntersect()) / $this->getSlope(); - } - - /** - * Return the Equation of the best-fit line. - * - * @param int $dp Number of places of decimal precision to display - * - * @return string - */ - public function getEquation($dp = 0) - { - $slope = $this->getSlope($dp); - $intersect = $this->getIntersect($dp); - - $equation = 'Y = ' . $intersect; - // Phpstan and Scrutinizer are both correct - getSlope returns float, not array. - // @phpstan-ignore-next-line - foreach ($slope as $key => $value) { - if ($value != 0.0) { - $equation .= ' + ' . $value . ' * X'; - if ($key > 0) { - $equation .= '^' . ($key + 1); - } - } - } - - return $equation; - } - - /** - * Return the Slope of the line. - * - * @param int $dp Number of places of decimal precision to display - * - * @return float - */ - public function getSlope($dp = 0) - { - if ($dp != 0) { - $coefficients = []; - // Scrutinizer is correct - $this->slope is float, not array. - //* @phpstan-ignore-next-line - foreach ($this->slope as $coefficient) { - $coefficients[] = round($coefficient, $dp); - } - - // @phpstan-ignore-next-line - return $coefficients; - } - - return $this->slope; - } - - /** - * @param int $dp - * - * @return array - */ - public function getCoefficients($dp = 0) - { - // Phpstan and Scrutinizer are both correct - getSlope returns float, not array. - // @phpstan-ignore-next-line - return array_merge([$this->getIntersect($dp)], $this->getSlope($dp)); - } - - /** - * Execute the regression and calculate the goodness of fit for a set of X and Y data values. - * - * @param int $order Order of Polynomial for this regression - * @param float[] $yValues The set of Y-values for this regression - * @param float[] $xValues The set of X-values for this regression - */ - private function polynomialRegression($order, $yValues, $xValues): void - { - // calculate sums - $x_sum = array_sum($xValues); - $y_sum = array_sum($yValues); - $xx_sum = $xy_sum = $yy_sum = 0; - for ($i = 0; $i < $this->valueCount; ++$i) { - $xy_sum += $xValues[$i] * $yValues[$i]; - $xx_sum += $xValues[$i] * $xValues[$i]; - $yy_sum += $yValues[$i] * $yValues[$i]; - } - /* - * This routine uses logic from the PHP port of polyfit version 0.1 - * written by Michael Bommarito and Paul Meagher - * - * The function fits a polynomial function of order $order through - * a series of x-y data points using least squares. - * - */ - $A = []; - $B = []; - for ($i = 0; $i < $this->valueCount; ++$i) { - for ($j = 0; $j <= $order; ++$j) { - $A[$i][$j] = $xValues[$i] ** $j; - } - } - for ($i = 0; $i < $this->valueCount; ++$i) { - $B[$i] = [$yValues[$i]]; - } - $matrixA = new Matrix($A); - $matrixB = new Matrix($B); - $C = $matrixA->solve($matrixB); - - $coefficients = []; - for ($i = 0; $i < $C->rows; ++$i) { - $r = $C->getValue($i + 1, 1); // row and column are origin-1 - if (abs($r) <= 10 ** (-9)) { - $r = 0; - } - $coefficients[] = $r; - } - - $this->intersect = array_shift($coefficients); - // Phpstan (and maybe Scrutinizer) are correct - //* @phpstan-ignore-next-line - $this->slope = $coefficients; - - $this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, 0, 0, 0); - foreach ($this->xValues as $xKey => $xValue) { - $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue); - } - } - - /** - * Define the regression and calculate the goodness of fit for a set of X and Y data values. - * - * @param int $order Order of Polynomial for this regression - * @param float[] $yValues The set of Y-values for this regression - * @param float[] $xValues The set of X-values for this regression - */ - public function __construct($order, $yValues, $xValues = []) - { - parent::__construct($yValues, $xValues); - - if (!$this->error) { - if ($order < $this->valueCount) { - $this->bestFitType .= '_' . $order; - $this->order = $order; - $this->polynomialRegression($order, $yValues, $xValues); - if (($this->getGoodnessOfFit() < 0.0) || ($this->getGoodnessOfFit() > 1.0)) { - $this->error = true; - } - } else { - $this->error = true; - } - } - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php deleted file mode 100644 index cafd01158e9..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php +++ /dev/null @@ -1,109 +0,0 @@ -getIntersect() * ($xValue - $this->xOffset) ** $this->getSlope(); - } - - /** - * Return the X-Value for a specified value of Y. - * - * @param float $yValue Y-Value - * - * @return float X-Value - */ - public function getValueOfXForY($yValue) - { - return (($yValue + $this->yOffset) / $this->getIntersect()) ** (1 / $this->getSlope()); - } - - /** - * Return the Equation of the best-fit line. - * - * @param int $dp Number of places of decimal precision to display - * - * @return string - */ - public function getEquation($dp = 0) - { - $slope = $this->getSlope($dp); - $intersect = $this->getIntersect($dp); - - return 'Y = ' . $intersect . ' * X^' . $slope; - } - - /** - * Return the Value of X where it intersects Y = 0. - * - * @param int $dp Number of places of decimal precision to display - * - * @return float - */ - public function getIntersect($dp = 0) - { - if ($dp != 0) { - return round(exp($this->intersect), $dp); - } - - return exp($this->intersect); - } - - /** - * Execute the regression and calculate the goodness of fit for a set of X and Y data values. - * - * @param float[] $yValues The set of Y-values for this regression - * @param float[] $xValues The set of X-values for this regression - */ - private function powerRegression(array $yValues, array $xValues, bool $const): void - { - $adjustedYValues = array_map( - function ($value) { - return ($value < 0.0) ? 0 - log(abs($value)) : log($value); - }, - $yValues - ); - $adjustedXValues = array_map( - function ($value) { - return ($value < 0.0) ? 0 - log(abs($value)) : log($value); - }, - $xValues - ); - - $this->leastSquareFit($adjustedYValues, $adjustedXValues, $const); - } - - /** - * Define the regression and calculate the goodness of fit for a set of X and Y data values. - * - * @param float[] $yValues The set of Y-values for this regression - * @param float[] $xValues The set of X-values for this regression - * @param bool $const - */ - public function __construct($yValues, $xValues = [], $const = true) - { - parent::__construct($yValues, $xValues); - - if (!$this->error) { - $this->powerRegression($yValues, $xValues, (bool) $const); - } - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php deleted file mode 100644 index 117848c778b..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php +++ /dev/null @@ -1,130 +0,0 @@ -getGoodnessOfFit(); - } - if ($trendType != self::TREND_BEST_FIT_NO_POLY) { - foreach (self::$trendTypePolynomialOrders as $trendMethod) { - $order = (int) substr($trendMethod, -1); - $bestFit[$trendMethod] = new PolynomialBestFit($order, $yValues, $xValues); - if ($bestFit[$trendMethod]->getError()) { - unset($bestFit[$trendMethod]); - } else { - $bestFitValue[$trendMethod] = $bestFit[$trendMethod]->getGoodnessOfFit(); - } - } - } - // Determine which of our Trend lines is the best fit, and then we return the instance of that Trend class - arsort($bestFitValue); - $bestFitType = key($bestFitValue); - - return $bestFit[$bestFitType]; - default: - return false; - } - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php deleted file mode 100644 index d9f403d7ada..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php +++ /dev/null @@ -1,104 +0,0 @@ -openMemory(); - } else { - // Create temporary filename - if ($temporaryStorageFolder === null) { - $temporaryStorageFolder = File::sysGetTempDir(); - } - $this->tempFileName = (string) @tempnam($temporaryStorageFolder, 'xml'); - - // Open storage - if (empty($this->tempFileName) || $this->openUri($this->tempFileName) === false) { - // Fallback to memory... - $this->openMemory(); - } - } - - // Set default values - if (self::$debugEnabled) { - $this->setIndent(true); - } - } - - /** - * Destructor. - */ - public function __destruct() - { - // Unlink temporary files - // There is nothing reasonable to do if unlink fails. - if ($this->tempFileName != '') { - /** @scrutinizer ignore-unhandled */ - @unlink($this->tempFileName); - } - } - - public function __wakeup(): void - { - $this->tempFileName = ''; - - throw new SpreadsheetException('Unserialize not permitted'); - } - - /** - * Get written data. - * - * @return string - */ - public function getData() - { - if ($this->tempFileName == '') { - return $this->outputMemory(true); - } - $this->flush(); - - return file_get_contents($this->tempFileName) ?: ''; - } - - /** - * Wrapper method for writeRaw. - * - * @param null|string|string[] $rawTextData - * - * @return bool - */ - public function writeRawData($rawTextData) - { - if (is_array($rawTextData)) { - $rawTextData = implode("\n", $rawTextData); - } - - return $this->writeRaw(htmlspecialchars($rawTextData ?? '')); - } -} From 0c143ff7ce3a869e2d8fc11d660bb1c62b83a523 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 31 Oct 2023 09:26:21 +0800 Subject: [PATCH 06/16] MDL-81031 core: Add Slim Framework --- lib/classes/component.php | 2 + lib/nikic/fast-route/LICENSE | 31 ++ lib/nikic/fast-route/README.md | 313 +++++++++++++++ lib/nikic/fast-route/composer.json | 24 ++ .../fast-route/src/BadRouteException.php | 7 + lib/nikic/fast-route/src/DataGenerator.php | 26 ++ .../src/DataGenerator/CharCountBased.php | 31 ++ .../src/DataGenerator/GroupCountBased.php | 30 ++ .../src/DataGenerator/GroupPosBased.php | 27 ++ .../src/DataGenerator/MarkBased.php | 27 ++ .../src/DataGenerator/RegexBasedAbstract.php | 186 +++++++++ lib/nikic/fast-route/src/Dispatcher.php | 26 ++ .../src/Dispatcher/CharCountBased.php | 31 ++ .../src/Dispatcher/GroupCountBased.php | 31 ++ .../src/Dispatcher/GroupPosBased.php | 33 ++ .../fast-route/src/Dispatcher/MarkBased.php | 31 ++ .../src/Dispatcher/RegexBasedAbstract.php | 88 +++++ lib/nikic/fast-route/src/Route.php | 47 +++ lib/nikic/fast-route/src/RouteCollector.php | 152 ++++++++ lib/nikic/fast-route/src/RouteParser.php | 37 ++ lib/nikic/fast-route/src/RouteParser/Std.php | 87 +++++ lib/nikic/fast-route/src/bootstrap.php | 12 + lib/nikic/fast-route/src/functions.php | 74 ++++ lib/nikic/readme_moodle.txt | 13 + lib/slim/readme_moodle.txt | 5 + lib/slim/slim/CHANGELOG.md | 237 ++++++++++++ lib/slim/slim/LICENSE.md | 19 + lib/slim/slim/MAINTAINERS.md | 17 + lib/slim/slim/SECURITY.md | 14 + lib/slim/slim/Slim/App.php | 216 +++++++++++ lib/slim/slim/Slim/CallableResolver.php | 193 ++++++++++ .../slim/Slim/Error/AbstractErrorRenderer.php | 46 +++ .../Error/Renderers/HtmlErrorRenderer.php | 84 ++++ .../Error/Renderers/JsonErrorRenderer.php | 56 +++ .../Renderers/PlainTextErrorRenderer.php | 59 +++ .../Slim/Error/Renderers/XmlErrorRenderer.php | 54 +++ .../Exception/HttpBadRequestException.php | 28 ++ .../slim/Slim/Exception/HttpException.php | 64 ++++ .../Slim/Exception/HttpForbiddenException.php | 27 ++ .../slim/Slim/Exception/HttpGoneException.php | 27 ++ .../HttpInternalServerErrorException.php | 27 ++ .../HttpMethodNotAllowedException.php | 52 +++ .../Slim/Exception/HttpNotFoundException.php | 27 ++ .../Exception/HttpNotImplementedException.php | 27 ++ .../Exception/HttpSpecializedException.php | 31 ++ .../HttpTooManyRequestsException.php | 28 ++ .../Exception/HttpUnauthorizedException.php | 27 ++ lib/slim/slim/Slim/Factory/AppFactory.php | 206 ++++++++++ .../Slim/Factory/Psr17/GuzzlePsr17Factory.php | 19 + .../Factory/Psr17/HttpSoftPsr17Factory.php | 19 + .../Psr17/LaminasDiactorosPsr17Factory.php | 19 + .../Slim/Factory/Psr17/NyholmPsr17Factory.php | 36 ++ .../slim/Slim/Factory/Psr17/Psr17Factory.php | 101 +++++ .../Factory/Psr17/Psr17FactoryProvider.php | 53 +++ .../Factory/Psr17/ServerRequestCreator.php | 44 +++ .../Factory/Psr17/SlimHttpPsr17Factory.php | 39 ++ .../Psr17/SlimHttpServerRequestCreator.php | 56 +++ .../Slim/Factory/Psr17/SlimPsr17Factory.php | 19 + .../Factory/ServerRequestCreatorFactory.php | 87 +++++ lib/slim/slim/Slim/Handlers/ErrorHandler.php | 308 +++++++++++++++ .../Handlers/Strategies/RequestHandler.php | 48 +++ .../Handlers/Strategies/RequestResponse.php | 40 ++ .../Strategies/RequestResponseArgs.php | 38 ++ .../Strategies/RequestResponseNamedArgs.php | 44 +++ .../AdvancedCallableResolverInterface.php | 28 ++ .../Interfaces/CallableResolverInterface.php | 21 + .../Slim/Interfaces/DispatcherInterface.php | 28 ++ .../Slim/Interfaces/ErrorHandlerInterface.php | 26 ++ .../Interfaces/ErrorRendererInterface.php | 18 + .../InvocationStrategyInterface.php | 37 ++ .../MiddlewareDispatcherInterface.php | 42 ++ .../Slim/Interfaces/Psr17FactoryInterface.php | 48 +++ .../Psr17FactoryProviderInterface.php | 26 ++ ...uestHandlerInvocationStrategyInterface.php | 15 + .../Interfaces/RouteCollectorInterface.php | 102 +++++ .../RouteCollectorProxyInterface.php | 118 ++++++ .../Slim/Interfaces/RouteGroupInterface.php | 41 ++ .../slim/Slim/Interfaces/RouteInterface.php | 123 ++++++ .../Slim/Interfaces/RouteParserInterface.php | 52 +++ .../Interfaces/RouteResolverInterface.php | 17 + .../ServerRequestCreatorInterface.php | 18 + lib/slim/slim/Slim/Logger.php | 32 ++ .../Slim/Middleware/BodyParsingMiddleware.php | 196 ++++++++++ .../Middleware/ContentLengthMiddleware.php | 32 ++ .../slim/Slim/Middleware/ErrorMiddleware.php | 212 +++++++++++ .../Middleware/MethodOverrideMiddleware.php | 43 +++ .../Middleware/OutputBufferingMiddleware.php | 74 ++++ .../Slim/Middleware/RoutingMiddleware.php | 98 +++++ lib/slim/slim/Slim/MiddlewareDispatcher.php | 275 +++++++++++++ lib/slim/slim/Slim/ResponseEmitter.php | 136 +++++++ lib/slim/slim/Slim/Routing/Dispatcher.php | 78 ++++ .../slim/Slim/Routing/FastRouteDispatcher.php | 109 ++++++ lib/slim/slim/Slim/Routing/Route.php | 360 ++++++++++++++++++ lib/slim/slim/Slim/Routing/RouteCollector.php | 293 ++++++++++++++ .../slim/Slim/Routing/RouteCollectorProxy.php | 187 +++++++++ lib/slim/slim/Slim/Routing/RouteContext.php | 88 +++++ lib/slim/slim/Slim/Routing/RouteGroup.php | 104 +++++ lib/slim/slim/Slim/Routing/RouteParser.php | 127 ++++++ lib/slim/slim/Slim/Routing/RouteResolver.php | 56 +++ lib/slim/slim/Slim/Routing/RouteRunner.php | 70 ++++ lib/slim/slim/Slim/Routing/RoutingResults.php | 112 ++++++ lib/slim/slim/composer.json | 102 +++++ lib/thirdpartylibs.xml | 15 + 103 files changed, 7466 insertions(+) create mode 100644 lib/nikic/fast-route/LICENSE create mode 100644 lib/nikic/fast-route/README.md create mode 100644 lib/nikic/fast-route/composer.json create mode 100644 lib/nikic/fast-route/src/BadRouteException.php create mode 100644 lib/nikic/fast-route/src/DataGenerator.php create mode 100644 lib/nikic/fast-route/src/DataGenerator/CharCountBased.php create mode 100644 lib/nikic/fast-route/src/DataGenerator/GroupCountBased.php create mode 100644 lib/nikic/fast-route/src/DataGenerator/GroupPosBased.php create mode 100644 lib/nikic/fast-route/src/DataGenerator/MarkBased.php create mode 100644 lib/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php create mode 100644 lib/nikic/fast-route/src/Dispatcher.php create mode 100644 lib/nikic/fast-route/src/Dispatcher/CharCountBased.php create mode 100644 lib/nikic/fast-route/src/Dispatcher/GroupCountBased.php create mode 100644 lib/nikic/fast-route/src/Dispatcher/GroupPosBased.php create mode 100644 lib/nikic/fast-route/src/Dispatcher/MarkBased.php create mode 100644 lib/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php create mode 100644 lib/nikic/fast-route/src/Route.php create mode 100644 lib/nikic/fast-route/src/RouteCollector.php create mode 100644 lib/nikic/fast-route/src/RouteParser.php create mode 100644 lib/nikic/fast-route/src/RouteParser/Std.php create mode 100644 lib/nikic/fast-route/src/bootstrap.php create mode 100644 lib/nikic/fast-route/src/functions.php create mode 100644 lib/nikic/readme_moodle.txt create mode 100644 lib/slim/readme_moodle.txt create mode 100644 lib/slim/slim/CHANGELOG.md create mode 100644 lib/slim/slim/LICENSE.md create mode 100644 lib/slim/slim/MAINTAINERS.md create mode 100644 lib/slim/slim/SECURITY.md create mode 100644 lib/slim/slim/Slim/App.php create mode 100644 lib/slim/slim/Slim/CallableResolver.php create mode 100644 lib/slim/slim/Slim/Error/AbstractErrorRenderer.php create mode 100644 lib/slim/slim/Slim/Error/Renderers/HtmlErrorRenderer.php create mode 100644 lib/slim/slim/Slim/Error/Renderers/JsonErrorRenderer.php create mode 100644 lib/slim/slim/Slim/Error/Renderers/PlainTextErrorRenderer.php create mode 100644 lib/slim/slim/Slim/Error/Renderers/XmlErrorRenderer.php create mode 100644 lib/slim/slim/Slim/Exception/HttpBadRequestException.php create mode 100644 lib/slim/slim/Slim/Exception/HttpException.php create mode 100644 lib/slim/slim/Slim/Exception/HttpForbiddenException.php create mode 100644 lib/slim/slim/Slim/Exception/HttpGoneException.php create mode 100644 lib/slim/slim/Slim/Exception/HttpInternalServerErrorException.php create mode 100644 lib/slim/slim/Slim/Exception/HttpMethodNotAllowedException.php create mode 100644 lib/slim/slim/Slim/Exception/HttpNotFoundException.php create mode 100644 lib/slim/slim/Slim/Exception/HttpNotImplementedException.php create mode 100644 lib/slim/slim/Slim/Exception/HttpSpecializedException.php create mode 100644 lib/slim/slim/Slim/Exception/HttpTooManyRequestsException.php create mode 100644 lib/slim/slim/Slim/Exception/HttpUnauthorizedException.php create mode 100644 lib/slim/slim/Slim/Factory/AppFactory.php create mode 100644 lib/slim/slim/Slim/Factory/Psr17/GuzzlePsr17Factory.php create mode 100644 lib/slim/slim/Slim/Factory/Psr17/HttpSoftPsr17Factory.php create mode 100644 lib/slim/slim/Slim/Factory/Psr17/LaminasDiactorosPsr17Factory.php create mode 100644 lib/slim/slim/Slim/Factory/Psr17/NyholmPsr17Factory.php create mode 100644 lib/slim/slim/Slim/Factory/Psr17/Psr17Factory.php create mode 100644 lib/slim/slim/Slim/Factory/Psr17/Psr17FactoryProvider.php create mode 100644 lib/slim/slim/Slim/Factory/Psr17/ServerRequestCreator.php create mode 100644 lib/slim/slim/Slim/Factory/Psr17/SlimHttpPsr17Factory.php create mode 100644 lib/slim/slim/Slim/Factory/Psr17/SlimHttpServerRequestCreator.php create mode 100644 lib/slim/slim/Slim/Factory/Psr17/SlimPsr17Factory.php create mode 100644 lib/slim/slim/Slim/Factory/ServerRequestCreatorFactory.php create mode 100644 lib/slim/slim/Slim/Handlers/ErrorHandler.php create mode 100644 lib/slim/slim/Slim/Handlers/Strategies/RequestHandler.php create mode 100644 lib/slim/slim/Slim/Handlers/Strategies/RequestResponse.php create mode 100644 lib/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php create mode 100644 lib/slim/slim/Slim/Handlers/Strategies/RequestResponseNamedArgs.php create mode 100644 lib/slim/slim/Slim/Interfaces/AdvancedCallableResolverInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/CallableResolverInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/DispatcherInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/ErrorHandlerInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/ErrorRendererInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/InvocationStrategyInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/MiddlewareDispatcherInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/Psr17FactoryInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/Psr17FactoryProviderInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/RequestHandlerInvocationStrategyInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/RouteCollectorInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/RouteCollectorProxyInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/RouteGroupInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/RouteInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/RouteParserInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/RouteResolverInterface.php create mode 100644 lib/slim/slim/Slim/Interfaces/ServerRequestCreatorInterface.php create mode 100644 lib/slim/slim/Slim/Logger.php create mode 100644 lib/slim/slim/Slim/Middleware/BodyParsingMiddleware.php create mode 100644 lib/slim/slim/Slim/Middleware/ContentLengthMiddleware.php create mode 100644 lib/slim/slim/Slim/Middleware/ErrorMiddleware.php create mode 100644 lib/slim/slim/Slim/Middleware/MethodOverrideMiddleware.php create mode 100644 lib/slim/slim/Slim/Middleware/OutputBufferingMiddleware.php create mode 100644 lib/slim/slim/Slim/Middleware/RoutingMiddleware.php create mode 100644 lib/slim/slim/Slim/MiddlewareDispatcher.php create mode 100644 lib/slim/slim/Slim/ResponseEmitter.php create mode 100644 lib/slim/slim/Slim/Routing/Dispatcher.php create mode 100644 lib/slim/slim/Slim/Routing/FastRouteDispatcher.php create mode 100644 lib/slim/slim/Slim/Routing/Route.php create mode 100644 lib/slim/slim/Slim/Routing/RouteCollector.php create mode 100644 lib/slim/slim/Slim/Routing/RouteCollectorProxy.php create mode 100644 lib/slim/slim/Slim/Routing/RouteContext.php create mode 100644 lib/slim/slim/Slim/Routing/RouteGroup.php create mode 100644 lib/slim/slim/Slim/Routing/RouteParser.php create mode 100644 lib/slim/slim/Slim/Routing/RouteResolver.php create mode 100644 lib/slim/slim/Slim/Routing/RouteRunner.php create mode 100644 lib/slim/slim/Slim/Routing/RoutingResults.php create mode 100644 lib/slim/slim/composer.json diff --git a/lib/classes/component.php b/lib/classes/component.php index beeba5b815a..ab495b07671 100644 --- a/lib/classes/component.php +++ b/lib/classes/component.php @@ -145,6 +145,8 @@ class component { \Matrix::class => 'lib/phpspreadsheet/markbaker/classes/src', \PhpOffice\PhpSpreadsheet::class => 'lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet', \Invoker::class => 'lib/php-di/invoker/src', + \FastRoute::class => 'lib/nikic/fast-route/src', + \Slim::class => 'lib/slim/slim/Slim', ]; /** diff --git a/lib/nikic/fast-route/LICENSE b/lib/nikic/fast-route/LICENSE new file mode 100644 index 00000000000..478e7641e93 --- /dev/null +++ b/lib/nikic/fast-route/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2013 by Nikita Popov. + +Some rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/nikic/fast-route/README.md b/lib/nikic/fast-route/README.md new file mode 100644 index 00000000000..91bd4664e03 --- /dev/null +++ b/lib/nikic/fast-route/README.md @@ -0,0 +1,313 @@ +FastRoute - Fast request router for PHP +======================================= + +This library provides a fast implementation of a regular expression based router. [Blog post explaining how the +implementation works and why it is fast.][blog_post] + +Install +------- + +To install with composer: + +```sh +composer require nikic/fast-route +``` + +Requires PHP 5.4 or newer. + +Usage +----- + +Here's a basic usage example: + +```php +addRoute('GET', '/users', 'get_all_users_handler'); + // {id} must be a number (\d+) + $r->addRoute('GET', '/user/{id:\d+}', 'get_user_handler'); + // The /{title} suffix is optional + $r->addRoute('GET', '/articles/{id:\d+}[/{title}]', 'get_article_handler'); +}); + +// Fetch method and URI from somewhere +$httpMethod = $_SERVER['REQUEST_METHOD']; +$uri = $_SERVER['REQUEST_URI']; + +// Strip query string (?foo=bar) and decode URI +if (false !== $pos = strpos($uri, '?')) { + $uri = substr($uri, 0, $pos); +} +$uri = rawurldecode($uri); + +$routeInfo = $dispatcher->dispatch($httpMethod, $uri); +switch ($routeInfo[0]) { + case FastRoute\Dispatcher::NOT_FOUND: + // ... 404 Not Found + break; + case FastRoute\Dispatcher::METHOD_NOT_ALLOWED: + $allowedMethods = $routeInfo[1]; + // ... 405 Method Not Allowed + break; + case FastRoute\Dispatcher::FOUND: + $handler = $routeInfo[1]; + $vars = $routeInfo[2]; + // ... call $handler with $vars + break; +} +``` + +### Defining routes + +The routes are defined by calling the `FastRoute\simpleDispatcher()` function, which accepts +a callable taking a `FastRoute\RouteCollector` instance. The routes are added by calling +`addRoute()` on the collector instance: + +```php +$r->addRoute($method, $routePattern, $handler); +``` + +The `$method` is an uppercase HTTP method string for which a certain route should match. It +is possible to specify multiple valid methods using an array: + +```php +// These two calls +$r->addRoute('GET', '/test', 'handler'); +$r->addRoute('POST', '/test', 'handler'); +// Are equivalent to this one call +$r->addRoute(['GET', 'POST'], '/test', 'handler'); +``` + +By default the `$routePattern` uses a syntax where `{foo}` specifies a placeholder with name `foo` +and matching the regex `[^/]+`. To adjust the pattern the placeholder matches, you can specify +a custom pattern by writing `{bar:[0-9]+}`. Some examples: + +```php +// Matches /user/42, but not /user/xyz +$r->addRoute('GET', '/user/{id:\d+}', 'handler'); + +// Matches /user/foobar, but not /user/foo/bar +$r->addRoute('GET', '/user/{name}', 'handler'); + +// Matches /user/foo/bar as well +$r->addRoute('GET', '/user/{name:.+}', 'handler'); +``` + +Custom patterns for route placeholders cannot use capturing groups. For example `{lang:(en|de)}` +is not a valid placeholder, because `()` is a capturing group. Instead you can use either +`{lang:en|de}` or `{lang:(?:en|de)}`. + +Furthermore parts of the route enclosed in `[...]` are considered optional, so that `/foo[bar]` +will match both `/foo` and `/foobar`. Optional parts are only supported in a trailing position, +not in the middle of a route. + +```php +// This route +$r->addRoute('GET', '/user/{id:\d+}[/{name}]', 'handler'); +// Is equivalent to these two routes +$r->addRoute('GET', '/user/{id:\d+}', 'handler'); +$r->addRoute('GET', '/user/{id:\d+}/{name}', 'handler'); + +// Multiple nested optional parts are possible as well +$r->addRoute('GET', '/user[/{id:\d+}[/{name}]]', 'handler'); + +// This route is NOT valid, because optional parts can only occur at the end +$r->addRoute('GET', '/user[/{id:\d+}]/{name}', 'handler'); +``` + +The `$handler` parameter does not necessarily have to be a callback, it could also be a controller +class name or any other kind of data you wish to associate with the route. FastRoute only tells you +which handler corresponds to your URI, how you interpret it is up to you. + +#### Shorcut methods for common request methods + +For the `GET`, `POST`, `PUT`, `PATCH`, `DELETE` and `HEAD` request methods shortcut methods are available. For example: + +```php +$r->get('/get-route', 'get_handler'); +$r->post('/post-route', 'post_handler'); +``` + +Is equivalent to: + +```php +$r->addRoute('GET', '/get-route', 'get_handler'); +$r->addRoute('POST', '/post-route', 'post_handler'); +``` + +#### Route Groups + +Additionally, you can specify routes inside of a group. All routes defined inside a group will have a common prefix. + +For example, defining your routes as: + +```php +$r->addGroup('/admin', function (RouteCollector $r) { + $r->addRoute('GET', '/do-something', 'handler'); + $r->addRoute('GET', '/do-another-thing', 'handler'); + $r->addRoute('GET', '/do-something-else', 'handler'); +}); +``` + +Will have the same result as: + + ```php +$r->addRoute('GET', '/admin/do-something', 'handler'); +$r->addRoute('GET', '/admin/do-another-thing', 'handler'); +$r->addRoute('GET', '/admin/do-something-else', 'handler'); + ``` + +Nested groups are also supported, in which case the prefixes of all the nested groups are combined. + +### Caching + +The reason `simpleDispatcher` accepts a callback for defining the routes is to allow seamless +caching. By using `cachedDispatcher` instead of `simpleDispatcher` you can cache the generated +routing data and construct the dispatcher from the cached information: + +```php +addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); + $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); + $r->addRoute('GET', '/user/{name}', 'handler2'); +}, [ + 'cacheFile' => __DIR__ . '/route.cache', /* required */ + 'cacheDisabled' => IS_DEBUG_ENABLED, /* optional, enabled by default */ +]); +``` + +The second parameter to the function is an options array, which can be used to specify the cache +file location, among other things. + +### Dispatching a URI + +A URI is dispatched by calling the `dispatch()` method of the created dispatcher. This method +accepts the HTTP method and a URI. Getting those two bits of information (and normalizing them +appropriately) is your job - this library is not bound to the PHP web SAPIs. + +The `dispatch()` method returns an array whose first element contains a status code. It is one +of `Dispatcher::NOT_FOUND`, `Dispatcher::METHOD_NOT_ALLOWED` and `Dispatcher::FOUND`. For the +method not allowed status the second array element contains a list of HTTP methods allowed for +the supplied URI. For example: + + [FastRoute\Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'POST']] + +> **NOTE:** The HTTP specification requires that a `405 Method Not Allowed` response include the +`Allow:` header to detail available methods for the requested resource. Applications using FastRoute +should use the second array element to add this header when relaying a 405 response. + +For the found status the second array element is the handler that was associated with the route +and the third array element is a dictionary of placeholder names to their values. For example: + + /* Routing against GET /user/nikic/42 */ + + [FastRoute\Dispatcher::FOUND, 'handler0', ['name' => 'nikic', 'id' => '42']] + +### Overriding the route parser and dispatcher + +The routing process makes use of three components: A route parser, a data generator and a +dispatcher. The three components adhere to the following interfaces: + +```php + 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', +]); +``` + +The above options array corresponds to the defaults. By replacing `GroupCountBased` by +`GroupPosBased` you could switch to a different dispatching strategy. + +### A Note on HEAD Requests + +The HTTP spec requires servers to [support both GET and HEAD methods][2616-511]: + +> The methods GET and HEAD MUST be supported by all general-purpose servers + +To avoid forcing users to manually register HEAD routes for each resource we fallback to matching an +available GET route for a given resource. The PHP web SAPI transparently removes the entity body +from HEAD responses so this behavior has no effect on the vast majority of users. + +However, implementers using FastRoute outside the web SAPI environment (e.g. a custom server) MUST +NOT send entity bodies generated in response to HEAD requests. If you are a non-SAPI user this is +*your responsibility*; FastRoute has no purview to prevent you from breaking HTTP in such cases. + +Finally, note that applications MAY always specify their own HEAD method route for a given +resource to bypass this behavior entirely. + +### Credits + +This library is based on a router that [Levi Morrison][levi] implemented for the Aerys server. + +A large number of tests, as well as HTTP compliance considerations, were provided by [Daniel Lowrey][rdlowrey]. + + +[2616-511]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1 "RFC 2616 Section 5.1.1" +[blog_post]: http://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html +[levi]: https://github.com/morrisonlevi +[rdlowrey]: https://github.com/rdlowrey diff --git a/lib/nikic/fast-route/composer.json b/lib/nikic/fast-route/composer.json new file mode 100644 index 00000000000..fb446a2abfb --- /dev/null +++ b/lib/nikic/fast-route/composer.json @@ -0,0 +1,24 @@ +{ + "name": "nikic/fast-route", + "description": "Fast request router for PHP", + "keywords": ["routing", "router"], + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "autoload": { + "psr-4": { + "FastRoute\\": "src/" + }, + "files": ["src/functions.php"] + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + } +} diff --git a/lib/nikic/fast-route/src/BadRouteException.php b/lib/nikic/fast-route/src/BadRouteException.php new file mode 100644 index 00000000000..62262ec66f6 --- /dev/null +++ b/lib/nikic/fast-route/src/BadRouteException.php @@ -0,0 +1,7 @@ + $route) { + $suffixLen++; + $suffix .= "\t"; + + $regexes[] = '(?:' . $regex . '/(\t{' . $suffixLen . '})\t{' . ($count - $suffixLen) . '})'; + $routeMap[$suffix] = [$route->handler, $route->variables]; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'suffix' => '/' . $suffix, 'routeMap' => $routeMap]; + } +} diff --git a/lib/nikic/fast-route/src/DataGenerator/GroupCountBased.php b/lib/nikic/fast-route/src/DataGenerator/GroupCountBased.php new file mode 100644 index 00000000000..54d9a05e20b --- /dev/null +++ b/lib/nikic/fast-route/src/DataGenerator/GroupCountBased.php @@ -0,0 +1,30 @@ + $route) { + $numVariables = count($route->variables); + $numGroups = max($numGroups, $numVariables); + + $regexes[] = $regex . str_repeat('()', $numGroups - $numVariables); + $routeMap[$numGroups + 1] = [$route->handler, $route->variables]; + + ++$numGroups; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} diff --git a/lib/nikic/fast-route/src/DataGenerator/GroupPosBased.php b/lib/nikic/fast-route/src/DataGenerator/GroupPosBased.php new file mode 100644 index 00000000000..fc4dc0aff63 --- /dev/null +++ b/lib/nikic/fast-route/src/DataGenerator/GroupPosBased.php @@ -0,0 +1,27 @@ + $route) { + $regexes[] = $regex; + $routeMap[$offset] = [$route->handler, $route->variables]; + + $offset += count($route->variables); + } + + $regex = '~^(?:' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} diff --git a/lib/nikic/fast-route/src/DataGenerator/MarkBased.php b/lib/nikic/fast-route/src/DataGenerator/MarkBased.php new file mode 100644 index 00000000000..0aebed9a161 --- /dev/null +++ b/lib/nikic/fast-route/src/DataGenerator/MarkBased.php @@ -0,0 +1,27 @@ + $route) { + $regexes[] = $regex . '(*MARK:' . $markName . ')'; + $routeMap[$markName] = [$route->handler, $route->variables]; + + ++$markName; + } + + $regex = '~^(?|' . implode('|', $regexes) . ')$~'; + return ['regex' => $regex, 'routeMap' => $routeMap]; + } +} diff --git a/lib/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php b/lib/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php new file mode 100644 index 00000000000..64572905992 --- /dev/null +++ b/lib/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php @@ -0,0 +1,186 @@ +isStaticRoute($routeData)) { + $this->addStaticRoute($httpMethod, $routeData, $handler); + } else { + $this->addVariableRoute($httpMethod, $routeData, $handler); + } + } + + /** + * @return mixed[] + */ + public function getData() + { + if (empty($this->methodToRegexToRoutesMap)) { + return [$this->staticRoutes, []]; + } + + return [$this->staticRoutes, $this->generateVariableRouteData()]; + } + + /** + * @return mixed[] + */ + private function generateVariableRouteData() + { + $data = []; + foreach ($this->methodToRegexToRoutesMap as $method => $regexToRoutesMap) { + $chunkSize = $this->computeChunkSize(count($regexToRoutesMap)); + $chunks = array_chunk($regexToRoutesMap, $chunkSize, true); + $data[$method] = array_map([$this, 'processChunk'], $chunks); + } + return $data; + } + + /** + * @param int + * @return int + */ + private function computeChunkSize($count) + { + $numParts = max(1, round($count / $this->getApproxChunkSize())); + return (int) ceil($count / $numParts); + } + + /** + * @param mixed[] + * @return bool + */ + private function isStaticRoute($routeData) + { + return count($routeData) === 1 && is_string($routeData[0]); + } + + private function addStaticRoute($httpMethod, $routeData, $handler) + { + $routeStr = $routeData[0]; + + if (isset($this->staticRoutes[$httpMethod][$routeStr])) { + throw new BadRouteException(sprintf( + 'Cannot register two routes matching "%s" for method "%s"', + $routeStr, $httpMethod + )); + } + + if (isset($this->methodToRegexToRoutesMap[$httpMethod])) { + foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) { + if ($route->matches($routeStr)) { + throw new BadRouteException(sprintf( + 'Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"', + $routeStr, $route->regex, $httpMethod + )); + } + } + } + + $this->staticRoutes[$httpMethod][$routeStr] = $handler; + } + + private function addVariableRoute($httpMethod, $routeData, $handler) + { + list($regex, $variables) = $this->buildRegexForRoute($routeData); + + if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) { + throw new BadRouteException(sprintf( + 'Cannot register two routes matching "%s" for method "%s"', + $regex, $httpMethod + )); + } + + $this->methodToRegexToRoutesMap[$httpMethod][$regex] = new Route( + $httpMethod, $handler, $regex, $variables + ); + } + + /** + * @param mixed[] + * @return mixed[] + */ + private function buildRegexForRoute($routeData) + { + $regex = ''; + $variables = []; + foreach ($routeData as $part) { + if (is_string($part)) { + $regex .= preg_quote($part, '~'); + continue; + } + + list($varName, $regexPart) = $part; + + if (isset($variables[$varName])) { + throw new BadRouteException(sprintf( + 'Cannot use the same placeholder "%s" twice', $varName + )); + } + + if ($this->regexHasCapturingGroups($regexPart)) { + throw new BadRouteException(sprintf( + 'Regex "%s" for parameter "%s" contains a capturing group', + $regexPart, $varName + )); + } + + $variables[$varName] = $varName; + $regex .= '(' . $regexPart . ')'; + } + + return [$regex, $variables]; + } + + /** + * @param string + * @return bool + */ + private function regexHasCapturingGroups($regex) + { + if (false === strpos($regex, '(')) { + // Needs to have at least a ( to contain a capturing group + return false; + } + + // Semi-accurate detection for capturing groups + return (bool) preg_match( + '~ + (?: + \(\?\( + | \[ [^\]\\\\]* (?: \\\\ . [^\]\\\\]* )* \] + | \\\\ . + ) (*SKIP)(*FAIL) | + \( + (?! + \? (?! <(?![!=]) | P< | \' ) + | \* + ) + ~x', + $regex + ); + } +} diff --git a/lib/nikic/fast-route/src/Dispatcher.php b/lib/nikic/fast-route/src/Dispatcher.php new file mode 100644 index 00000000000..4ae72a356b6 --- /dev/null +++ b/lib/nikic/fast-route/src/Dispatcher.php @@ -0,0 +1,26 @@ + 'value', ...]] + * + * @param string $httpMethod + * @param string $uri + * + * @return array + */ + public function dispatch($httpMethod, $uri); +} diff --git a/lib/nikic/fast-route/src/Dispatcher/CharCountBased.php b/lib/nikic/fast-route/src/Dispatcher/CharCountBased.php new file mode 100644 index 00000000000..ef1eec1345e --- /dev/null +++ b/lib/nikic/fast-route/src/Dispatcher/CharCountBased.php @@ -0,0 +1,31 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri . $data['suffix'], $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][end($matches)]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/lib/nikic/fast-route/src/Dispatcher/GroupCountBased.php b/lib/nikic/fast-route/src/Dispatcher/GroupCountBased.php new file mode 100644 index 00000000000..493e7a94f08 --- /dev/null +++ b/lib/nikic/fast-route/src/Dispatcher/GroupCountBased.php @@ -0,0 +1,31 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][count($matches)]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/lib/nikic/fast-route/src/Dispatcher/GroupPosBased.php b/lib/nikic/fast-route/src/Dispatcher/GroupPosBased.php new file mode 100644 index 00000000000..498220ed6f7 --- /dev/null +++ b/lib/nikic/fast-route/src/Dispatcher/GroupPosBased.php @@ -0,0 +1,33 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + // find first non-empty match + for ($i = 1; '' === $matches[$i]; ++$i); + + list($handler, $varNames) = $data['routeMap'][$i]; + + $vars = []; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[$i++]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/lib/nikic/fast-route/src/Dispatcher/MarkBased.php b/lib/nikic/fast-route/src/Dispatcher/MarkBased.php new file mode 100644 index 00000000000..22eb09ba575 --- /dev/null +++ b/lib/nikic/fast-route/src/Dispatcher/MarkBased.php @@ -0,0 +1,31 @@ +staticRouteMap, $this->variableRouteData) = $data; + } + + protected function dispatchVariableRoute($routeData, $uri) + { + foreach ($routeData as $data) { + if (!preg_match($data['regex'], $uri, $matches)) { + continue; + } + + list($handler, $varNames) = $data['routeMap'][$matches['MARK']]; + + $vars = []; + $i = 0; + foreach ($varNames as $varName) { + $vars[$varName] = $matches[++$i]; + } + return [self::FOUND, $handler, $vars]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/lib/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php b/lib/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php new file mode 100644 index 00000000000..206e879f7b2 --- /dev/null +++ b/lib/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php @@ -0,0 +1,88 @@ +staticRouteMap[$httpMethod][$uri])) { + $handler = $this->staticRouteMap[$httpMethod][$uri]; + return [self::FOUND, $handler, []]; + } + + $varRouteData = $this->variableRouteData; + if (isset($varRouteData[$httpMethod])) { + $result = $this->dispatchVariableRoute($varRouteData[$httpMethod], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + + // For HEAD requests, attempt fallback to GET + if ($httpMethod === 'HEAD') { + if (isset($this->staticRouteMap['GET'][$uri])) { + $handler = $this->staticRouteMap['GET'][$uri]; + return [self::FOUND, $handler, []]; + } + if (isset($varRouteData['GET'])) { + $result = $this->dispatchVariableRoute($varRouteData['GET'], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + } + + // If nothing else matches, try fallback routes + if (isset($this->staticRouteMap['*'][$uri])) { + $handler = $this->staticRouteMap['*'][$uri]; + return [self::FOUND, $handler, []]; + } + if (isset($varRouteData['*'])) { + $result = $this->dispatchVariableRoute($varRouteData['*'], $uri); + if ($result[0] === self::FOUND) { + return $result; + } + } + + // Find allowed methods for this URI by matching against all other HTTP methods as well + $allowedMethods = []; + + foreach ($this->staticRouteMap as $method => $uriMap) { + if ($method !== $httpMethod && isset($uriMap[$uri])) { + $allowedMethods[] = $method; + } + } + + foreach ($varRouteData as $method => $routeData) { + if ($method === $httpMethod) { + continue; + } + + $result = $this->dispatchVariableRoute($routeData, $uri); + if ($result[0] === self::FOUND) { + $allowedMethods[] = $method; + } + } + + // If there are no allowed methods the route simply does not exist + if ($allowedMethods) { + return [self::METHOD_NOT_ALLOWED, $allowedMethods]; + } + + return [self::NOT_FOUND]; + } +} diff --git a/lib/nikic/fast-route/src/Route.php b/lib/nikic/fast-route/src/Route.php new file mode 100644 index 00000000000..e1bf7dd9722 --- /dev/null +++ b/lib/nikic/fast-route/src/Route.php @@ -0,0 +1,47 @@ +httpMethod = $httpMethod; + $this->handler = $handler; + $this->regex = $regex; + $this->variables = $variables; + } + + /** + * Tests whether this route matches the given string. + * + * @param string $str + * + * @return bool + */ + public function matches($str) + { + $regex = '~^' . $this->regex . '$~'; + return (bool) preg_match($regex, $str); + } +} diff --git a/lib/nikic/fast-route/src/RouteCollector.php b/lib/nikic/fast-route/src/RouteCollector.php new file mode 100644 index 00000000000..c1c1762d8d3 --- /dev/null +++ b/lib/nikic/fast-route/src/RouteCollector.php @@ -0,0 +1,152 @@ +routeParser = $routeParser; + $this->dataGenerator = $dataGenerator; + $this->currentGroupPrefix = ''; + } + + /** + * Adds a route to the collection. + * + * The syntax used in the $route string depends on the used route parser. + * + * @param string|string[] $httpMethod + * @param string $route + * @param mixed $handler + */ + public function addRoute($httpMethod, $route, $handler) + { + $route = $this->currentGroupPrefix . $route; + $routeDatas = $this->routeParser->parse($route); + foreach ((array) $httpMethod as $method) { + foreach ($routeDatas as $routeData) { + $this->dataGenerator->addRoute($method, $routeData, $handler); + } + } + } + + /** + * Create a route group with a common prefix. + * + * All routes created in the passed callback will have the given group prefix prepended. + * + * @param string $prefix + * @param callable $callback + */ + public function addGroup($prefix, callable $callback) + { + $previousGroupPrefix = $this->currentGroupPrefix; + $this->currentGroupPrefix = $previousGroupPrefix . $prefix; + $callback($this); + $this->currentGroupPrefix = $previousGroupPrefix; + } + + /** + * Adds a GET route to the collection + * + * This is simply an alias of $this->addRoute('GET', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function get($route, $handler) + { + $this->addRoute('GET', $route, $handler); + } + + /** + * Adds a POST route to the collection + * + * This is simply an alias of $this->addRoute('POST', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function post($route, $handler) + { + $this->addRoute('POST', $route, $handler); + } + + /** + * Adds a PUT route to the collection + * + * This is simply an alias of $this->addRoute('PUT', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function put($route, $handler) + { + $this->addRoute('PUT', $route, $handler); + } + + /** + * Adds a DELETE route to the collection + * + * This is simply an alias of $this->addRoute('DELETE', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function delete($route, $handler) + { + $this->addRoute('DELETE', $route, $handler); + } + + /** + * Adds a PATCH route to the collection + * + * This is simply an alias of $this->addRoute('PATCH', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function patch($route, $handler) + { + $this->addRoute('PATCH', $route, $handler); + } + + /** + * Adds a HEAD route to the collection + * + * This is simply an alias of $this->addRoute('HEAD', $route, $handler) + * + * @param string $route + * @param mixed $handler + */ + public function head($route, $handler) + { + $this->addRoute('HEAD', $route, $handler); + } + + /** + * Returns the collected route data, as provided by the data generator. + * + * @return array + */ + public function getData() + { + return $this->dataGenerator->getData(); + } +} diff --git a/lib/nikic/fast-route/src/RouteParser.php b/lib/nikic/fast-route/src/RouteParser.php new file mode 100644 index 00000000000..6a7685cfed6 --- /dev/null +++ b/lib/nikic/fast-route/src/RouteParser.php @@ -0,0 +1,37 @@ + $segment) { + if ($segment === '' && $n !== 0) { + throw new BadRouteException('Empty optional part'); + } + + $currentRoute .= $segment; + $routeDatas[] = $this->parsePlaceholders($currentRoute); + } + return $routeDatas; + } + + /** + * Parses a route string that does not contain optional segments. + * + * @param string + * @return mixed[] + */ + private function parsePlaceholders($route) + { + if (!preg_match_all( + '~' . self::VARIABLE_REGEX . '~x', $route, $matches, + PREG_OFFSET_CAPTURE | PREG_SET_ORDER + )) { + return [$route]; + } + + $offset = 0; + $routeData = []; + foreach ($matches as $set) { + if ($set[0][1] > $offset) { + $routeData[] = substr($route, $offset, $set[0][1] - $offset); + } + $routeData[] = [ + $set[1][0], + isset($set[2]) ? trim($set[2][0]) : self::DEFAULT_DISPATCH_REGEX + ]; + $offset = $set[0][1] + strlen($set[0][0]); + } + + if ($offset !== strlen($route)) { + $routeData[] = substr($route, $offset); + } + + return $routeData; + } +} diff --git a/lib/nikic/fast-route/src/bootstrap.php b/lib/nikic/fast-route/src/bootstrap.php new file mode 100644 index 00000000000..0bce3a42071 --- /dev/null +++ b/lib/nikic/fast-route/src/bootstrap.php @@ -0,0 +1,12 @@ + 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', + 'routeCollector' => 'FastRoute\\RouteCollector', + ]; + + /** @var RouteCollector $routeCollector */ + $routeCollector = new $options['routeCollector']( + new $options['routeParser'], new $options['dataGenerator'] + ); + $routeDefinitionCallback($routeCollector); + + return new $options['dispatcher']($routeCollector->getData()); + } + + /** + * @param callable $routeDefinitionCallback + * @param array $options + * + * @return Dispatcher + */ + function cachedDispatcher(callable $routeDefinitionCallback, array $options = []) + { + $options += [ + 'routeParser' => 'FastRoute\\RouteParser\\Std', + 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', + 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', + 'routeCollector' => 'FastRoute\\RouteCollector', + 'cacheDisabled' => false, + ]; + + if (!isset($options['cacheFile'])) { + throw new \LogicException('Must specify "cacheFile" option'); + } + + if (!$options['cacheDisabled'] && file_exists($options['cacheFile'])) { + $dispatchData = require $options['cacheFile']; + if (!is_array($dispatchData)) { + throw new \RuntimeException('Invalid cache file "' . $options['cacheFile'] . '"'); + } + return new $options['dispatcher']($dispatchData); + } + + $routeCollector = new $options['routeCollector']( + new $options['routeParser'], new $options['dataGenerator'] + ); + $routeDefinitionCallback($routeCollector); + + /** @var RouteCollector $routeCollector */ + $dispatchData = $routeCollector->getData(); + if (!$options['cacheDisabled']) { + file_put_contents( + $options['cacheFile'], + 'routeResolver = $routeResolver ?? new RouteResolver($this->routeCollector); + $routeRunner = new RouteRunner($this->routeResolver, $this->routeCollector->getRouteParser(), $this); + + if (!$middlewareDispatcher) { + $middlewareDispatcher = new MiddlewareDispatcher($routeRunner, $this->callableResolver, $container); + } else { + $middlewareDispatcher->seedMiddlewareStack($routeRunner); + } + + $this->middlewareDispatcher = $middlewareDispatcher; + } + + /** + * @return RouteResolverInterface + */ + public function getRouteResolver(): RouteResolverInterface + { + return $this->routeResolver; + } + + /** + * @return MiddlewareDispatcherInterface + */ + public function getMiddlewareDispatcher(): MiddlewareDispatcherInterface + { + return $this->middlewareDispatcher; + } + + /** + * @param MiddlewareInterface|string|callable $middleware + */ + public function add($middleware): self + { + $this->middlewareDispatcher->add($middleware); + return $this; + } + + /** + * @param MiddlewareInterface $middleware + */ + public function addMiddleware(MiddlewareInterface $middleware): self + { + $this->middlewareDispatcher->addMiddleware($middleware); + return $this; + } + + /** + * Add the Slim built-in routing middleware to the app middleware stack + * + * This method can be used to control middleware order and is not required for default routing operation. + * + * @return RoutingMiddleware + */ + public function addRoutingMiddleware(): RoutingMiddleware + { + $routingMiddleware = new RoutingMiddleware( + $this->getRouteResolver(), + $this->getRouteCollector()->getRouteParser() + ); + $this->add($routingMiddleware); + return $routingMiddleware; + } + + /** + * Add the Slim built-in error middleware to the app middleware stack + * + * @param bool $displayErrorDetails + * @param bool $logErrors + * @param bool $logErrorDetails + * @param LoggerInterface|null $logger + * + * @return ErrorMiddleware + */ + public function addErrorMiddleware( + bool $displayErrorDetails, + bool $logErrors, + bool $logErrorDetails, + ?LoggerInterface $logger = null + ): ErrorMiddleware { + $errorMiddleware = new ErrorMiddleware( + $this->getCallableResolver(), + $this->getResponseFactory(), + $displayErrorDetails, + $logErrors, + $logErrorDetails, + $logger + ); + $this->add($errorMiddleware); + return $errorMiddleware; + } + + /** + * Add the Slim body parsing middleware to the app middleware stack + * + * @param callable[] $bodyParsers + * + * @return BodyParsingMiddleware + */ + public function addBodyParsingMiddleware(array $bodyParsers = []): BodyParsingMiddleware + { + $bodyParsingMiddleware = new BodyParsingMiddleware($bodyParsers); + $this->add($bodyParsingMiddleware); + return $bodyParsingMiddleware; + } + + /** + * Run application + * + * This method traverses the application middleware stack and then sends the + * resultant Response object to the HTTP client. + * + * @param ServerRequestInterface|null $request + * @return void + */ + public function run(?ServerRequestInterface $request = null): void + { + if (!$request) { + $serverRequestCreator = ServerRequestCreatorFactory::create(); + $request = $serverRequestCreator->createServerRequestFromGlobals(); + } + + $response = $this->handle($request); + $responseEmitter = new ResponseEmitter(); + $responseEmitter->emit($response); + } + + /** + * Handle a request + * + * This method traverses the application middleware stack and then returns the + * resultant Response object. + * + * @param ServerRequestInterface $request + * @return ResponseInterface + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + $response = $this->middlewareDispatcher->handle($request); + + /** + * This is to be in compliance with RFC 2616, Section 9. + * If the incoming request method is HEAD, we need to ensure that the response body + * is empty as the request may fall back on a GET route handler due to FastRoute's + * routing logic which could potentially append content to the response body + * https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4 + */ + $method = strtoupper($request->getMethod()); + if ($method === 'HEAD') { + $emptyBody = $this->responseFactory->createResponse()->getBody(); + return $response->withBody($emptyBody); + } + + return $response; + } +} diff --git a/lib/slim/slim/Slim/CallableResolver.php b/lib/slim/slim/Slim/CallableResolver.php new file mode 100644 index 00000000000..66f225de890 --- /dev/null +++ b/lib/slim/slim/Slim/CallableResolver.php @@ -0,0 +1,193 @@ +container = $container; + } + + /** + * {@inheritdoc} + */ + public function resolve($toResolve): callable + { + $toResolve = $this->prepareToResolve($toResolve); + if (is_callable($toResolve)) { + return $this->bindToContainer($toResolve); + } + $resolved = $toResolve; + if (is_string($toResolve)) { + $resolved = $this->resolveSlimNotation($toResolve); + $resolved[1] ??= '__invoke'; + } + $callable = $this->assertCallable($resolved, $toResolve); + return $this->bindToContainer($callable); + } + + /** + * {@inheritdoc} + */ + public function resolveRoute($toResolve): callable + { + return $this->resolveByPredicate($toResolve, [$this, 'isRoute'], 'handle'); + } + + /** + * {@inheritdoc} + */ + public function resolveMiddleware($toResolve): callable + { + return $this->resolveByPredicate($toResolve, [$this, 'isMiddleware'], 'process'); + } + + /** + * @param string|callable $toResolve + * + * @throws RuntimeException + */ + private function resolveByPredicate($toResolve, callable $predicate, string $defaultMethod): callable + { + $toResolve = $this->prepareToResolve($toResolve); + if (is_callable($toResolve)) { + return $this->bindToContainer($toResolve); + } + $resolved = $toResolve; + if ($predicate($toResolve)) { + $resolved = [$toResolve, $defaultMethod]; + } + if (is_string($toResolve)) { + [$instance, $method] = $this->resolveSlimNotation($toResolve); + if ($method === null && $predicate($instance)) { + $method = $defaultMethod; + } + $resolved = [$instance, $method ?? '__invoke']; + } + $callable = $this->assertCallable($resolved, $toResolve); + return $this->bindToContainer($callable); + } + + /** + * @param mixed $toResolve + */ + private function isRoute($toResolve): bool + { + return $toResolve instanceof RequestHandlerInterface; + } + + /** + * @param mixed $toResolve + */ + private function isMiddleware($toResolve): bool + { + return $toResolve instanceof MiddlewareInterface; + } + + /** + * @throws RuntimeException + * + * @return array{object, string|null} [Instance, Method Name] + */ + private function resolveSlimNotation(string $toResolve): array + { + preg_match(CallableResolver::$callablePattern, $toResolve, $matches); + [$class, $method] = $matches ? [$matches[1], $matches[2]] : [$toResolve, null]; + + /** @var string $class */ + /** @var string|null $method */ + if ($this->container && $this->container->has($class)) { + $instance = $this->container->get($class); + if (!is_object($instance)) { + throw new RuntimeException(sprintf('%s container entry is not an object', $class)); + } + } else { + if (!class_exists($class)) { + if ($method) { + $class .= '::' . $method . '()'; + } + throw new RuntimeException(sprintf('Callable %s does not exist', $class)); + } + $instance = new $class($this->container); + } + return [$instance, $method]; + } + + /** + * @param mixed $resolved + * @param mixed $toResolve + * + * @throws RuntimeException + */ + private function assertCallable($resolved, $toResolve): callable + { + if (!is_callable($resolved)) { + if (is_callable($toResolve) || is_object($toResolve) || is_array($toResolve)) { + $formatedToResolve = ($toResolveJson = json_encode($toResolve)) !== false ? $toResolveJson : ''; + } else { + $formatedToResolve = is_string($toResolve) ? $toResolve : ''; + } + throw new RuntimeException(sprintf('%s is not resolvable', $formatedToResolve)); + } + return $resolved; + } + + private function bindToContainer(callable $callable): callable + { + if (is_array($callable) && $callable[0] instanceof Closure) { + $callable = $callable[0]; + } + if ($this->container && $callable instanceof Closure) { + /** @var Closure $callable */ + $callable = $callable->bindTo($this->container); + } + return $callable; + } + + /** + * @param string|callable $toResolve + * @return string|callable + */ + private function prepareToResolve($toResolve) + { + if (!is_array($toResolve)) { + return $toResolve; + } + $candidate = $toResolve; + $class = array_shift($candidate); + $method = array_shift($candidate); + if (is_string($class) && is_string($method)) { + return $class . ':' . $method; + } + return $toResolve; + } +} diff --git a/lib/slim/slim/Slim/Error/AbstractErrorRenderer.php b/lib/slim/slim/Slim/Error/AbstractErrorRenderer.php new file mode 100644 index 00000000000..90b290d410f --- /dev/null +++ b/lib/slim/slim/Slim/Error/AbstractErrorRenderer.php @@ -0,0 +1,46 @@ +getTitle(); + } + + return $this->defaultErrorTitle; + } + + protected function getErrorDescription(Throwable $exception): string + { + if ($exception instanceof HttpException) { + return $exception->getDescription(); + } + + return $this->defaultErrorDescription; + } +} diff --git a/lib/slim/slim/Slim/Error/Renderers/HtmlErrorRenderer.php b/lib/slim/slim/Slim/Error/Renderers/HtmlErrorRenderer.php new file mode 100644 index 00000000000..e030522aa87 --- /dev/null +++ b/lib/slim/slim/Slim/Error/Renderers/HtmlErrorRenderer.php @@ -0,0 +1,84 @@ +The application could not run because of the following error:

'; + $html .= '

Details

'; + $html .= $this->renderExceptionFragment($exception); + } else { + $html = "

{$this->getErrorDescription($exception)}

"; + } + + return $this->renderHtmlBody($this->getErrorTitle($exception), $html); + } + + private function renderExceptionFragment(Throwable $exception): string + { + $html = sprintf('
Type: %s
', get_class($exception)); + + /** @var int|string $code */ + $code = $exception->getCode(); + $html .= sprintf('
Code: %s
', $code); + + $html .= sprintf('
Message: %s
', htmlentities($exception->getMessage())); + + $html .= sprintf('
File: %s
', $exception->getFile()); + + $html .= sprintf('
Line: %s
', $exception->getLine()); + + $html .= '

Trace

'; + $html .= sprintf('
%s
', htmlentities($exception->getTraceAsString())); + + return $html; + } + + public function renderHtmlBody(string $title = '', string $html = ''): string + { + return sprintf( + '' . + '' . + ' ' . + ' ' . + ' ' . + ' %s' . + ' ' . + ' ' . + ' ' . + '

%s

' . + '
%s
' . + ' Go Back' . + ' ' . + '', + $title, + $title, + $html + ); + } +} diff --git a/lib/slim/slim/Slim/Error/Renderers/JsonErrorRenderer.php b/lib/slim/slim/Slim/Error/Renderers/JsonErrorRenderer.php new file mode 100644 index 00000000000..63d905b3cd3 --- /dev/null +++ b/lib/slim/slim/Slim/Error/Renderers/JsonErrorRenderer.php @@ -0,0 +1,56 @@ + $this->getErrorTitle($exception)]; + + if ($displayErrorDetails) { + $error['exception'] = []; + do { + $error['exception'][] = $this->formatExceptionFragment($exception); + } while ($exception = $exception->getPrevious()); + } + + return (string) json_encode($error, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } + + /** + * @return array + */ + private function formatExceptionFragment(Throwable $exception): array + { + /** @var int|string $code */ + $code = $exception->getCode(); + return [ + 'type' => get_class($exception), + 'code' => $code, + 'message' => $exception->getMessage(), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + ]; + } +} diff --git a/lib/slim/slim/Slim/Error/Renderers/PlainTextErrorRenderer.php b/lib/slim/slim/Slim/Error/Renderers/PlainTextErrorRenderer.php new file mode 100644 index 00000000000..3d80c74be96 --- /dev/null +++ b/lib/slim/slim/Slim/Error/Renderers/PlainTextErrorRenderer.php @@ -0,0 +1,59 @@ +getErrorTitle($exception)}\n"; + + if ($displayErrorDetails) { + $text .= $this->formatExceptionFragment($exception); + + while ($exception = $exception->getPrevious()) { + $text .= "\nPrevious Error:\n"; + $text .= $this->formatExceptionFragment($exception); + } + } + + return $text; + } + + private function formatExceptionFragment(Throwable $exception): string + { + $text = sprintf("Type: %s\n", get_class($exception)); + + $code = $exception->getCode(); + /** @var int|string $code */ + $text .= sprintf("Code: %s\n", $code); + + $text .= sprintf("Message: %s\n", htmlentities($exception->getMessage())); + + $text .= sprintf("File: %s\n", $exception->getFile()); + + $text .= sprintf("Line: %s\n", $exception->getLine()); + + $text .= sprintf('Trace: %s', $exception->getTraceAsString()); + + return $text; + } +} diff --git a/lib/slim/slim/Slim/Error/Renderers/XmlErrorRenderer.php b/lib/slim/slim/Slim/Error/Renderers/XmlErrorRenderer.php new file mode 100644 index 00000000000..1171b79b2d4 --- /dev/null +++ b/lib/slim/slim/Slim/Error/Renderers/XmlErrorRenderer.php @@ -0,0 +1,54 @@ +\n"; + $xml .= "\n " . $this->createCdataSection($this->getErrorTitle($exception)) . "\n"; + + if ($displayErrorDetails) { + do { + $xml .= " \n"; + $xml .= ' ' . get_class($exception) . "\n"; + $xml .= ' ' . $exception->getCode() . "\n"; + $xml .= ' ' . $this->createCdataSection($exception->getMessage()) . "\n"; + $xml .= ' ' . $exception->getFile() . "\n"; + $xml .= ' ' . $exception->getLine() . "\n"; + $xml .= " \n"; + } while ($exception = $exception->getPrevious()); + } + + $xml .= ''; + + return $xml; + } + + /** + * Returns a CDATA section with the given content. + */ + private function createCdataSection(string $content): string + { + return sprintf('', str_replace(']]>', ']]]]>', $content)); + } +} diff --git a/lib/slim/slim/Slim/Exception/HttpBadRequestException.php b/lib/slim/slim/Slim/Exception/HttpBadRequestException.php new file mode 100644 index 00000000000..9be551cf999 --- /dev/null +++ b/lib/slim/slim/Slim/Exception/HttpBadRequestException.php @@ -0,0 +1,28 @@ +request = $request; + } + + public function getRequest(): ServerRequestInterface + { + return $this->request; + } + + public function getTitle(): string + { + return $this->title; + } + + public function setTitle(string $title): self + { + $this->title = $title; + return $this; + } + + public function getDescription(): string + { + return $this->description; + } + + public function setDescription(string $description): self + { + $this->description = $description; + return $this; + } +} diff --git a/lib/slim/slim/Slim/Exception/HttpForbiddenException.php b/lib/slim/slim/Slim/Exception/HttpForbiddenException.php new file mode 100644 index 00000000000..dd3bb230a30 --- /dev/null +++ b/lib/slim/slim/Slim/Exception/HttpForbiddenException.php @@ -0,0 +1,27 @@ +allowedMethods; + } + + /** + * @param string[] $methods + */ + public function setAllowedMethods(array $methods): self + { + $this->allowedMethods = $methods; + $this->message = 'Method not allowed. Must be one of: ' . implode(', ', $methods); + return $this; + } +} diff --git a/lib/slim/slim/Slim/Exception/HttpNotFoundException.php b/lib/slim/slim/Slim/Exception/HttpNotFoundException.php new file mode 100644 index 00000000000..865146d68f8 --- /dev/null +++ b/lib/slim/slim/Slim/Exception/HttpNotFoundException.php @@ -0,0 +1,27 @@ +message = $message; + } + + parent::__construct($request, $this->message, $this->code, $previous); + } +} diff --git a/lib/slim/slim/Slim/Exception/HttpTooManyRequestsException.php b/lib/slim/slim/Slim/Exception/HttpTooManyRequestsException.php new file mode 100644 index 00000000000..be4b29bb3c6 --- /dev/null +++ b/lib/slim/slim/Slim/Exception/HttpTooManyRequestsException.php @@ -0,0 +1,28 @@ +has(ResponseFactoryInterface::class) + && ( + $responseFactoryFromContainer = $container->get(ResponseFactoryInterface::class) + ) instanceof ResponseFactoryInterface + ? $responseFactoryFromContainer + : self::determineResponseFactory(); + + $callableResolver = $container->has(CallableResolverInterface::class) + && ( + $callableResolverFromContainer = $container->get(CallableResolverInterface::class) + ) instanceof CallableResolverInterface + ? $callableResolverFromContainer + : null; + + $routeCollector = $container->has(RouteCollectorInterface::class) + && ( + $routeCollectorFromContainer = $container->get(RouteCollectorInterface::class) + ) instanceof RouteCollectorInterface + ? $routeCollectorFromContainer + : null; + + $routeResolver = $container->has(RouteResolverInterface::class) + && ( + $routeResolverFromContainer = $container->get(RouteResolverInterface::class) + ) instanceof RouteResolverInterface + ? $routeResolverFromContainer + : null; + + $middlewareDispatcher = $container->has(MiddlewareDispatcherInterface::class) + && ( + $middlewareDispatcherFromContainer = $container->get(MiddlewareDispatcherInterface::class) + ) instanceof MiddlewareDispatcherInterface + ? $middlewareDispatcherFromContainer + : null; + + return new App( + $responseFactory, + $container, + $callableResolver, + $routeCollector, + $routeResolver, + $middlewareDispatcher + ); + } + + /** + * @throws RuntimeException + */ + public static function determineResponseFactory(): ResponseFactoryInterface + { + if (static::$responseFactory) { + if (static::$streamFactory) { + return static::attemptResponseFactoryDecoration(static::$responseFactory, static::$streamFactory); + } + return static::$responseFactory; + } + + $psr17FactoryProvider = static::$psr17FactoryProvider ?? new Psr17FactoryProvider(); + + /** @var Psr17Factory $psr17factory */ + foreach ($psr17FactoryProvider->getFactories() as $psr17factory) { + if ($psr17factory::isResponseFactoryAvailable()) { + $responseFactory = $psr17factory::getResponseFactory(); + + if (static::$streamFactory || $psr17factory::isStreamFactoryAvailable()) { + $streamFactory = static::$streamFactory ?? $psr17factory::getStreamFactory(); + return static::attemptResponseFactoryDecoration($responseFactory, $streamFactory); + } + + return $responseFactory; + } + } + + throw new RuntimeException( + "Could not detect any PSR-17 ResponseFactory implementations. " . + "Please install a supported implementation in order to use `AppFactory::create()`. " . + "See https://github.com/slimphp/Slim/blob/4.x/README.md for a list of supported implementations." + ); + } + + protected static function attemptResponseFactoryDecoration( + ResponseFactoryInterface $responseFactory, + StreamFactoryInterface $streamFactory + ): ResponseFactoryInterface { + if ( + static::$slimHttpDecoratorsAutomaticDetectionEnabled + && SlimHttpPsr17Factory::isResponseFactoryAvailable() + ) { + return SlimHttpPsr17Factory::createDecoratedResponseFactory($responseFactory, $streamFactory); + } + + return $responseFactory; + } + + public static function setPsr17FactoryProvider(Psr17FactoryProviderInterface $psr17FactoryProvider): void + { + static::$psr17FactoryProvider = $psr17FactoryProvider; + } + + public static function setResponseFactory(ResponseFactoryInterface $responseFactory): void + { + static::$responseFactory = $responseFactory; + } + + public static function setStreamFactory(StreamFactoryInterface $streamFactory): void + { + static::$streamFactory = $streamFactory; + } + + public static function setContainer(ContainerInterface $container): void + { + static::$container = $container; + } + + public static function setCallableResolver(CallableResolverInterface $callableResolver): void + { + static::$callableResolver = $callableResolver; + } + + public static function setRouteCollector(RouteCollectorInterface $routeCollector): void + { + static::$routeCollector = $routeCollector; + } + + public static function setRouteResolver(RouteResolverInterface $routeResolver): void + { + static::$routeResolver = $routeResolver; + } + + public static function setMiddlewareDispatcher(MiddlewareDispatcherInterface $middlewareDispatcher): void + { + static::$middlewareDispatcher = $middlewareDispatcher; + } + + public static function setSlimHttpDecoratorsAutomaticDetection(bool $enabled): void + { + static::$slimHttpDecoratorsAutomaticDetectionEnabled = $enabled; + } +} diff --git a/lib/slim/slim/Slim/Factory/Psr17/GuzzlePsr17Factory.php b/lib/slim/slim/Slim/Factory/Psr17/GuzzlePsr17Factory.php new file mode 100644 index 00000000000..32a548a6720 --- /dev/null +++ b/lib/slim/slim/Slim/Factory/Psr17/GuzzlePsr17Factory.php @@ -0,0 +1,19 @@ +serverRequestCreator = $serverRequestCreator; + $this->serverRequestCreatorMethod = $serverRequestCreatorMethod; + } + + /** + * {@inheritdoc} + */ + public function createServerRequestFromGlobals(): ServerRequestInterface + { + /** @var callable $callable */ + $callable = [$this->serverRequestCreator, $this->serverRequestCreatorMethod]; + return (Closure::fromCallable($callable))(); + } +} diff --git a/lib/slim/slim/Slim/Factory/Psr17/SlimHttpPsr17Factory.php b/lib/slim/slim/Slim/Factory/Psr17/SlimHttpPsr17Factory.php new file mode 100644 index 00000000000..5d636318bc8 --- /dev/null +++ b/lib/slim/slim/Slim/Factory/Psr17/SlimHttpPsr17Factory.php @@ -0,0 +1,39 @@ +serverRequestCreator = $serverRequestCreator; + } + + /** + * {@inheritdoc} + */ + public function createServerRequestFromGlobals(): ServerRequestInterface + { + if (!static::isServerRequestDecoratorAvailable()) { + throw new RuntimeException('The Slim-Http ServerRequest decorator is not available.'); + } + + $request = $this->serverRequestCreator->createServerRequestFromGlobals(); + + if ( + !(( + $decoratedServerRequest = new static::$serverRequestDecoratorClass($request) + ) instanceof ServerRequestInterface) + ) { + throw new RuntimeException(get_called_class() . ' could not instantiate a decorated server request.'); + } + + return $decoratedServerRequest; + } + + public static function isServerRequestDecoratorAvailable(): bool + { + return class_exists(static::$serverRequestDecoratorClass); + } +} diff --git a/lib/slim/slim/Slim/Factory/Psr17/SlimPsr17Factory.php b/lib/slim/slim/Slim/Factory/Psr17/SlimPsr17Factory.php new file mode 100644 index 00000000000..46c46f9ce3b --- /dev/null +++ b/lib/slim/slim/Slim/Factory/Psr17/SlimPsr17Factory.php @@ -0,0 +1,19 @@ +getFactories() as $psr17Factory) { + if ($psr17Factory::isServerRequestCreatorAvailable()) { + $serverRequestCreator = $psr17Factory::getServerRequestCreator(); + return static::attemptServerRequestCreatorDecoration($serverRequestCreator); + } + } + + throw new RuntimeException( + "Could not detect any ServerRequest creator implementations. " . + "Please install a supported implementation in order to use `App::run()` " . + "without having to pass in a `ServerRequest` object. " . + "See https://github.com/slimphp/Slim/blob/4.x/README.md for a list of supported implementations." + ); + } + + protected static function attemptServerRequestCreatorDecoration( + ServerRequestCreatorInterface $serverRequestCreator + ): ServerRequestCreatorInterface { + if ( + static::$slimHttpDecoratorsAutomaticDetectionEnabled + && SlimHttpServerRequestCreator::isServerRequestDecoratorAvailable() + ) { + return new SlimHttpServerRequestCreator($serverRequestCreator); + } + + return $serverRequestCreator; + } + + public static function setPsr17FactoryProvider(Psr17FactoryProviderInterface $psr17FactoryProvider): void + { + static::$psr17FactoryProvider = $psr17FactoryProvider; + } + + public static function setServerRequestCreator(ServerRequestCreatorInterface $serverRequestCreator): void + { + self::$serverRequestCreator = $serverRequestCreator; + } + + public static function setSlimHttpDecoratorsAutomaticDetection(bool $enabled): void + { + static::$slimHttpDecoratorsAutomaticDetectionEnabled = $enabled; + } +} diff --git a/lib/slim/slim/Slim/Handlers/ErrorHandler.php b/lib/slim/slim/Slim/Handlers/ErrorHandler.php new file mode 100644 index 00000000000..f9606e364ba --- /dev/null +++ b/lib/slim/slim/Slim/Handlers/ErrorHandler.php @@ -0,0 +1,308 @@ + + */ + protected array $errorRenderers = [ + 'application/json' => JsonErrorRenderer::class, + 'application/xml' => XmlErrorRenderer::class, + 'text/xml' => XmlErrorRenderer::class, + 'text/html' => HtmlErrorRenderer::class, + 'text/plain' => PlainTextErrorRenderer::class, + ]; + + protected bool $displayErrorDetails = false; + + protected bool $logErrors; + + protected bool $logErrorDetails = false; + + protected ?string $contentType = null; + + protected ?string $method = null; + + protected ServerRequestInterface $request; + + protected Throwable $exception; + + protected int $statusCode; + + protected CallableResolverInterface $callableResolver; + + protected ResponseFactoryInterface $responseFactory; + + protected LoggerInterface $logger; + + public function __construct( + CallableResolverInterface $callableResolver, + ResponseFactoryInterface $responseFactory, + ?LoggerInterface $logger = null + ) { + $this->callableResolver = $callableResolver; + $this->responseFactory = $responseFactory; + $this->logger = $logger ?: $this->getDefaultLogger(); + } + + /** + * Invoke error handler + * + * @param ServerRequestInterface $request The most recent Request object + * @param Throwable $exception The caught Exception object + * @param bool $displayErrorDetails Whether or not to display the error details + * @param bool $logErrors Whether or not to log errors + * @param bool $logErrorDetails Whether or not to log error details + */ + public function __invoke( + ServerRequestInterface $request, + Throwable $exception, + bool $displayErrorDetails, + bool $logErrors, + bool $logErrorDetails + ): ResponseInterface { + $this->displayErrorDetails = $displayErrorDetails; + $this->logErrors = $logErrors; + $this->logErrorDetails = $logErrorDetails; + $this->request = $request; + $this->exception = $exception; + $this->method = $request->getMethod(); + $this->statusCode = $this->determineStatusCode(); + if ($this->contentType === null) { + $this->contentType = $this->determineContentType($request); + } + + if ($logErrors) { + $this->writeToErrorLog(); + } + + return $this->respond(); + } + + /** + * Force the content type for all error handler responses. + * + * @param string|null $contentType The content type + */ + public function forceContentType(?string $contentType): void + { + $this->contentType = $contentType; + } + + protected function determineStatusCode(): int + { + if ($this->method === 'OPTIONS') { + return 200; + } + + if ($this->exception instanceof HttpException) { + return $this->exception->getCode(); + } + + return 500; + } + + /** + * Determine which content type we know about is wanted using Accept header + * + * Note: This method is a bare-bones implementation designed specifically for + * Slim's error handling requirements. Consider a fully-feature solution such + * as willdurand/negotiation for any other situation. + */ + protected function determineContentType(ServerRequestInterface $request): ?string + { + $acceptHeader = $request->getHeaderLine('Accept'); + $selectedContentTypes = array_intersect( + explode(',', $acceptHeader), + array_keys($this->errorRenderers) + ); + $count = count($selectedContentTypes); + + if ($count) { + $current = current($selectedContentTypes); + + /** + * Ensure other supported content types take precedence over text/plain + * when multiple content types are provided via Accept header. + */ + if ($current === 'text/plain' && $count > 1) { + $next = next($selectedContentTypes); + if (is_string($next)) { + return $next; + } + } + + if (is_string($current)) { + return $current; + } + } + + if (preg_match('/\+(json|xml)/', $acceptHeader, $matches)) { + $mediaType = 'application/' . $matches[1]; + if (array_key_exists($mediaType, $this->errorRenderers)) { + return $mediaType; + } + } + + return null; + } + + /** + * Determine which renderer to use based on content type + * + * @throws RuntimeException + */ + protected function determineRenderer(): callable + { + if ($this->contentType !== null && array_key_exists($this->contentType, $this->errorRenderers)) { + $renderer = $this->errorRenderers[$this->contentType]; + } else { + $renderer = $this->defaultErrorRenderer; + } + + return $this->callableResolver->resolve($renderer); + } + + /** + * Register an error renderer for a specific content-type + * + * @param string $contentType The content-type this renderer should be registered to + * @param ErrorRendererInterface|string|callable $errorRenderer The error renderer + */ + public function registerErrorRenderer(string $contentType, $errorRenderer): void + { + $this->errorRenderers[$contentType] = $errorRenderer; + } + + /** + * Set the default error renderer + * + * @param string $contentType The content type of the default error renderer + * @param ErrorRendererInterface|string|callable $errorRenderer The default error renderer + */ + public function setDefaultErrorRenderer(string $contentType, $errorRenderer): void + { + $this->defaultErrorRendererContentType = $contentType; + $this->defaultErrorRenderer = $errorRenderer; + } + + /** + * Set the renderer for the error logger + * + * @param ErrorRendererInterface|string|callable $logErrorRenderer + */ + public function setLogErrorRenderer($logErrorRenderer): void + { + $this->logErrorRenderer = $logErrorRenderer; + } + + /** + * Write to the error log if $logErrors has been set to true + */ + protected function writeToErrorLog(): void + { + $renderer = $this->callableResolver->resolve($this->logErrorRenderer); + $error = $renderer($this->exception, $this->logErrorDetails); + if (!$this->displayErrorDetails) { + $error .= "\nTips: To display error details in HTTP response "; + $error .= 'set "displayErrorDetails" to true in the ErrorHandler constructor.'; + } + $this->logError($error); + } + + /** + * Wraps the error_log function so that this can be easily tested + */ + protected function logError(string $error): void + { + $this->logger->error($error); + } + + /** + * Returns a default logger implementation. + */ + protected function getDefaultLogger(): LoggerInterface + { + return new Logger(); + } + + protected function respond(): ResponseInterface + { + $response = $this->responseFactory->createResponse($this->statusCode); + if ($this->contentType !== null && array_key_exists($this->contentType, $this->errorRenderers)) { + $response = $response->withHeader('Content-type', $this->contentType); + } else { + $response = $response->withHeader('Content-type', $this->defaultErrorRendererContentType); + } + + if ($this->exception instanceof HttpMethodNotAllowedException) { + $allowedMethods = implode(', ', $this->exception->getAllowedMethods()); + $response = $response->withHeader('Allow', $allowedMethods); + } + + $renderer = $this->determineRenderer(); + $body = call_user_func($renderer, $this->exception, $this->displayErrorDetails); + if ($body !== false) { + /** @var string $body */ + $response->getBody()->write($body); + } + + return $response; + } +} diff --git a/lib/slim/slim/Slim/Handlers/Strategies/RequestHandler.php b/lib/slim/slim/Slim/Handlers/Strategies/RequestHandler.php new file mode 100644 index 00000000000..ea88a5f12dc --- /dev/null +++ b/lib/slim/slim/Slim/Handlers/Strategies/RequestHandler.php @@ -0,0 +1,48 @@ +appendRouteArgumentsToRequestAttributes = $appendRouteArgumentsToRequestAttributes; + } + + /** + * Invoke a route callable that implements RequestHandlerInterface + * + * @param array $routeArguments + */ + public function __invoke( + callable $callable, + ServerRequestInterface $request, + ResponseInterface $response, + array $routeArguments + ): ResponseInterface { + if ($this->appendRouteArgumentsToRequestAttributes) { + foreach ($routeArguments as $k => $v) { + $request = $request->withAttribute($k, $v); + } + } + + return $callable($request); + } +} diff --git a/lib/slim/slim/Slim/Handlers/Strategies/RequestResponse.php b/lib/slim/slim/Slim/Handlers/Strategies/RequestResponse.php new file mode 100644 index 00000000000..45b2c05a497 --- /dev/null +++ b/lib/slim/slim/Slim/Handlers/Strategies/RequestResponse.php @@ -0,0 +1,40 @@ + $routeArguments + */ + public function __invoke( + callable $callable, + ServerRequestInterface $request, + ResponseInterface $response, + array $routeArguments + ): ResponseInterface { + foreach ($routeArguments as $k => $v) { + $request = $request->withAttribute($k, $v); + } + + return $callable($request, $response, $routeArguments); + } +} diff --git a/lib/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php b/lib/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php new file mode 100644 index 00000000000..c4ab16df574 --- /dev/null +++ b/lib/slim/slim/Slim/Handlers/Strategies/RequestResponseArgs.php @@ -0,0 +1,38 @@ + $routeArguments + */ + public function __invoke( + callable $callable, + ServerRequestInterface $request, + ResponseInterface $response, + array $routeArguments + ): ResponseInterface { + return $callable($request, $response, ...array_values($routeArguments)); + } +} diff --git a/lib/slim/slim/Slim/Handlers/Strategies/RequestResponseNamedArgs.php b/lib/slim/slim/Slim/Handlers/Strategies/RequestResponseNamedArgs.php new file mode 100644 index 00000000000..651111ddeb6 --- /dev/null +++ b/lib/slim/slim/Slim/Handlers/Strategies/RequestResponseNamedArgs.php @@ -0,0 +1,44 @@ += 8.0.0'); + } + } + + /** + * Invoke a route callable with request, response and all route parameters + * as individual arguments. + * + * @param array $routeArguments + */ + public function __invoke( + callable $callable, + ServerRequestInterface $request, + ResponseInterface $response, + array $routeArguments + ): ResponseInterface { + return $callable($request, $response, ...$routeArguments); + } +} diff --git a/lib/slim/slim/Slim/Interfaces/AdvancedCallableResolverInterface.php b/lib/slim/slim/Slim/Interfaces/AdvancedCallableResolverInterface.php new file mode 100644 index 00000000000..aa1d897de61 --- /dev/null +++ b/lib/slim/slim/Slim/Interfaces/AdvancedCallableResolverInterface.php @@ -0,0 +1,28 @@ + $routeArguments The route's placeholder arguments + * + * @return ResponseInterface The response from the callable. + */ + public function __invoke( + callable $callable, + ServerRequestInterface $request, + ResponseInterface $response, + array $routeArguments + ): ResponseInterface; +} diff --git a/lib/slim/slim/Slim/Interfaces/MiddlewareDispatcherInterface.php b/lib/slim/slim/Slim/Interfaces/MiddlewareDispatcherInterface.php new file mode 100644 index 00000000000..aa7a26ad27e --- /dev/null +++ b/lib/slim/slim/Slim/Interfaces/MiddlewareDispatcherInterface.php @@ -0,0 +1,42 @@ + + */ + public function getArguments(): array; + + /** + * Set a route argument + */ + public function setArgument(string $name, string $value): RouteInterface; + + /** + * Replace route arguments + * + * @param array $arguments + */ + public function setArguments(array $arguments): self; + + /** + * @param MiddlewareInterface|string|callable $middleware + */ + public function add($middleware): self; + + public function addMiddleware(MiddlewareInterface $middleware): self; + + /** + * Prepare the route for use + * + * @param array $arguments + */ + public function prepare(array $arguments): self; + + /** + * Run route + * + * This method traverses the middleware stack, including the route's callable + * and captures the resultant HTTP response object. It then sends the response + * back to the Application. + */ + public function run(ServerRequestInterface $request): ResponseInterface; +} diff --git a/lib/slim/slim/Slim/Interfaces/RouteParserInterface.php b/lib/slim/slim/Slim/Interfaces/RouteParserInterface.php new file mode 100644 index 00000000000..03d93266519 --- /dev/null +++ b/lib/slim/slim/Slim/Interfaces/RouteParserInterface.php @@ -0,0 +1,52 @@ + $data Named argument replacement data + * @param array $queryParams Optional query string parameters + * + * @throws RuntimeException If named route does not exist + * @throws InvalidArgumentException If required data not provided + */ + public function relativeUrlFor(string $routeName, array $data = [], array $queryParams = []): string; + + /** + * Build the path for a named route including the base path + * + * @param string $routeName Route name + * @param array $data Named argument replacement data + * @param array $queryParams Optional query string parameters + * + * @throws RuntimeException If named route does not exist + * @throws InvalidArgumentException If required data not provided + */ + public function urlFor(string $routeName, array $data = [], array $queryParams = []): string; + + /** + * Get fully qualified URL for named route + * + * @param UriInterface $uri + * @param string $routeName Route name + * @param array $data Named argument replacement data + * @param array $queryParams Optional query string parameters + */ + public function fullUrlFor(UriInterface $uri, string $routeName, array $data = [], array $queryParams = []): string; +} diff --git a/lib/slim/slim/Slim/Interfaces/RouteResolverInterface.php b/lib/slim/slim/Slim/Interfaces/RouteResolverInterface.php new file mode 100644 index 00000000000..256a3599759 --- /dev/null +++ b/lib/slim/slim/Slim/Interfaces/RouteResolverInterface.php @@ -0,0 +1,17 @@ +getPath() + */ + public function computeRoutingResults(string $uri, string $method): RoutingResults; + + public function resolveRoute(string $identifier): RouteInterface; +} diff --git a/lib/slim/slim/Slim/Interfaces/ServerRequestCreatorInterface.php b/lib/slim/slim/Slim/Interfaces/ServerRequestCreatorInterface.php new file mode 100644 index 00000000000..54d231edd5f --- /dev/null +++ b/lib/slim/slim/Slim/Interfaces/ServerRequestCreatorInterface.php @@ -0,0 +1,18 @@ + $context + * + * @throws InvalidArgumentException + */ + public function log($level, $message, array $context = []): void + { + error_log((string) $message); + } +} diff --git a/lib/slim/slim/Slim/Middleware/BodyParsingMiddleware.php b/lib/slim/slim/Slim/Middleware/BodyParsingMiddleware.php new file mode 100644 index 00000000000..9a90f30c4a5 --- /dev/null +++ b/lib/slim/slim/Slim/Middleware/BodyParsingMiddleware.php @@ -0,0 +1,196 @@ + callable + */ + public function __construct(array $bodyParsers = []) + { + $this->registerDefaultBodyParsers(); + + foreach ($bodyParsers as $mediaType => $parser) { + $this->registerBodyParser($mediaType, $parser); + } + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $parsedBody = $request->getParsedBody(); + + if (empty($parsedBody)) { + $parsedBody = $this->parseBody($request); + $request = $request->withParsedBody($parsedBody); + } + + return $handler->handle($request); + } + + /** + * @param string $mediaType A HTTP media type (excluding content-type params). + * @param callable $callable A callable that returns parsed contents for media type. + */ + public function registerBodyParser(string $mediaType, callable $callable): self + { + $this->bodyParsers[$mediaType] = $callable; + return $this; + } + + /** + * @param string $mediaType A HTTP media type (excluding content-type params). + */ + public function hasBodyParser(string $mediaType): bool + { + return isset($this->bodyParsers[$mediaType]); + } + + /** + * @param string $mediaType A HTTP media type (excluding content-type params). + * @throws RuntimeException + */ + public function getBodyParser(string $mediaType): callable + { + if (!isset($this->bodyParsers[$mediaType])) { + throw new RuntimeException('No parser for type ' . $mediaType); + } + return $this->bodyParsers[$mediaType]; + } + + protected function registerDefaultBodyParsers(): void + { + $this->registerBodyParser('application/json', static function ($input) { + $result = json_decode($input, true); + + if (!is_array($result)) { + return null; + } + + return $result; + }); + + $this->registerBodyParser('application/x-www-form-urlencoded', static function ($input) { + parse_str($input, $data); + return $data; + }); + + $xmlCallable = static function ($input) { + $backup = self::disableXmlEntityLoader(true); + $backup_errors = libxml_use_internal_errors(true); + $result = simplexml_load_string($input); + + self::disableXmlEntityLoader($backup); + libxml_clear_errors(); + libxml_use_internal_errors($backup_errors); + + if ($result === false) { + return null; + } + + return $result; + }; + + $this->registerBodyParser('application/xml', $xmlCallable); + $this->registerBodyParser('text/xml', $xmlCallable); + } + + /** + * @return null|array|object + */ + protected function parseBody(ServerRequestInterface $request) + { + $mediaType = $this->getMediaType($request); + if ($mediaType === null) { + return null; + } + + // Check if this specific media type has a parser registered first + if (!isset($this->bodyParsers[$mediaType])) { + // If not, look for a media type with a structured syntax suffix (RFC 6839) + $parts = explode('+', $mediaType); + if (count($parts) >= 2) { + $mediaType = 'application/' . $parts[count($parts) - 1]; + } + } + + if (isset($this->bodyParsers[$mediaType])) { + $body = (string)$request->getBody(); + $parsed = $this->bodyParsers[$mediaType]($body); + + if ($parsed !== null && !is_object($parsed) && !is_array($parsed)) { + throw new RuntimeException( + 'Request body media type parser return value must be an array, an object, or null' + ); + } + + return $parsed; + } + + return null; + } + + /** + * @return string|null The serverRequest media type, minus content-type params + */ + protected function getMediaType(ServerRequestInterface $request): ?string + { + $contentType = $request->getHeader('Content-Type')[0] ?? null; + + if (is_string($contentType) && trim($contentType) !== '') { + $contentTypeParts = explode(';', $contentType); + return strtolower(trim($contentTypeParts[0])); + } + + return null; + } + + protected static function disableXmlEntityLoader(bool $disable): bool + { + if (LIBXML_VERSION >= 20900) { + // libxml >= 2.9.0 disables entity loading by default, so it is + // safe to skip the real call (deprecated in PHP 8). + return true; + } + + // @codeCoverageIgnoreStart + return libxml_disable_entity_loader($disable); + // @codeCoverageIgnoreEnd + } +} diff --git a/lib/slim/slim/Slim/Middleware/ContentLengthMiddleware.php b/lib/slim/slim/Slim/Middleware/ContentLengthMiddleware.php new file mode 100644 index 00000000000..8fa13bcf779 --- /dev/null +++ b/lib/slim/slim/Slim/Middleware/ContentLengthMiddleware.php @@ -0,0 +1,32 @@ +handle($request); + + // Add Content-Length header if not already added + $size = $response->getBody()->getSize(); + if ($size !== null && !$response->hasHeader('Content-Length')) { + $response = $response->withHeader('Content-Length', (string) $size); + } + + return $response; + } +} diff --git a/lib/slim/slim/Slim/Middleware/ErrorMiddleware.php b/lib/slim/slim/Slim/Middleware/ErrorMiddleware.php new file mode 100644 index 00000000000..2eb5cc96ffa --- /dev/null +++ b/lib/slim/slim/Slim/Middleware/ErrorMiddleware.php @@ -0,0 +1,212 @@ +callableResolver = $callableResolver; + $this->responseFactory = $responseFactory; + $this->displayErrorDetails = $displayErrorDetails; + $this->logErrors = $logErrors; + $this->logErrorDetails = $logErrorDetails; + $this->logger = $logger; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + return $handler->handle($request); + } catch (Throwable $e) { + return $this->handleException($request, $e); + } + } + + public function handleException(ServerRequestInterface $request, Throwable $exception): ResponseInterface + { + if ($exception instanceof HttpException) { + $request = $exception->getRequest(); + } + + $exceptionType = get_class($exception); + $handler = $this->getErrorHandler($exceptionType); + + return $handler($request, $exception, $this->displayErrorDetails, $this->logErrors, $this->logErrorDetails); + } + + /** + * Get callable to handle scenarios where an error + * occurs when processing the current request. + * + * @param string $type Exception/Throwable name. ie: RuntimeException::class + * @return callable|ErrorHandler + */ + public function getErrorHandler(string $type) + { + if (isset($this->handlers[$type])) { + return $this->callableResolver->resolve($this->handlers[$type]); + } + + if (isset($this->subClassHandlers[$type])) { + return $this->callableResolver->resolve($this->subClassHandlers[$type]); + } + + foreach ($this->subClassHandlers as $class => $handler) { + if (is_subclass_of($type, $class)) { + return $this->callableResolver->resolve($handler); + } + } + + return $this->getDefaultErrorHandler(); + } + + /** + * Get default error handler + * + * @return ErrorHandler|callable + */ + public function getDefaultErrorHandler() + { + if ($this->defaultErrorHandler === null) { + $this->defaultErrorHandler = new ErrorHandler( + $this->callableResolver, + $this->responseFactory, + $this->logger + ); + } + + return $this->callableResolver->resolve($this->defaultErrorHandler); + } + + /** + * Set callable as the default Slim application error handler. + * + * The callable signature MUST match the ErrorHandlerInterface + * + * @see \Slim\Interfaces\ErrorHandlerInterface + * + * 1. Instance of \Psr\Http\Message\ServerRequestInterface + * 2. Instance of \Throwable + * 3. Boolean $displayErrorDetails + * 4. Boolean $logErrors + * 5. Boolean $logErrorDetails + * + * The callable MUST return an instance of + * \Psr\Http\Message\ResponseInterface. + * + * @param string|callable|ErrorHandler $handler + */ + public function setDefaultErrorHandler($handler): self + { + $this->defaultErrorHandler = $handler; + return $this; + } + + /** + * Set callable to handle scenarios where an error + * occurs when processing the current request. + * + * The callable signature MUST match the ErrorHandlerInterface + * + * Pass true to $handleSubclasses to make the handler handle all subclasses of + * the type as well. Pass an array of classes to make the same function handle multiple exceptions. + * + * @see \Slim\Interfaces\ErrorHandlerInterface + * + * 1. Instance of \Psr\Http\Message\ServerRequestInterface + * 2. Instance of \Throwable + * 3. Boolean $displayErrorDetails + * 4. Boolean $logErrors + * 5. Boolean $logErrorDetails + * + * The callable MUST return an instance of + * \Psr\Http\Message\ResponseInterface. + * + * @param string|string[] $typeOrTypes Exception/Throwable name. + * ie: RuntimeException::class or an array of classes + * ie: [HttpNotFoundException::class, HttpMethodNotAllowedException::class] + * @param string|callable|ErrorHandlerInterface $handler + */ + public function setErrorHandler($typeOrTypes, $handler, bool $handleSubclasses = false): self + { + if (is_array($typeOrTypes)) { + foreach ($typeOrTypes as $type) { + $this->addErrorHandler($type, $handler, $handleSubclasses); + } + } else { + $this->addErrorHandler($typeOrTypes, $handler, $handleSubclasses); + } + + return $this; + } + + /** + * Used internally to avoid code repetition when passing multiple exceptions to setErrorHandler(). + * @param string|callable|ErrorHandlerInterface $handler + */ + private function addErrorHandler(string $type, $handler, bool $handleSubclasses): void + { + if ($handleSubclasses) { + $this->subClassHandlers[$type] = $handler; + } else { + $this->handlers[$type] = $handler; + } + } +} diff --git a/lib/slim/slim/Slim/Middleware/MethodOverrideMiddleware.php b/lib/slim/slim/Slim/Middleware/MethodOverrideMiddleware.php new file mode 100644 index 00000000000..079a1f18988 --- /dev/null +++ b/lib/slim/slim/Slim/Middleware/MethodOverrideMiddleware.php @@ -0,0 +1,43 @@ +getHeaderLine('X-Http-Method-Override'); + + if ($methodHeader) { + $request = $request->withMethod($methodHeader); + } elseif (strtoupper($request->getMethod()) === 'POST') { + $body = $request->getParsedBody(); + + if (is_array($body) && !empty($body['_METHOD'])) { + $request = $request->withMethod($body['_METHOD']); + } + + if ($request->getBody()->eof()) { + $request->getBody()->rewind(); + } + } + + return $handler->handle($request); + } +} diff --git a/lib/slim/slim/Slim/Middleware/OutputBufferingMiddleware.php b/lib/slim/slim/Slim/Middleware/OutputBufferingMiddleware.php new file mode 100644 index 00000000000..69ee1f6f1fd --- /dev/null +++ b/lib/slim/slim/Slim/Middleware/OutputBufferingMiddleware.php @@ -0,0 +1,74 @@ +streamFactory = $streamFactory; + $this->style = $style; + + if (!in_array($style, [static::APPEND, static::PREPEND], true)) { + throw new InvalidArgumentException("Invalid style `{$style}`. Must be `append` or `prepend`"); + } + } + + /** + * @throws Throwable + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + try { + ob_start(); + $response = $handler->handle($request); + $output = ob_get_clean(); + } catch (Throwable $e) { + ob_end_clean(); + throw $e; + } + + if (!empty($output)) { + if ($this->style === static::PREPEND) { + $body = $this->streamFactory->createStream(); + $body->write($output . $response->getBody()); + $response = $response->withBody($body); + } elseif ($this->style === static::APPEND && $response->getBody()->isWritable()) { + $response->getBody()->write($output); + } + } + + return $response; + } +} diff --git a/lib/slim/slim/Slim/Middleware/RoutingMiddleware.php b/lib/slim/slim/Slim/Middleware/RoutingMiddleware.php new file mode 100644 index 00000000000..a3d3085bd4b --- /dev/null +++ b/lib/slim/slim/Slim/Middleware/RoutingMiddleware.php @@ -0,0 +1,98 @@ +routeResolver = $routeResolver; + $this->routeParser = $routeParser; + } + + /** + * @throws HttpNotFoundException + * @throws HttpMethodNotAllowedException + * @throws RuntimeException + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $request = $this->performRouting($request); + return $handler->handle($request); + } + + /** + * Perform routing + * + * @param ServerRequestInterface $request PSR7 Server Request + * + * @throws HttpNotFoundException + * @throws HttpMethodNotAllowedException + * @throws RuntimeException + */ + public function performRouting(ServerRequestInterface $request): ServerRequestInterface + { + $request = $request->withAttribute(RouteContext::ROUTE_PARSER, $this->routeParser); + + $routingResults = $this->resolveRoutingResultsFromRequest($request); + $routeStatus = $routingResults->getRouteStatus(); + + $request = $request->withAttribute(RouteContext::ROUTING_RESULTS, $routingResults); + + switch ($routeStatus) { + case RoutingResults::FOUND: + $routeArguments = $routingResults->getRouteArguments(); + $routeIdentifier = $routingResults->getRouteIdentifier() ?? ''; + $route = $this->routeResolver + ->resolveRoute($routeIdentifier) + ->prepare($routeArguments); + return $request->withAttribute(RouteContext::ROUTE, $route); + + case RoutingResults::NOT_FOUND: + throw new HttpNotFoundException($request); + + case RoutingResults::METHOD_NOT_ALLOWED: + $exception = new HttpMethodNotAllowedException($request); + $exception->setAllowedMethods($routingResults->getAllowedMethods()); + throw $exception; + + default: + throw new RuntimeException('An unexpected error occurred while performing routing.'); + } + } + + /** + * Resolves the route from the given request + */ + protected function resolveRoutingResultsFromRequest(ServerRequestInterface $request): RoutingResults + { + return $this->routeResolver->computeRoutingResults( + $request->getUri()->getPath(), + $request->getMethod() + ); + } +} diff --git a/lib/slim/slim/Slim/MiddlewareDispatcher.php b/lib/slim/slim/Slim/MiddlewareDispatcher.php new file mode 100644 index 00000000000..7e05644142c --- /dev/null +++ b/lib/slim/slim/Slim/MiddlewareDispatcher.php @@ -0,0 +1,275 @@ +seedMiddlewareStack($kernel); + $this->callableResolver = $callableResolver; + $this->container = $container; + } + + /** + * {@inheritdoc} + */ + public function seedMiddlewareStack(RequestHandlerInterface $kernel): void + { + $this->tip = $kernel; + } + + /** + * Invoke the middleware stack + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->tip->handle($request); + } + + /** + * Add a new middleware to the stack + * + * Middleware are organized as a stack. That means middleware + * that have been added before will be executed after the newly + * added one (last in, first out). + * + * @param MiddlewareInterface|string|callable $middleware + */ + public function add($middleware): MiddlewareDispatcherInterface + { + if ($middleware instanceof MiddlewareInterface) { + return $this->addMiddleware($middleware); + } + + if (is_string($middleware)) { + return $this->addDeferred($middleware); + } + + if (is_callable($middleware)) { + return $this->addCallable($middleware); + } + + /** @phpstan-ignore-next-line */ + throw new RuntimeException( + 'A middleware must be an object/class name referencing an implementation of ' . + 'MiddlewareInterface or a callable with a matching signature.' + ); + } + + /** + * Add a new middleware to the stack + * + * Middleware are organized as a stack. That means middleware + * that have been added before will be executed after the newly + * added one (last in, first out). + */ + public function addMiddleware(MiddlewareInterface $middleware): MiddlewareDispatcherInterface + { + $next = $this->tip; + $this->tip = new class ($middleware, $next) implements RequestHandlerInterface { + private MiddlewareInterface $middleware; + + private RequestHandlerInterface $next; + + public function __construct(MiddlewareInterface $middleware, RequestHandlerInterface $next) + { + $this->middleware = $middleware; + $this->next = $next; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return $this->middleware->process($request, $this->next); + } + }; + + return $this; + } + + /** + * Add a new middleware by class name + * + * Middleware are organized as a stack. That means middleware + * that have been added before will be executed after the newly + * added one (last in, first out). + */ + public function addDeferred(string $middleware): self + { + $next = $this->tip; + $this->tip = new class ( + $middleware, + $next, + $this->container, + $this->callableResolver + ) implements RequestHandlerInterface { + private string $middleware; + + private RequestHandlerInterface $next; + + private ?ContainerInterface $container; + + private ?CallableResolverInterface $callableResolver; + + public function __construct( + string $middleware, + RequestHandlerInterface $next, + ?ContainerInterface $container = null, + ?CallableResolverInterface $callableResolver = null + ) { + $this->middleware = $middleware; + $this->next = $next; + $this->container = $container; + $this->callableResolver = $callableResolver; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + if ($this->callableResolver instanceof AdvancedCallableResolverInterface) { + $callable = $this->callableResolver->resolveMiddleware($this->middleware); + return $callable($request, $this->next); + } + + $callable = null; + + if ($this->callableResolver instanceof CallableResolverInterface) { + try { + $callable = $this->callableResolver->resolve($this->middleware); + } catch (RuntimeException $e) { + // Do Nothing + } + } + + if (!$callable) { + $resolved = $this->middleware; + $instance = null; + $method = null; + + // Check for Slim callable as `class:method` + if (preg_match(CallableResolver::$callablePattern, $resolved, $matches)) { + $resolved = $matches[1]; + $method = $matches[2]; + } + + if ($this->container && $this->container->has($resolved)) { + $instance = $this->container->get($resolved); + if ($instance instanceof MiddlewareInterface) { + return $instance->process($request, $this->next); + } + } elseif (!function_exists($resolved)) { + if (!class_exists($resolved)) { + throw new RuntimeException(sprintf('Middleware %s does not exist', $resolved)); + } + $instance = new $resolved($this->container); + } + + if ($instance && $instance instanceof MiddlewareInterface) { + return $instance->process($request, $this->next); + } + + $callable = $instance ?? $resolved; + if ($instance && $method) { + $callable = [$instance, $method]; + } + + if ($this->container && $callable instanceof Closure) { + $callable = $callable->bindTo($this->container); + } + } + + if (!is_callable($callable)) { + throw new RuntimeException( + sprintf( + 'Middleware %s is not resolvable', + $this->middleware + ) + ); + } + + return $callable($request, $this->next); + } + }; + + return $this; + } + + /** + * Add a (non-standard) callable middleware to the stack + * + * Middleware are organized as a stack. That means middleware + * that have been added before will be executed after the newly + * added one (last in, first out). + */ + public function addCallable(callable $middleware): self + { + $next = $this->tip; + + if ($this->container && $middleware instanceof Closure) { + /** @var Closure $middleware */ + $middleware = $middleware->bindTo($this->container); + } + + $this->tip = new class ($middleware, $next) implements RequestHandlerInterface { + /** + * @var callable + */ + private $middleware; + + /** + * @var RequestHandlerInterface + */ + private $next; + + public function __construct(callable $middleware, RequestHandlerInterface $next) + { + $this->middleware = $middleware; + $this->next = $next; + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return ($this->middleware)($request, $this->next); + } + }; + + return $this; + } +} diff --git a/lib/slim/slim/Slim/ResponseEmitter.php b/lib/slim/slim/Slim/ResponseEmitter.php new file mode 100644 index 00000000000..fac36e9e7c7 --- /dev/null +++ b/lib/slim/slim/Slim/ResponseEmitter.php @@ -0,0 +1,136 @@ +responseChunkSize = $responseChunkSize; + } + + /** + * Send the response the client + */ + public function emit(ResponseInterface $response): void + { + $isEmpty = $this->isResponseEmpty($response); + if (headers_sent() === false) { + $this->emitHeaders($response); + + // Set the status _after_ the headers, because of PHP's "helpful" behavior with location headers. + // See https://github.com/slimphp/Slim/issues/1730 + + $this->emitStatusLine($response); + } + + if (!$isEmpty) { + $this->emitBody($response); + } + } + + /** + * Emit Response Headers + */ + private function emitHeaders(ResponseInterface $response): void + { + foreach ($response->getHeaders() as $name => $values) { + $first = strtolower($name) !== 'set-cookie'; + foreach ($values as $value) { + $header = sprintf('%s: %s', $name, $value); + header($header, $first); + $first = false; + } + } + } + + /** + * Emit Status Line + */ + private function emitStatusLine(ResponseInterface $response): void + { + $statusLine = sprintf( + 'HTTP/%s %s %s', + $response->getProtocolVersion(), + $response->getStatusCode(), + $response->getReasonPhrase() + ); + header($statusLine, true, $response->getStatusCode()); + } + + /** + * Emit Body + */ + private function emitBody(ResponseInterface $response): void + { + $body = $response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + + $amountToRead = (int) $response->getHeaderLine('Content-Length'); + if (!$amountToRead) { + $amountToRead = $body->getSize(); + } + + if ($amountToRead) { + while ($amountToRead > 0 && !$body->eof()) { + $length = min($this->responseChunkSize, $amountToRead); + $data = $body->read($length); + echo $data; + + $amountToRead -= strlen($data); + + if (connection_status() !== CONNECTION_NORMAL) { + break; + } + } + } else { + while (!$body->eof()) { + echo $body->read($this->responseChunkSize); + if (connection_status() !== CONNECTION_NORMAL) { + break; + } + } + } + } + + /** + * Asserts response body is empty or status code is 204, 205 or 304 + */ + public function isResponseEmpty(ResponseInterface $response): bool + { + if (in_array($response->getStatusCode(), [204, 205, 304], true)) { + return true; + } + $stream = $response->getBody(); + $seekable = $stream->isSeekable(); + if ($seekable) { + $stream->rewind(); + } + return $seekable ? $stream->read(1) === '' : $stream->eof(); + } +} diff --git a/lib/slim/slim/Slim/Routing/Dispatcher.php b/lib/slim/slim/Slim/Routing/Dispatcher.php new file mode 100644 index 00000000000..e33eac39644 --- /dev/null +++ b/lib/slim/slim/Slim/Routing/Dispatcher.php @@ -0,0 +1,78 @@ +routeCollector = $routeCollector; + } + + protected function createDispatcher(): FastRouteDispatcher + { + if ($this->dispatcher) { + return $this->dispatcher; + } + + $routeDefinitionCallback = function (FastRouteCollector $r): void { + $basePath = $this->routeCollector->getBasePath(); + + foreach ($this->routeCollector->getRoutes() as $route) { + $r->addRoute($route->getMethods(), $basePath . $route->getPattern(), $route->getIdentifier()); + } + }; + + $cacheFile = $this->routeCollector->getCacheFile(); + if ($cacheFile) { + /** @var FastRouteDispatcher $dispatcher */ + $dispatcher = \FastRoute\cachedDispatcher($routeDefinitionCallback, [ + 'dataGenerator' => GroupCountBased::class, + 'dispatcher' => FastRouteDispatcher::class, + 'routeParser' => new Std(), + 'cacheFile' => $cacheFile, + ]); + } else { + /** @var FastRouteDispatcher $dispatcher */ + $dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback, [ + 'dataGenerator' => GroupCountBased::class, + 'dispatcher' => FastRouteDispatcher::class, + 'routeParser' => new Std(), + ]); + } + + $this->dispatcher = $dispatcher; + return $this->dispatcher; + } + + /** + * {@inheritdoc} + */ + public function dispatch(string $method, string $uri): RoutingResults + { + $dispatcher = $this->createDispatcher(); + $results = $dispatcher->dispatch($method, $uri); + return new RoutingResults($this, $method, $uri, $results[0], $results[1], $results[2]); + } + + /** + * {@inheritdoc} + */ + public function getAllowedMethods(string $uri): array + { + $dispatcher = $this->createDispatcher(); + return $dispatcher->getAllowedMethods($uri); + } +} diff --git a/lib/slim/slim/Slim/Routing/FastRouteDispatcher.php b/lib/slim/slim/Slim/Routing/FastRouteDispatcher.php new file mode 100644 index 00000000000..797746bbb10 --- /dev/null +++ b/lib/slim/slim/Slim/Routing/FastRouteDispatcher.php @@ -0,0 +1,109 @@ +} + */ + public function dispatch($httpMethod, $uri): array + { + $routingResults = $this->routingResults($httpMethod, $uri); + if ($routingResults[0] === self::FOUND) { + return $routingResults; + } + + // For HEAD requests, attempt fallback to GET + if ($httpMethod === 'HEAD') { + $routingResults = $this->routingResults('GET', $uri); + if ($routingResults[0] === self::FOUND) { + return $routingResults; + } + } + + // If nothing else matches, try fallback routes + $routingResults = $this->routingResults('*', $uri); + if ($routingResults[0] === self::FOUND) { + return $routingResults; + } + + if (!empty($this->getAllowedMethods($uri))) { + return [self::METHOD_NOT_ALLOWED, null, []]; + } + + return [self::NOT_FOUND, null, []]; + } + + /** + * @param string $httpMethod + * @param string $uri + * + * @return array{int, string|null, array} + */ + private function routingResults(string $httpMethod, string $uri): array + { + if (isset($this->staticRouteMap[$httpMethod][$uri])) { + /** @var string $routeIdentifier */ + $routeIdentifier = $this->staticRouteMap[$httpMethod][$uri]; + return [self::FOUND, $routeIdentifier, []]; + } + + if (isset($this->variableRouteData[$httpMethod])) { + /** @var array{0: int, 1?: string, 2?: array} $result */ + $result = $this->dispatchVariableRoute($this->variableRouteData[$httpMethod], $uri); + if ($result[0] === self::FOUND) { + /** @var array{int, string, array} $result */ + return [self::FOUND, $result[1], $result[2]]; + } + } + + return [self::NOT_FOUND, null, []]; + } + + /** + * @param string $uri + * + * @return string[] + */ + public function getAllowedMethods(string $uri): array + { + if (isset($this->allowedMethods[$uri])) { + return $this->allowedMethods[$uri]; + } + + $allowedMethods = []; + foreach ($this->staticRouteMap as $method => $uriMap) { + if (isset($uriMap[$uri])) { + $allowedMethods[$method] = true; + } + } + + foreach ($this->variableRouteData as $method => $routeData) { + $result = $this->dispatchVariableRoute($routeData, $uri); + if ($result[0] === self::FOUND) { + $allowedMethods[$method] = true; + } + } + + return $this->allowedMethods[$uri] = array_keys($allowedMethods); + } +} diff --git a/lib/slim/slim/Slim/Routing/Route.php b/lib/slim/slim/Slim/Routing/Route.php new file mode 100644 index 00000000000..2cd9fa567ed --- /dev/null +++ b/lib/slim/slim/Slim/Routing/Route.php @@ -0,0 +1,360 @@ + + */ + protected array $arguments = []; + + /** + * Route arguments parameters + * + * @var string[] + */ + protected array $savedArguments = []; + + /** + * Container + */ + protected ?ContainerInterface $container = null; + + protected MiddlewareDispatcher $middlewareDispatcher; + + /** + * Route callable + * + * @var callable|string + */ + protected $callable; + + protected CallableResolverInterface $callableResolver; + + protected ResponseFactoryInterface $responseFactory; + + /** + * Route pattern + */ + protected string $pattern; + + protected bool $groupMiddlewareAppended = false; + + /** + * @param string[] $methods The route HTTP methods + * @param string $pattern The route pattern + * @param callable|string $callable The route callable + * @param ResponseFactoryInterface $responseFactory + * @param CallableResolverInterface $callableResolver + * @param ContainerInterface|null $container + * @param InvocationStrategyInterface|null $invocationStrategy + * @param RouteGroupInterface[] $groups The parent route groups + * @param int $identifier The route identifier + */ + public function __construct( + array $methods, + string $pattern, + $callable, + ResponseFactoryInterface $responseFactory, + CallableResolverInterface $callableResolver, + ?ContainerInterface $container = null, + ?InvocationStrategyInterface $invocationStrategy = null, + array $groups = [], + int $identifier = 0 + ) { + $this->methods = $methods; + $this->pattern = $pattern; + $this->callable = $callable; + $this->responseFactory = $responseFactory; + $this->callableResolver = $callableResolver; + $this->container = $container; + $this->invocationStrategy = $invocationStrategy ?? new RequestResponse(); + $this->groups = $groups; + $this->identifier = 'route' . $identifier; + $this->middlewareDispatcher = new MiddlewareDispatcher($this, $callableResolver, $container); + } + + public function getCallableResolver(): CallableResolverInterface + { + return $this->callableResolver; + } + + /** + * {@inheritdoc} + */ + public function getInvocationStrategy(): InvocationStrategyInterface + { + return $this->invocationStrategy; + } + + /** + * {@inheritdoc} + */ + public function setInvocationStrategy(InvocationStrategyInterface $invocationStrategy): RouteInterface + { + $this->invocationStrategy = $invocationStrategy; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getMethods(): array + { + return $this->methods; + } + + /** + * {@inheritdoc} + */ + public function getPattern(): string + { + return $this->pattern; + } + + /** + * {@inheritdoc} + */ + public function setPattern(string $pattern): RouteInterface + { + $this->pattern = $pattern; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCallable() + { + return $this->callable; + } + + /** + * {@inheritdoc} + */ + public function setCallable($callable): RouteInterface + { + $this->callable = $callable; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setName(string $name): RouteInterface + { + $this->name = $name; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * {@inheritdoc} + */ + public function getArgument(string $name, ?string $default = null): ?string + { + if (array_key_exists($name, $this->arguments)) { + return $this->arguments[$name]; + } + return $default; + } + + /** + * {@inheritdoc} + */ + public function getArguments(): array + { + return $this->arguments; + } + + /** + * {@inheritdoc} + */ + public function setArguments(array $arguments, bool $includeInSavedArguments = true): RouteInterface + { + if ($includeInSavedArguments) { + $this->savedArguments = $arguments; + } + + $this->arguments = $arguments; + return $this; + } + + /** + * @return RouteGroupInterface[] + */ + public function getGroups(): array + { + return $this->groups; + } + + /** + * {@inheritdoc} + */ + public function add($middleware): RouteInterface + { + $this->middlewareDispatcher->add($middleware); + return $this; + } + + /** + * {@inheritdoc} + */ + public function addMiddleware(MiddlewareInterface $middleware): RouteInterface + { + $this->middlewareDispatcher->addMiddleware($middleware); + return $this; + } + + /** + * {@inheritdoc} + */ + public function prepare(array $arguments): RouteInterface + { + $this->arguments = array_replace($this->savedArguments, $arguments); + return $this; + } + + /** + * {@inheritdoc} + */ + public function setArgument(string $name, string $value, bool $includeInSavedArguments = true): RouteInterface + { + if ($includeInSavedArguments) { + $this->savedArguments[$name] = $value; + } + + $this->arguments[$name] = $value; + return $this; + } + + /** + * {@inheritdoc} + */ + public function run(ServerRequestInterface $request): ResponseInterface + { + if (!$this->groupMiddlewareAppended) { + $this->appendGroupMiddlewareToRoute(); + } + + return $this->middlewareDispatcher->handle($request); + } + + /** + * @return void + */ + protected function appendGroupMiddlewareToRoute(): void + { + $inner = $this->middlewareDispatcher; + $this->middlewareDispatcher = new MiddlewareDispatcher($inner, $this->callableResolver, $this->container); + + /** @var RouteGroupInterface $group */ + foreach (array_reverse($this->groups) as $group) { + $group->appendMiddlewareToDispatcher($this->middlewareDispatcher); + } + + $this->groupMiddlewareAppended = true; + } + + /** + * {@inheritdoc} + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + if ($this->callableResolver instanceof AdvancedCallableResolverInterface) { + $callable = $this->callableResolver->resolveRoute($this->callable); + } else { + $callable = $this->callableResolver->resolve($this->callable); + } + $strategy = $this->invocationStrategy; + + /** @var string[] $strategyImplements */ + $strategyImplements = class_implements($strategy); + + if ( + is_array($callable) + && $callable[0] instanceof RequestHandlerInterface + && !in_array(RequestHandlerInvocationStrategyInterface::class, $strategyImplements) + ) { + $strategy = new RequestHandler(); + } + + $response = $this->responseFactory->createResponse(); + return $strategy($callable, $request, $response, $this->arguments); + } +} diff --git a/lib/slim/slim/Slim/Routing/RouteCollector.php b/lib/slim/slim/Slim/Routing/RouteCollector.php new file mode 100644 index 00000000000..61b450300b9 --- /dev/null +++ b/lib/slim/slim/Slim/Routing/RouteCollector.php @@ -0,0 +1,293 @@ +responseFactory = $responseFactory; + $this->callableResolver = $callableResolver; + $this->container = $container; + $this->defaultInvocationStrategy = $defaultInvocationStrategy ?? new RequestResponse(); + $this->routeParser = $routeParser ?? new RouteParser($this); + + if ($cacheFile) { + $this->setCacheFile($cacheFile); + } + } + + public function getRouteParser(): RouteParserInterface + { + return $this->routeParser; + } + + /** + * Get default route invocation strategy + */ + public function getDefaultInvocationStrategy(): InvocationStrategyInterface + { + return $this->defaultInvocationStrategy; + } + + public function setDefaultInvocationStrategy(InvocationStrategyInterface $strategy): RouteCollectorInterface + { + $this->defaultInvocationStrategy = $strategy; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getCacheFile(): ?string + { + return $this->cacheFile; + } + + /** + * {@inheritdoc} + */ + public function setCacheFile(string $cacheFile): RouteCollectorInterface + { + if (file_exists($cacheFile) && !is_readable($cacheFile)) { + throw new RuntimeException( + sprintf('Route collector cache file `%s` is not readable', $cacheFile) + ); + } + + if (!file_exists($cacheFile) && !is_writable(dirname($cacheFile))) { + throw new RuntimeException( + sprintf('Route collector cache file directory `%s` is not writable', dirname($cacheFile)) + ); + } + + $this->cacheFile = $cacheFile; + return $this; + } + + /** + * {@inheritdoc} + */ + public function getBasePath(): string + { + return $this->basePath; + } + + /** + * Set the base path used in urlFor() + */ + public function setBasePath(string $basePath): RouteCollectorInterface + { + $this->basePath = $basePath; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getRoutes(): array + { + return $this->routes; + } + + /** + * {@inheritdoc} + */ + public function removeNamedRoute(string $name): RouteCollectorInterface + { + $route = $this->getNamedRoute($name); + + unset($this->routesByName[$route->getName()], $this->routes[$route->getIdentifier()]); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getNamedRoute(string $name): RouteInterface + { + if (isset($this->routesByName[$name])) { + $route = $this->routesByName[$name]; + if ($route->getName() === $name) { + return $route; + } + + unset($this->routesByName[$name]); + } + + foreach ($this->routes as $route) { + if ($name === $route->getName()) { + $this->routesByName[$name] = $route; + return $route; + } + } + + throw new RuntimeException('Named route does not exist for name: ' . $name); + } + + /** + * {@inheritdoc} + */ + public function lookupRoute(string $identifier): RouteInterface + { + if (!isset($this->routes[$identifier])) { + throw new RuntimeException('Route not found, looks like your route cache is stale.'); + } + return $this->routes[$identifier]; + } + + /** + * {@inheritdoc} + */ + public function group(string $pattern, $callable): RouteGroupInterface + { + $routeGroup = $this->createGroup($pattern, $callable); + $this->routeGroups[] = $routeGroup; + + $routeGroup->collectRoutes(); + array_pop($this->routeGroups); + + return $routeGroup; + } + + /** + * @param string|callable $callable + */ + protected function createGroup(string $pattern, $callable): RouteGroupInterface + { + $routeCollectorProxy = $this->createProxy($pattern); + return new RouteGroup($pattern, $callable, $this->callableResolver, $routeCollectorProxy); + } + + protected function createProxy(string $pattern): RouteCollectorProxyInterface + { + return new RouteCollectorProxy( + $this->responseFactory, + $this->callableResolver, + $this->container, + $this, + $pattern + ); + } + + /** + * {@inheritdoc} + */ + public function map(array $methods, string $pattern, $handler): RouteInterface + { + $route = $this->createRoute($methods, $pattern, $handler); + $this->routes[$route->getIdentifier()] = $route; + + $routeName = $route->getName(); + if ($routeName !== null && !isset($this->routesByName[$routeName])) { + $this->routesByName[$routeName] = $route; + } + + $this->routeCounter++; + + return $route; + } + + /** + * @param string[] $methods + * @param callable|string $callable + */ + protected function createRoute(array $methods, string $pattern, $callable): RouteInterface + { + return new Route( + $methods, + $pattern, + $callable, + $this->responseFactory, + $this->callableResolver, + $this->container, + $this->defaultInvocationStrategy, + $this->routeGroups, + $this->routeCounter + ); + } +} diff --git a/lib/slim/slim/Slim/Routing/RouteCollectorProxy.php b/lib/slim/slim/Slim/Routing/RouteCollectorProxy.php new file mode 100644 index 00000000000..f8bc232bc46 --- /dev/null +++ b/lib/slim/slim/Slim/Routing/RouteCollectorProxy.php @@ -0,0 +1,187 @@ +responseFactory = $responseFactory; + $this->callableResolver = $callableResolver; + $this->container = $container; + $this->routeCollector = $routeCollector ?? new RouteCollector($responseFactory, $callableResolver, $container); + $this->groupPattern = $groupPattern; + } + + /** + * {@inheritdoc} + */ + public function getResponseFactory(): ResponseFactoryInterface + { + return $this->responseFactory; + } + + /** + * {@inheritdoc} + */ + public function getCallableResolver(): CallableResolverInterface + { + return $this->callableResolver; + } + + /** + * {@inheritdoc} + */ + public function getContainer(): ?ContainerInterface + { + return $this->container; + } + + /** + * {@inheritdoc} + */ + public function getRouteCollector(): RouteCollectorInterface + { + return $this->routeCollector; + } + + /** + * {@inheritdoc} + */ + public function getBasePath(): string + { + return $this->routeCollector->getBasePath(); + } + + /** + * {@inheritdoc} + */ + public function setBasePath(string $basePath): RouteCollectorProxyInterface + { + $this->routeCollector->setBasePath($basePath); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function get(string $pattern, $callable): RouteInterface + { + return $this->map(['GET'], $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function post(string $pattern, $callable): RouteInterface + { + return $this->map(['POST'], $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function put(string $pattern, $callable): RouteInterface + { + return $this->map(['PUT'], $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function patch(string $pattern, $callable): RouteInterface + { + return $this->map(['PATCH'], $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function delete(string $pattern, $callable): RouteInterface + { + return $this->map(['DELETE'], $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function options(string $pattern, $callable): RouteInterface + { + return $this->map(['OPTIONS'], $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function any(string $pattern, $callable): RouteInterface + { + return $this->map(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function map(array $methods, string $pattern, $callable): RouteInterface + { + $pattern = $this->groupPattern . $pattern; + + return $this->routeCollector->map($methods, $pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function group(string $pattern, $callable): RouteGroupInterface + { + $pattern = $this->groupPattern . $pattern; + + return $this->routeCollector->group($pattern, $callable); + } + + /** + * {@inheritdoc} + */ + public function redirect(string $from, $to, int $status = 302): RouteInterface + { + $responseFactory = $this->responseFactory; + + $handler = function () use ($to, $status, $responseFactory) { + $response = $responseFactory->createResponse($status); + return $response->withHeader('Location', (string) $to); + }; + + return $this->get($from, $handler); + } +} diff --git a/lib/slim/slim/Slim/Routing/RouteContext.php b/lib/slim/slim/Slim/Routing/RouteContext.php new file mode 100644 index 00000000000..3ba5e23a615 --- /dev/null +++ b/lib/slim/slim/Slim/Routing/RouteContext.php @@ -0,0 +1,88 @@ +getAttribute(self::ROUTE); + $routeParser = $serverRequest->getAttribute(self::ROUTE_PARSER); + $routingResults = $serverRequest->getAttribute(self::ROUTING_RESULTS); + $basePath = $serverRequest->getAttribute(self::BASE_PATH); + + if ($routeParser === null || $routingResults === null) { + throw new RuntimeException('Cannot create RouteContext before routing has been completed'); + } + + /** @var RouteInterface|null $route */ + /** @var RouteParserInterface $routeParser */ + /** @var RoutingResults $routingResults */ + /** @var string|null $basePath */ + return new self($route, $routeParser, $routingResults, $basePath); + } + + private ?RouteInterface $route; + + private RouteParserInterface $routeParser; + + private RoutingResults $routingResults; + + private ?string $basePath; + + private function __construct( + ?RouteInterface $route, + RouteParserInterface $routeParser, + RoutingResults $routingResults, + ?string $basePath = null + ) { + $this->route = $route; + $this->routeParser = $routeParser; + $this->routingResults = $routingResults; + $this->basePath = $basePath; + } + + public function getRoute(): ?RouteInterface + { + return $this->route; + } + + public function getRouteParser(): RouteParserInterface + { + return $this->routeParser; + } + + public function getRoutingResults(): RoutingResults + { + return $this->routingResults; + } + + public function getBasePath(): string + { + if ($this->basePath === null) { + throw new RuntimeException('No base path defined.'); + } + return $this->basePath; + } +} diff --git a/lib/slim/slim/Slim/Routing/RouteGroup.php b/lib/slim/slim/Slim/Routing/RouteGroup.php new file mode 100644 index 00000000000..cd2f4e79a3f --- /dev/null +++ b/lib/slim/slim/Slim/Routing/RouteGroup.php @@ -0,0 +1,104 @@ +pattern = $pattern; + $this->callable = $callable; + $this->callableResolver = $callableResolver; + $this->routeCollectorProxy = $routeCollectorProxy; + } + + /** + * {@inheritdoc} + */ + public function collectRoutes(): RouteGroupInterface + { + if ($this->callableResolver instanceof AdvancedCallableResolverInterface) { + $callable = $this->callableResolver->resolveRoute($this->callable); + } else { + $callable = $this->callableResolver->resolve($this->callable); + } + $callable($this->routeCollectorProxy); + return $this; + } + + /** + * {@inheritdoc} + */ + public function add($middleware): RouteGroupInterface + { + $this->middleware[] = $middleware; + return $this; + } + + /** + * {@inheritdoc} + */ + public function addMiddleware(MiddlewareInterface $middleware): RouteGroupInterface + { + $this->middleware[] = $middleware; + return $this; + } + + /** + * {@inheritdoc} + */ + public function appendMiddlewareToDispatcher(MiddlewareDispatcher $dispatcher): RouteGroupInterface + { + foreach ($this->middleware as $middleware) { + $dispatcher->add($middleware); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getPattern(): string + { + return $this->pattern; + } +} diff --git a/lib/slim/slim/Slim/Routing/RouteParser.php b/lib/slim/slim/Slim/Routing/RouteParser.php new file mode 100644 index 00000000000..afb533cc5ad --- /dev/null +++ b/lib/slim/slim/Slim/Routing/RouteParser.php @@ -0,0 +1,127 @@ +routeCollector = $routeCollector; + $this->routeParser = new Std(); + } + + /** + * {@inheritdoc} + */ + public function relativeUrlFor(string $routeName, array $data = [], array $queryParams = []): string + { + $route = $this->routeCollector->getNamedRoute($routeName); + $pattern = $route->getPattern(); + + $segments = []; + $segmentName = ''; + + /* + * $routes is an associative array of expressions representing a route as multiple segments + * There is an expression for each optional parameter plus one without the optional parameters + * The most specific is last, hence why we reverse the array before iterating over it + */ + $expressions = array_reverse($this->routeParser->parse($pattern)); + foreach ($expressions as $expression) { + foreach ($expression as $segment) { + /* + * Each $segment is either a string or an array of strings + * containing optional parameters of an expression + */ + if (is_string($segment)) { + $segments[] = $segment; + continue; + } + + /** @var string[] $segment */ + /* + * If we don't have a data element for this segment in the provided $data + * we cancel testing to move onto the next expression with a less specific item + */ + if (!array_key_exists($segment[0], $data)) { + $segments = []; + $segmentName = $segment[0]; + break; + } + + $segments[] = $data[$segment[0]]; + } + + /* + * If we get to this logic block we have found all the parameters + * for the provided $data which means we don't need to continue testing + * less specific expressions + */ + if (!empty($segments)) { + break; + } + } + + if (empty($segments)) { + throw new InvalidArgumentException('Missing data for URL segment: ' . $segmentName); + } + + $url = implode('', $segments); + if ($queryParams) { + $url .= '?' . http_build_query($queryParams); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function urlFor(string $routeName, array $data = [], array $queryParams = []): string + { + $basePath = $this->routeCollector->getBasePath(); + $url = $this->relativeUrlFor($routeName, $data, $queryParams); + + if ($basePath) { + $url = $basePath . $url; + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function fullUrlFor(UriInterface $uri, string $routeName, array $data = [], array $queryParams = []): string + { + $path = $this->urlFor($routeName, $data, $queryParams); + $scheme = $uri->getScheme(); + $authority = $uri->getAuthority(); + $protocol = ($scheme ? $scheme . ':' : '') . ($authority ? '//' . $authority : ''); + return $protocol . $path; + } +} diff --git a/lib/slim/slim/Slim/Routing/RouteResolver.php b/lib/slim/slim/Slim/Routing/RouteResolver.php new file mode 100644 index 00000000000..d4f4eafa326 --- /dev/null +++ b/lib/slim/slim/Slim/Routing/RouteResolver.php @@ -0,0 +1,56 @@ +routeCollector = $routeCollector; + $this->dispatcher = $dispatcher ?? new Dispatcher($routeCollector); + } + + /** + * @param string $uri Should be $request->getUri()->getPath() + */ + public function computeRoutingResults(string $uri, string $method): RoutingResults + { + $uri = rawurldecode($uri); + if ($uri === '' || $uri[0] !== '/') { + $uri = '/' . $uri; + } + return $this->dispatcher->dispatch($method, $uri); + } + + /** + * @throws RuntimeException + */ + public function resolveRoute(string $identifier): RouteInterface + { + return $this->routeCollector->lookupRoute($identifier); + } +} diff --git a/lib/slim/slim/Slim/Routing/RouteRunner.php b/lib/slim/slim/Slim/Routing/RouteRunner.php new file mode 100644 index 00000000000..40946af5607 --- /dev/null +++ b/lib/slim/slim/Slim/Routing/RouteRunner.php @@ -0,0 +1,70 @@ +routeResolver = $routeResolver; + $this->routeParser = $routeParser; + $this->routeCollectorProxy = $routeCollectorProxy; + } + + /** + * This request handler is instantiated automatically in App::__construct() + * It is at the very tip of the middleware queue meaning it will be executed + * last and it detects whether or not routing has been performed in the user + * defined middleware stack. In the event that the user did not perform routing + * it is done here + * + * @throws HttpNotFoundException + * @throws HttpMethodNotAllowedException + */ + public function handle(ServerRequestInterface $request): ResponseInterface + { + // If routing hasn't been done, then do it now so we can dispatch + if ($request->getAttribute(RouteContext::ROUTING_RESULTS) === null) { + $routingMiddleware = new RoutingMiddleware($this->routeResolver, $this->routeParser); + $request = $routingMiddleware->performRouting($request); + } + + if ($this->routeCollectorProxy !== null) { + $request = $request->withAttribute( + RouteContext::BASE_PATH, + $this->routeCollectorProxy->getBasePath() + ); + } + + /** @var Route $route */ + $route = $request->getAttribute(RouteContext::ROUTE); + return $route->run($request); + } +} diff --git a/lib/slim/slim/Slim/Routing/RoutingResults.php b/lib/slim/slim/Slim/Routing/RoutingResults.php new file mode 100644 index 00000000000..ac2fa64f93f --- /dev/null +++ b/lib/slim/slim/Slim/Routing/RoutingResults.php @@ -0,0 +1,112 @@ + + */ + protected array $routeArguments; + + /** + * @param array $routeArguments + */ + public function __construct( + DispatcherInterface $dispatcher, + string $method, + string $uri, + int $routeStatus, + ?string $routeIdentifier = null, + array $routeArguments = [] + ) { + $this->dispatcher = $dispatcher; + $this->method = $method; + $this->uri = $uri; + $this->routeStatus = $routeStatus; + $this->routeIdentifier = $routeIdentifier; + $this->routeArguments = $routeArguments; + } + + public function getDispatcher(): DispatcherInterface + { + return $this->dispatcher; + } + + public function getMethod(): string + { + return $this->method; + } + + public function getUri(): string + { + return $this->uri; + } + + public function getRouteStatus(): int + { + return $this->routeStatus; + } + + public function getRouteIdentifier(): ?string + { + return $this->routeIdentifier; + } + + /** + * @return array + */ + public function getRouteArguments(bool $urlDecode = true): array + { + if (!$urlDecode) { + return $this->routeArguments; + } + + $routeArguments = []; + foreach ($this->routeArguments as $key => $value) { + $routeArguments[$key] = rawurldecode($value); + } + + return $routeArguments; + } + + /** + * @return string[] + */ + public function getAllowedMethods(): array + { + return $this->dispatcher->getAllowedMethods($this->uri); + } +} diff --git a/lib/slim/slim/composer.json b/lib/slim/slim/composer.json new file mode 100644 index 00000000000..f98064ff275 --- /dev/null +++ b/lib/slim/slim/composer.json @@ -0,0 +1,102 @@ +{ + "name": "slim/slim", + "type": "library", + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "keywords": ["framework","micro","api","router"], + "homepage": "https://www.slimframework.com", + "license": "MIT", + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "http://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "http://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "http://www.lgse.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + } + ], + "support": { + "docs": "https://www.slimframework.com/docs/v4/", + "forum": "https://discourse.slimframework.com/", + "irc": "irc://irc.freenode.net:6667/slimphp", + "issues": "https://github.com/slimphp/Slim/issues", + "rss": "https://www.slimframework.com/blog/feed.rss", + "slack": "https://slimphp.slack.com/", + "source": "https://github.com/slimphp/Slim", + "wiki": "https://github.com/slimphp/Slim/wiki" + }, + "require": { + "php": "^7.4 || ^8.0", + "ext-json": "*", + "nikic/fast-route": "^1.3", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "ext-simplexml": "*", + "adriansuter/php-autoload-override": "^1.4", + "guzzlehttp/psr7": "^2.6", + "httpsoft/http-message": "^1.1", + "httpsoft/http-server-request": "^1.1", + "laminas/laminas-diactoros": "^2.17 || ^3", + "nyholm/psr7": "^1.8", + "nyholm/psr7-server": "^1.1", + "phpspec/prophecy": "^1.19", + "phpspec/prophecy-phpunit": "^2.1", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.6", + "slim/http": "^1.3", + "slim/psr7": "^1.6", + "squizlabs/php_codesniffer": "^3.9" + }, + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "autoload-dev": { + "psr-4": { + "Slim\\Tests\\": "tests" + } + }, + "scripts": { + "test": [ + "@phpunit", + "@phpcs", + "@phpstan" + ], + "phpunit": "phpunit", + "phpcs": "phpcs", + "phpstan": "phpstan --memory-limit=-1" + }, + "suggest": { + "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware", + "ext-xml": "Needed to support XML format in BodyParsingMiddleware", + "slim/psr7": "Slim PSR-7 implementation. See https://www.slimframework.com/docs/v4/start/installation.html for more information.", + "php-di/php-di": "PHP-DI is the recommended container library to be used with Slim" + }, + "config": { + "sort-packages": true + } +} diff --git a/lib/thirdpartylibs.xml b/lib/thirdpartylibs.xml index 8f388fc3cb6..62fa9f82985 100644 --- a/lib/thirdpartylibs.xml +++ b/lib/thirdpartylibs.xml @@ -793,4 +793,19 @@ All rights reserved. MIT https://github.com/php-di/invoker
+ + slim + Slim Framework + 4.13.0 + MIT + https://github.com/slimphp/Slim + + + nikic/fast-route + FastRoute + 1.3.0 + BSD + 3-Clause + https://github.com/nikic/FastRoute + From f943311753ba3706bcae5aaf6c5be61ab961c833 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Fri, 19 Jul 2024 13:20:04 +0800 Subject: [PATCH 07/16] MDL-81031 core: Rename \core_user to \core\user --- .upgradenotes/MDL-81031-2024071905193761.yml | 13 +++++ lib/classes/user.php | 60 +++++++++++--------- 2 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 .upgradenotes/MDL-81031-2024071905193761.yml diff --git a/.upgradenotes/MDL-81031-2024071905193761.yml b/.upgradenotes/MDL-81031-2024071905193761.yml new file mode 100644 index 00000000000..3573257665f --- /dev/null +++ b/.upgradenotes/MDL-81031-2024071905193761.yml @@ -0,0 +1,13 @@ +issueNumber: MDL-81031 +notes: + core: + - message: | + The following classes have been renamed. + Existing classes are currently unaffected. + + | Old class name | New class name | + + | --- | --- | + + | `\core_user` | `\core\user` | + type: improved diff --git a/lib/classes/user.php b/lib/classes/user.php index 1f1a0be23ee..9778564b9b8 100644 --- a/lib/classes/user.php +++ b/lib/classes/user.php @@ -14,17 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * User class - * - * @package core - * @copyright 2013 Rajesh Taneja - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ +namespace core; +use core\context\user as context_user; +use core\context\course as context_course; +use core\context\system as context_system; use core_user\fields; - -defined('MOODLE_INTERNAL') || die(); +use core\exception\invalid_parameter_exception; +use core\exception\moodle_exception; +use core\exception\coding_exception; +use core\output\theme_config; +use core\output\user_picture; +use core_date; +use dml_exception; +use stdClass; /** * User class to access user details. @@ -34,7 +37,7 @@ defined('MOODLE_INTERNAL') || die(); * @copyright 2013 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class core_user { +class user { /** * No reply user id. */ @@ -104,7 +107,7 @@ class core_user { /** * Return user object from db or create noreply or support user, - * if userid matches corse_user::NOREPLY_USER or corse_user::SUPPORT_USER + * if userid matches \core\user::NOREPLY_USER or \core\user::SUPPORT_USER * respectively. If userid is not found, then return false. * * @param int $userid user id @@ -210,12 +213,12 @@ class core_user { * user identity fields. * * @param string $query Search query text - * @param \context_course|null $coursecontext Course context or null if system-wide + * @param context_course|null $coursecontext Course context or null if system-wide * @param int $max Max number of users to return, default 30 (zero = no limit) * @param int $querylimit Max number of database queries, default 5 (zero = no limit) * @return array Array of user objects with limited fields */ - public static function search($query, \context_course $coursecontext = null, + public static function search($query, context_course $coursecontext = null, $max = 30, $querylimit = 5) { global $CFG, $DB; require_once($CFG->dirroot . '/user/lib.php'); @@ -229,7 +232,7 @@ class core_user { } // Check permission to view profiles at each context. - $systemcontext = \context_system::instance(); + $systemcontext = context_system::instance(); $viewsystem = has_capability('moodle/user:viewdetails', $systemcontext); if ($viewsystem) { $userquery = 'SELECT id FROM {user}'; @@ -384,8 +387,8 @@ class core_user { $unionparams = []; foreach ($courses as $course) { // Get SQL to list user ids enrolled in this course. - \context_helper::preload_from_record($course); - list ($sql, $params) = get_enrolled_sql(\context_course::instance($course->id)); + context_helper::preload_from_record($course); + list ($sql, $params) = get_enrolled_sql(context_course::instance($course->id)); // Combine to a big union query. if ($unionsql) { @@ -594,7 +597,7 @@ class core_user { require_once("$CFG->libdir/gdlib.php"); $context = context_user::instance($usernew->id, MUST_EXIST); - $user = core_user::get_user($usernew->id, 'id, picture', MUST_EXIST); + $user = self::get_user($usernew->id, 'id, picture', MUST_EXIST); $newpicture = $user->picture; // Get file_storage to process files. @@ -806,7 +809,7 @@ class core_user { foreach ($user as $field => $value) { // Get the property parameter type and do the cleaning. try { - $user->$field = core_user::clean_field($value, $field); + $user->$field = self::clean_field($value, $field); } catch (coding_exception $e) { debugging("The property '$field' could not be cleaned.", DEBUG_DEVELOPER); } @@ -828,7 +831,7 @@ class core_user { } try { - $type = core_user::get_property_type($field); + $type = self::get_property_type($field); if (isset(self::$propertiescache[$field]['choices'])) { if (!array_key_exists($data, self::$propertiescache[$field]['choices'])) { @@ -941,7 +944,7 @@ class core_user { * 'isregex' => false/true // Whether the name of the preference is a regular expression (default false). * 'permissioncallback' => callable // Function accepting arguments ($user, $preferencename) that checks if current user * // is allowed to modify this preference for given user. - * // If not specified core_user::default_preference_permission_check() will be assumed. + * // If not specified \core\user::default_preference_permission_check() will be assumed. * 'cleancallback' => callable // Custom callback for cleaning value if something more difficult than just type/choices is needed * // accepts arguments ($value, $preferencename) * ) @@ -970,7 +973,7 @@ class core_user { 'choices' => array(0, 1)); $preferences['htmleditor'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED, 'cleancallback' => function($value, $preferencename) { - if (empty($value) || !array_key_exists($value, core_component::get_plugin_list('editor'))) { + if (empty($value) || !array_key_exists($value, component::get_plugin_list('editor'))) { return null; } return $value; @@ -1091,7 +1094,7 @@ class core_user { * @param string $preferencename * @return array */ - protected static function get_preference_definition($preferencename) { + public static function get_preference_definition($preferencename) { self::fill_preferences_cache(); foreach (self::$preferencescache as $key => $preference) { @@ -1412,9 +1415,9 @@ class core_user { * * @param stdClass $user the person to get details of. * @param context|null $context The context will be used to determine the visibility of the user's profile url. - * @return moodle_url Profile url of the user + * @return url Profile url of the user */ - public static function get_profile_url(stdClass $user, context $context = null): moodle_url { + public static function get_profile_url(stdClass $user, context $context = null): url { if (empty($user->id)) { throw new coding_exception('User id is required when displaying profile url.'); } @@ -1429,9 +1432,9 @@ class core_user { // If courseid is not set or is set to site id, then return profile page, otherwise return view page. if (!isset($params['courseid']) || $params['courseid'] == SITEID) { - return new moodle_url('/user/profile.php', $params); + return new url('/user/profile.php', $params); } else { - return new moodle_url('/user/view.php', $params); + return new url('/user/view.php', $params); } } @@ -1606,3 +1609,8 @@ class core_user { return $namefields; } } + +// Alias this class to the old name. +// This file will be autoloaded by the legacyclasses autoload system. +// In future all uses of this class will be corrected and the legacy references will be removed. +class_alias(user::class, \core_user::class); From d3d5d8bc73e37d11f6a0949d118e875fc4d7440b Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Fri, 19 Jul 2024 13:31:24 +0800 Subject: [PATCH 08/16] MDL-81031 core: Coding style fixes for \core\user --- lib/classes/user.php | 304 +++++++++++++++++++++++++++---------------- 1 file changed, 190 insertions(+), 114 deletions(-) diff --git a/lib/classes/user.php b/lib/classes/user.php index 9778564b9b8..6c62af7a0a1 100644 --- a/lib/classes/user.php +++ b/lib/classes/user.php @@ -32,7 +32,7 @@ use stdClass; /** * User class to access user details. * - * @todo move api's from user/lib.php and deprecate old ones. + * @todo MDL-82650 Move api's from user/lib.php and deprecate old ones. * @package core * @copyright 2013 Rajesh Taneja * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -83,7 +83,7 @@ class user { 'firstnamephonetic', 'lastnamephonetic', 'middlename', - 'alternatename' + 'alternatename', ]; /** @var int Indicates that user profile view should be prevented */ @@ -131,7 +131,7 @@ class user { return self::get_support_user(); break; default: - return $DB->get_record('user', array('id' => $userid), $fields, $strictness); + return $DB->get_record('user', ['id' => $userid], $fields, $strictness); } } @@ -156,7 +156,7 @@ class user { $mnethostid = $CFG->mnet_localhost_id; } - return $DB->get_record('user', array('email' => $email, 'mnethostid' => $mnethostid), $fields, $strictness); + return $DB->get_record('user', ['email' => $email, 'mnethostid' => $mnethostid], $fields, $strictness); } /** @@ -180,7 +180,7 @@ class user { $mnethostid = $CFG->mnet_localhost_id; } - return $DB->get_record('user', array('username' => $username, 'mnethostid' => $mnethostid), $fields, $strictness); + return $DB->get_record('user', ['username' => $username, 'mnethostid' => $mnethostid], $fields, $strictness); } /** @@ -218,8 +218,12 @@ class user { * @param int $querylimit Max number of database queries, default 5 (zero = no limit) * @return array Array of user objects with limited fields */ - public static function search($query, context_course $coursecontext = null, - $max = 30, $querylimit = 5) { + public static function search( + $query, + ?context_course $coursecontext = null, + $max = 30, + $querylimit = 5 + ) { global $CFG, $DB; require_once($CFG->dirroot . '/user/lib.php'); @@ -239,8 +243,9 @@ class user { $userparams = []; } if (!$viewsystem) { - list($userquery, $userparams) = self::get_enrolled_sql_on_courses_with_capability( - 'moodle/user:viewdetails'); + [$userquery, $userparams] = self::get_enrolled_sql_on_courses_with_capability( + 'moodle/user:viewdetails' + ); if (!$userquery) { // No permissions anywhere, return nothing. return []; @@ -248,7 +253,7 @@ class user { } // Start building the WHERE clause based on name. - list ($where, $whereparams) = users_search_sql($query, 'u'); + [$where, $whereparams] = users_search_sql($query, 'u'); // We allow users to search with extra identity fields (as well as name) but only if they // have the permission to display those identity fields. @@ -281,8 +286,9 @@ class user { $whereparams = array_merge($whereparams, $extraparams); } else { // Get all courses where user can view full user identity. - list($sql, $params) = self::get_enrolled_sql_on_courses_with_capability( - 'moodle/site:viewuseridentity'); + [$sql, $params] = self::get_enrolled_sql_on_courses_with_capability( + 'moodle/site:viewuseridentity' + ); if ($sql) { // Join that with the user query to get an extra field indicating if we can. $userquery = " @@ -302,9 +308,9 @@ class user { // Default order is just name order. But if searching within a course then we show users // within the course first. - list ($order, $orderparams) = users_order_by_sql('u', $query, $systemcontext); + [$order, $orderparams] = users_order_by_sql('u', $query, $systemcontext); if ($coursecontext) { - list ($sql, $params) = get_enrolled_sql($coursecontext); + [$sql, $params] = get_enrolled_sql($coursecontext); $mainfield = 'innerusers2.id'; if ($usingshowidentity) { $mainfield .= ', innerusers2.showidentity'; @@ -326,13 +332,17 @@ class user { $pos = 0; $readcount = $max + 2; for ($i = 0; $i < $querylimit; $i++) { - $rawresult = $DB->get_records_sql(" + $rawresult = $DB->get_records_sql( + " SELECT $selectfields FROM ($userquery) users JOIN {user} u ON u.id = users.id WHERE $where - ORDER BY $order", array_merge($userparams, $whereparams, $orderparams), - $pos, $readcount); + ORDER BY $order", + array_merge($userparams, $whereparams, $orderparams), + $pos, + $readcount + ); foreach ($rawresult as $user) { // Skip guest. if ($user->username === 'guest') { @@ -374,8 +384,12 @@ class user { */ protected static function get_enrolled_sql_on_courses_with_capability($capability) { // Get all courses where user have the capability. - $courses = get_user_capability_course($capability, null, true, - implode(',', array_values(context_helper::get_preload_record_columns('ctx')))); + $courses = get_user_capability_course( + $capability, + null, + true, + implode(',', array_values(context_helper::get_preload_record_columns('ctx'))) + ); if (!$courses) { return [null, null]; } @@ -388,7 +402,7 @@ class user { foreach ($courses as $course) { // Get SQL to list user ids enrolled in this course. context_helper::preload_from_record($course); - list ($sql, $params) = get_enrolled_sql(context_course::instance($course->id)); + [$sql, $params] = get_enrolled_sql(context_course::instance($course->id)); // Combine to a big union query. if ($unionsql) { @@ -531,7 +545,7 @@ class user { return false; } if ($checkdb) { - return $DB->record_exists('user', array('id' => $userid)); + return $DB->record_exists('user', ['id' => $userid]); } else { return true; } @@ -576,11 +590,11 @@ class user { throw new moodle_exception('guestsarenotallowed', 'error'); } - if ($checksuspended and $user->suspended) { + if ($checksuspended && $user->suspended) { throw new moodle_exception('suspended', 'auth'); } - if ($checknologin and $user->auth == 'nologin') { + if ($checknologin && $user->auth == 'nologin') { throw new moodle_exception('suspended', 'auth'); } } @@ -592,7 +606,7 @@ class user { * @param array $filemanageroptions * @return bool True if the user was updated, false if it stayed the same. */ - public static function update_picture(stdClass $usernew, $filemanageroptions = array()) { + public static function update_picture(stdClass $usernew, $filemanageroptions = []) { global $CFG, $DB; require_once("$CFG->libdir/gdlib.php"); @@ -639,7 +653,7 @@ class user { } if ($newpicture != $user->picture) { - $DB->set_field('user', 'picture', $newpicture, array('id' => $user->id)); + $DB->set_field('user', 'picture', $newpicture, ['id' => $user->id]); return true; } else { return false; @@ -672,66 +686,112 @@ class user { // Array of user fields properties and expected parameters. // Every new field on the user table should be added here otherwise it won't be validated. - $fields = array(); - $fields['id'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED); - $fields['auth'] = array('type' => PARAM_AUTH, 'null' => NULL_NOT_ALLOWED); - $fields['confirmed'] = array('type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED); - $fields['policyagreed'] = array('type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED); - $fields['deleted'] = array('type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED); - $fields['suspended'] = array('type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED); - $fields['mnethostid'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED); - $fields['username'] = array('type' => PARAM_USERNAME, 'null' => NULL_NOT_ALLOWED); - $fields['password'] = array('type' => PARAM_RAW, 'null' => NULL_NOT_ALLOWED); - $fields['idnumber'] = array('type' => PARAM_RAW, 'null' => NULL_NOT_ALLOWED); - $fields['firstname'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED); - $fields['lastname'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED); - $fields['surname'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED); - $fields['email'] = array('type' => PARAM_RAW_TRIMMED, 'null' => NULL_NOT_ALLOWED); - $fields['emailstop'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0); - $fields['phone1'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED); - $fields['phone2'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED); - $fields['institution'] = array('type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED); - $fields['department'] = array('type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED); - $fields['address'] = array('type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED); - $fields['city'] = array('type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->defaultcity); - $fields['country'] = array('type' => PARAM_ALPHA, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->country, - 'choices' => array_merge(array('' => ''), get_string_manager()->get_list_of_countries(true, true))); - $fields['lang'] = array('type' => PARAM_LANG, 'null' => NULL_NOT_ALLOWED, - 'default' => (!empty($CFG->autolangusercreation) && !empty($SESSION->lang)) ? $SESSION->lang : $CFG->lang, - 'choices' => array_merge(array('' => ''), get_string_manager()->get_list_of_translations(false))); - $fields['calendartype'] = array('type' => PARAM_PLUGIN, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->calendartype, - 'choices' => array_merge(array('' => ''), \core_calendar\type_factory::get_list_of_calendar_types())); - $fields['theme'] = array('type' => PARAM_THEME, 'null' => NULL_NOT_ALLOWED, - 'default' => theme_config::DEFAULT_THEME, 'choices' => array_merge(array('' => ''), get_list_of_themes())); - $fields['timezone'] = array('type' => PARAM_TIMEZONE, 'null' => NULL_NOT_ALLOWED, - 'default' => core_date::get_server_timezone()); // Must not use choices here: timezones can come and go. - $fields['firstaccess'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED); - $fields['lastaccess'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED); - $fields['lastlogin'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED); - $fields['currentlogin'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED); - $fields['lastip'] = array('type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED); - $fields['secret'] = array('type' => PARAM_ALPHANUM, 'null' => NULL_NOT_ALLOWED); - $fields['picture'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED); - $fields['description'] = array('type' => PARAM_RAW, 'null' => NULL_ALLOWED); - $fields['descriptionformat'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED); - $fields['mailformat'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, - 'default' => $CFG->defaultpreference_mailformat); - $fields['maildigest'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, - 'default' => $CFG->defaultpreference_maildigest); - $fields['maildisplay'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, - 'default' => $CFG->defaultpreference_maildisplay); - $fields['autosubscribe'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, - 'default' => $CFG->defaultpreference_autosubscribe); - $fields['trackforums'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, - 'default' => $CFG->defaultpreference_trackforums); - $fields['timecreated'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED); - $fields['timemodified'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED); - $fields['trustbitmask'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED); - $fields['imagealt'] = array('type' => PARAM_TEXT, 'null' => NULL_ALLOWED); - $fields['lastnamephonetic'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED); - $fields['firstnamephonetic'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED); - $fields['middlename'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED); - $fields['alternatename'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED); + $fields = []; + $fields['id'] = ['type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED]; + $fields['auth'] = ['type' => PARAM_AUTH, 'null' => NULL_NOT_ALLOWED]; + $fields['confirmed'] = ['type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED]; + $fields['policyagreed'] = ['type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED]; + $fields['deleted'] = ['type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED]; + $fields['suspended'] = ['type' => PARAM_BOOL, 'null' => NULL_NOT_ALLOWED]; + $fields['mnethostid'] = ['type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED]; + $fields['username'] = ['type' => PARAM_USERNAME, 'null' => NULL_NOT_ALLOWED]; + $fields['password'] = ['type' => PARAM_RAW, 'null' => NULL_NOT_ALLOWED]; + $fields['idnumber'] = ['type' => PARAM_RAW, 'null' => NULL_NOT_ALLOWED]; + $fields['firstname'] = ['type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED]; + $fields['lastname'] = ['type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED]; + $fields['surname'] = ['type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED]; + $fields['email'] = ['type' => PARAM_RAW_TRIMMED, 'null' => NULL_NOT_ALLOWED]; + $fields['emailstop'] = ['type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 0]; + $fields['phone1'] = ['type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED]; + $fields['phone2'] = ['type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED]; + $fields['institution'] = ['type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED]; + $fields['department'] = ['type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED]; + $fields['address'] = ['type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED]; + $fields['city'] = ['type' => PARAM_TEXT, 'null' => NULL_NOT_ALLOWED, 'default' => $CFG->defaultcity]; + $fields['country'] = [ + 'type' => PARAM_ALPHA, + 'null' => NULL_NOT_ALLOWED, + 'default' => $CFG->country, + 'choices' => array_merge( + ['' => ''], + get_string_manager()->get_list_of_countries(true, true) + ), + ]; + $fields['lang'] = [ + 'type' => PARAM_LANG, + 'null' => NULL_NOT_ALLOWED, + 'default' => (!empty($CFG->autolangusercreation) && !empty($SESSION->lang)) ? $SESSION->lang : $CFG->lang, + 'choices' => array_merge( + ['' => ''], + get_string_manager()->get_list_of_translations(false) + ), + ]; + $fields['calendartype'] = [ + 'type' => PARAM_PLUGIN, + 'null' => NULL_NOT_ALLOWED, + 'default' => $CFG->calendartype, + 'choices' => array_merge( + ['' => ''], + \core_calendar\type_factory::get_list_of_calendar_types() + ), + ]; + $fields['theme'] = [ + 'type' => PARAM_THEME, + 'null' => NULL_NOT_ALLOWED, + 'default' => theme_config::DEFAULT_THEME, + 'choices' => array_merge( + ['' => ''], + get_list_of_themes() + ), + ]; + $fields['timezone'] = [ + // Must not use choices here: timezones can come and go. + 'type' => PARAM_TIMEZONE, + 'null' => NULL_NOT_ALLOWED, + 'default' => core_date::get_server_timezone(), + ]; + $fields['firstaccess'] = ['type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED]; + $fields['lastaccess'] = ['type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED]; + $fields['lastlogin'] = ['type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED]; + $fields['currentlogin'] = ['type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED]; + $fields['lastip'] = ['type' => PARAM_NOTAGS, 'null' => NULL_NOT_ALLOWED]; + $fields['secret'] = ['type' => PARAM_ALPHANUM, 'null' => NULL_NOT_ALLOWED]; + $fields['picture'] = ['type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED]; + $fields['description'] = ['type' => PARAM_RAW, 'null' => NULL_ALLOWED]; + $fields['descriptionformat'] = ['type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED]; + $fields['mailformat'] = [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + 'default' => $CFG->defaultpreference_mailformat, + ]; + $fields['maildigest'] = [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + 'default' => $CFG->defaultpreference_maildigest, + ]; + $fields['maildisplay'] = [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + 'default' => $CFG->defaultpreference_maildisplay, + ]; + $fields['autosubscribe'] = [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + 'default' => $CFG->defaultpreference_autosubscribe, + ]; + $fields['trackforums'] = [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + 'default' => $CFG->defaultpreference_trackforums, + ]; + $fields['timecreated'] = ['type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED]; + $fields['timemodified'] = ['type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED]; + $fields['trustbitmask'] = ['type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED]; + $fields['imagealt'] = ['type' => PARAM_TEXT, 'null' => NULL_ALLOWED]; + $fields['lastnamephonetic'] = ['type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED]; + $fields['firstnamephonetic'] = ['type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED]; + $fields['middlename'] = ['type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED]; + $fields['alternatename'] = ['type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED]; self::$propertiescache = $fields; } @@ -773,8 +833,10 @@ class user { validate_param($value, self::$propertiescache[$property]['type'], self::$propertiescache[$property]['null']); } // Check that the value is part of a list of allowed values. - if (!empty(self::$propertiescache[$property]['choices']) && - !isset(self::$propertiescache[$property]['choices'][$value])) { + if ( + !empty(self::$propertiescache[$property]['choices']) && + !isset(self::$propertiescache[$property]['choices'][$value]) + ) { throw new invalid_parameter_exception($value); } } catch (invalid_parameter_exception $e) { @@ -903,9 +965,10 @@ class user { self::fill_properties_cache(); - if (!array_key_exists($property, self::$propertiescache) && !array_key_exists('choices', - self::$propertiescache[$property])) { - + if ( + !array_key_exists($property, self::$propertiescache) && + !array_key_exists('choices', self::$propertiescache[$property]) + ) { throw new coding_exception('Invalid property requested, or the property does not has a list of choices.'); } @@ -942,11 +1005,12 @@ class user { * 'choices' => array(1, 2..) // An array of accepted values of the user field - optional * 'default' => $CFG->setting // An default value for the field - optional * 'isregex' => false/true // Whether the name of the preference is a regular expression (default false). - * 'permissioncallback' => callable // Function accepting arguments ($user, $preferencename) that checks if current user + * 'permissioncallback' => callable // Function accepting arguments ($user, $preferencename) that checks if current + * // user * // is allowed to modify this preference for given user. * // If not specified \core\user::default_preference_permission_check() will be assumed. - * 'cleancallback' => callable // Custom callback for cleaning value if something more difficult than just type/choices is needed - * // accepts arguments ($value, $preferencename) + * 'cleancallback' => callable // Custom callback for cleaning value if something more difficult than just type/choices + * // is needed accepts arguments ($value, $preferencename) * ) * ) * @@ -961,32 +1025,44 @@ class user { // Array of user preferences and expected types/values. // Every preference that can be updated directly by user should be added here. - $preferences = array(); - $preferences['auth_forcepasswordchange'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'choices' => array(0, 1), - 'permissioncallback' => function($user, $preferencename) { + $preferences = []; + $preferences['auth_forcepasswordchange'] = [ + 'type' => PARAM_INT, + 'null' => NULL_NOT_ALLOWED, + 'choices' => [0, 1], + 'permissioncallback' => function ($user, $preferencename) { global $USER; $systemcontext = context_system::instance(); return ($USER->id != $user->id && (has_capability('moodle/user:update', $systemcontext) || ($user->timecreated > time() - 10 && has_capability('moodle/user:create', $systemcontext)))); - }); - $preferences['forum_markasreadonnotification'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 1, - 'choices' => array(0, 1)); - $preferences['htmleditor'] = array('type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED, - 'cleancallback' => function($value, $preferencename) { + }, + ]; + $preferences['forum_markasreadonnotification'] = [ + 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 1, + 'choices' => [0, 1], + ]; + $preferences['htmleditor'] = [ + 'type' => PARAM_NOTAGS, 'null' => NULL_ALLOWED, + 'cleancallback' => function ($value, $preferencename) { if (empty($value) || !array_key_exists($value, component::get_plugin_list('editor'))) { return null; } return $value; - }); - $preferences['badgeprivacysetting'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 1, - 'choices' => array(0, 1), 'permissioncallback' => function($user, $preferencename) { + }, + ]; + $preferences['badgeprivacysetting'] = [ + 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 1, + 'choices' => [0, 1], 'permissioncallback' => function ($user, $preferencename) { global $CFG; return !empty($CFG->enablebadges) && self::is_current_user($user); - }); - $preferences['blogpagesize'] = array('type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 10, - 'permissioncallback' => function($user, $preferencename) { + }, + ]; + $preferences['blogpagesize'] = [ + 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, 'default' => 10, + 'permissioncallback' => function ($user, $preferencename) { return self::is_current_user($user) && has_capability('moodle/blog:view', context_system::instance()); - }); + }, + ]; $preferences['filemanager_recentviewmode'] = [ 'type' => PARAM_INT, 'null' => NULL_NOT_ALLOWED, @@ -1061,7 +1137,7 @@ class user { global $CFG; return self::is_current_user($user) && (!empty($CFG->defaulthomepage) && ($CFG->defaulthomepage == HOMEPAGE_USER)); - } + }, ]; // Core components that may want to define their preferences. @@ -1130,13 +1206,13 @@ class user { // Editing own profile. $systemcontext = context_system::instance(); return has_capability('moodle/user:editownprofile', $systemcontext); - } else { + } else { // Teachers, parents, etc. $personalcontext = context_user::instance($user->id); if (!has_capability('moodle/user:editprofile', $personalcontext)) { return false; } - if (is_siteadmin($user->id) and !is_siteadmin($USER)) { + if (is_siteadmin($user->id) && !is_siteadmin($USER)) { // Only admins may edit other admins. return false; } @@ -1364,7 +1440,7 @@ class user { } } - $requirednames = array(); + $requirednames = []; // With each name, see if it is in the display name template, and add it to the required names array if it is. foreach ($allnames as $allname) { if (strpos($template, $allname) !== false) { @@ -1389,7 +1465,7 @@ class user { // Tidy up any misc. characters (Not perfect, but gets most characters). // Don't remove the "u" at the end of the first expression unless you want garbled characters when combining hiragana or // katakana and parenthesis. - $patterns = array(); + $patterns = []; // This regular expression replacement is to fix problems such as 'James () Kirk' Where 'Tiberius' (middlename) has not been // filled in by a user. // The special characters are Japanese brackets that are common enough to make allowances for them (not covered by :punct:). @@ -1417,7 +1493,7 @@ class user { * @param context|null $context The context will be used to determine the visibility of the user's profile url. * @return url Profile url of the user */ - public static function get_profile_url(stdClass $user, context $context = null): url { + public static function get_profile_url(stdClass $user, ?context $context = null): url { if (empty($user->id)) { throw new coding_exception('User id is required when displaying profile url.'); } From 1b7d08465c0e151132155f569e700fcef39c1e37 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 31 Oct 2023 10:17:37 +0800 Subject: [PATCH 09/16] MDL-81031 core: Add routing --- lang/en/cache.php | 1 + lang/en/error.php | 1 + lib/apis.json | 10 + lib/classes/component.php | 2 +- .../exception/access_denied_exception.php | 33 ++ lib/classes/exception/not_found_exception.php | 52 ++ .../exception/response_aware_exception.php | 33 ++ lib/classes/hook/di_configuration.php | 6 +- .../page_requirements_manager.php | 24 + lib/classes/output/routed_error_handler.php | 50 ++ lib/classes/router.php | 259 ++++++++ lib/classes/router/abstract_route_loader.php | 209 +++++++ lib/classes/router/apidocs.php | 159 +++++ lib/classes/router/bridge.php | 95 +++ lib/classes/router/callable_resolver.php | 106 ++++ lib/classes/router/controller_invoker.php | 98 +++ lib/classes/router/hook_callbacks.php | 49 ++ .../router/middleware/cors_middleware.php | 47 ++ .../middleware/error_handling_middleware.php | 61 ++ .../moodle_bootstrap_middleware.php | 101 ++++ .../moodle_route_attribute_middleware.php | 39 ++ .../uri_normalisation_middleware.php | 61 ++ .../middleware/validation_middleware.php | 72 +++ .../router/parameters/header_language.php | 69 +++ .../router/parameters/path_component.php | 65 ++ lib/classes/router/parameters/path_course.php | 135 +++++ .../router/parameters/path_themename.php | 58 ++ lib/classes/router/parameters/path_user.php | 143 +++++ lib/classes/router/request_validator.php | 197 ++++++ .../router/request_validator_interface.php | 38 ++ .../response/access_denied_response.php | 36 ++ .../router/response/empty_response.php | 37 ++ .../router/response/exception_response.php | 135 +++++ .../response/invalid_parameter_response.php | 36 ++ .../router/response/not_found_response.php | 36 ++ lib/classes/router/response_handler.php | 103 ++++ lib/classes/router/response_validator.php | 50 ++ .../router/response_validator_interface.php | 40 ++ lib/classes/router/route.php | 318 ++++++++++ lib/classes/router/route_controller.php | 139 +++++ lib/classes/router/route_loader.php | 83 +++ lib/classes/router/route_loader_interface.php | 43 ++ lib/classes/router/schema/example.php | 109 ++++ lib/classes/router/schema/header_object.php | 46 ++ .../schema/objects/array_of_strings.php | 78 +++ .../router/schema/objects/array_of_things.php | 93 +++ .../router/schema/objects/scalar_type.php | 65 ++ .../router/schema/objects/schema_object.php | 113 ++++ .../router/schema/objects/stacktrace.php | 128 ++++ .../router/schema/objects/type_base.php | 95 +++ lib/classes/router/schema/openapi_base.php | 239 ++++++++ lib/classes/router/schema/parameter.php | 176 ++++++ .../schema/parameters/header_object.php | 106 ++++ .../parameters/mapped_property_parameter.php | 40 ++ .../schema/parameters/path_parameter.php | 113 ++++ .../schema/parameters/query_parameter.php | 147 +++++ .../router/schema/referenced_object.php | 29 + lib/classes/router/schema/request_body.php | 141 +++++ .../schema/response/abstract_response.php | 59 ++ .../response/content/json_media_type.php | 31 + .../schema/response/content/media_type.php | 128 ++++ .../content/payload_response_type.php | 112 ++++ .../schema/response/payload_response.php | 83 +++ .../router/schema/response/response.php | 135 +++++ .../router/schema/response/response_type.php | 47 ++ .../router/schema/response/view_response.php | 88 +++ lib/classes/router/schema/specification.php | 521 ++++++++++++++++ lib/classes/router/util.php | 230 +++++++ lib/classes/tests/route_testcase.php | 549 +++++++++++++++++ lib/classes/user.php | 31 + lib/db/caches.php | 7 + lib/db/hooks.php | 4 + lib/dmllib.php | 9 +- .../fixtures/router/mocking_route_loader.php | 150 +++++ ...implementing_request_handler_interface.php | 31 + lib/tests/fixtures/router/route_on_class.php | 53 ++ .../fixtures/router/route_on_method_only.php | 50 ++ .../router/abstract_route_loader_test.php | 141 +++++ lib/tests/router/apidocs_test.php | 40 ++ lib/tests/router/callable_resolver_test.php | 80 +++ lib/tests/router/controller_invoker_test.php | 138 +++++ .../middleware/cors_middleware_test.php | 84 +++ .../error_handling_middleware_test.php | 80 +++ .../moodle_bootstrap_middleware_test.php | 50 ++ ...moodle_route_attribute_middleware_test.php | 103 ++++ .../uri_normalisation_middleware_test.php | 86 +++ .../middleware/validation_middleware_test.php | 140 +++++ .../parameters/header_language_test.php | 113 ++++ .../router/parameters/path_component_test.php | 136 +++++ .../router/parameters/path_course_test.php | 170 ++++++ .../router/parameters/path_themename_test.php | 136 +++++ .../router/parameters/path_user_test.php | 194 ++++++ lib/tests/router/request_validator_test.php | 391 ++++++++++++ .../response/access_denied_response_test.php | 70 +++ .../router/response/empty_response_test.php | 48 ++ .../response/exception_response_test.php | 44 ++ .../invalid_parameter_response_test.php | 65 ++ .../response/not_found_response_test.php | 70 +++ lib/tests/router/response_handler_test.php | 195 ++++++ lib/tests/router/response_validator_test.php | 80 +++ lib/tests/router/route_controller_test.php | 151 +++++ lib/tests/router/route_loader_test.php | 91 +++ lib/tests/router/route_test.php | 561 ++++++++++++++++++ lib/tests/router/schema/example_test.php | 95 +++ .../router/schema/header_object_test.php | 85 +++ .../schema/objects/array_of_strings_test.php | 108 ++++ .../schema/objects/array_of_things_test.php | 201 +++++++ .../schema/objects/scalar_type_test.php | 80 +++ .../schema/objects/schema_object_test.php | 103 ++++ .../router/schema/objects/stacktrace_test.php | 62 ++ lib/tests/router/schema/parameter_test.php | 191 ++++++ .../schema/parameters/header_object_test.php | 171 ++++++ .../schema/parameters/path_parameter_test.php | 165 ++++++ .../parameters/query_parameter_test.php | 316 ++++++++++ lib/tests/router/schema/request_body_test.php | 136 +++++ .../response/content/json_media_type_test.php | 157 +++++ .../content/payload_response_type_test.php | 111 ++++ .../schema/response/payload_response_test.php | 81 +++ .../router/schema/response/response_test.php | 152 +++++ .../schema/response/view_response_test.php | 63 ++ .../router/schema/specification_test.php | 353 +++++++++++ lib/tests/router/util_test.php | 153 +++++ lib/tests/router_test.php | 109 ++++ lib/weblib.php | 2 +- r.php | 32 + user/tests/route/api/preferences_test.php | 96 +++ 126 files changed, 13839 insertions(+), 5 deletions(-) create mode 100644 lib/classes/exception/access_denied_exception.php create mode 100644 lib/classes/exception/not_found_exception.php create mode 100644 lib/classes/exception/response_aware_exception.php create mode 100644 lib/classes/output/routed_error_handler.php create mode 100644 lib/classes/router.php create mode 100644 lib/classes/router/abstract_route_loader.php create mode 100644 lib/classes/router/apidocs.php create mode 100644 lib/classes/router/bridge.php create mode 100644 lib/classes/router/callable_resolver.php create mode 100644 lib/classes/router/controller_invoker.php create mode 100644 lib/classes/router/hook_callbacks.php create mode 100644 lib/classes/router/middleware/cors_middleware.php create mode 100644 lib/classes/router/middleware/error_handling_middleware.php create mode 100644 lib/classes/router/middleware/moodle_bootstrap_middleware.php create mode 100644 lib/classes/router/middleware/moodle_route_attribute_middleware.php create mode 100644 lib/classes/router/middleware/uri_normalisation_middleware.php create mode 100644 lib/classes/router/middleware/validation_middleware.php create mode 100644 lib/classes/router/parameters/header_language.php create mode 100644 lib/classes/router/parameters/path_component.php create mode 100644 lib/classes/router/parameters/path_course.php create mode 100644 lib/classes/router/parameters/path_themename.php create mode 100644 lib/classes/router/parameters/path_user.php create mode 100644 lib/classes/router/request_validator.php create mode 100644 lib/classes/router/request_validator_interface.php create mode 100644 lib/classes/router/response/access_denied_response.php create mode 100644 lib/classes/router/response/empty_response.php create mode 100644 lib/classes/router/response/exception_response.php create mode 100644 lib/classes/router/response/invalid_parameter_response.php create mode 100644 lib/classes/router/response/not_found_response.php create mode 100644 lib/classes/router/response_handler.php create mode 100644 lib/classes/router/response_validator.php create mode 100644 lib/classes/router/response_validator_interface.php create mode 100644 lib/classes/router/route.php create mode 100644 lib/classes/router/route_controller.php create mode 100644 lib/classes/router/route_loader.php create mode 100644 lib/classes/router/route_loader_interface.php create mode 100644 lib/classes/router/schema/example.php create mode 100644 lib/classes/router/schema/header_object.php create mode 100644 lib/classes/router/schema/objects/array_of_strings.php create mode 100644 lib/classes/router/schema/objects/array_of_things.php create mode 100644 lib/classes/router/schema/objects/scalar_type.php create mode 100644 lib/classes/router/schema/objects/schema_object.php create mode 100644 lib/classes/router/schema/objects/stacktrace.php create mode 100644 lib/classes/router/schema/objects/type_base.php create mode 100644 lib/classes/router/schema/openapi_base.php create mode 100644 lib/classes/router/schema/parameter.php create mode 100644 lib/classes/router/schema/parameters/header_object.php create mode 100644 lib/classes/router/schema/parameters/mapped_property_parameter.php create mode 100644 lib/classes/router/schema/parameters/path_parameter.php create mode 100644 lib/classes/router/schema/parameters/query_parameter.php create mode 100644 lib/classes/router/schema/referenced_object.php create mode 100644 lib/classes/router/schema/request_body.php create mode 100644 lib/classes/router/schema/response/abstract_response.php create mode 100644 lib/classes/router/schema/response/content/json_media_type.php create mode 100644 lib/classes/router/schema/response/content/media_type.php create mode 100644 lib/classes/router/schema/response/content/payload_response_type.php create mode 100644 lib/classes/router/schema/response/payload_response.php create mode 100644 lib/classes/router/schema/response/response.php create mode 100644 lib/classes/router/schema/response/response_type.php create mode 100644 lib/classes/router/schema/response/view_response.php create mode 100644 lib/classes/router/schema/specification.php create mode 100644 lib/classes/router/util.php create mode 100644 lib/classes/tests/route_testcase.php create mode 100644 lib/tests/fixtures/router/mocking_route_loader.php create mode 100644 lib/tests/fixtures/router/route_implementing_request_handler_interface.php create mode 100644 lib/tests/fixtures/router/route_on_class.php create mode 100644 lib/tests/fixtures/router/route_on_method_only.php create mode 100644 lib/tests/router/abstract_route_loader_test.php create mode 100644 lib/tests/router/apidocs_test.php create mode 100644 lib/tests/router/callable_resolver_test.php create mode 100644 lib/tests/router/controller_invoker_test.php create mode 100644 lib/tests/router/middleware/cors_middleware_test.php create mode 100644 lib/tests/router/middleware/error_handling_middleware_test.php create mode 100644 lib/tests/router/middleware/moodle_bootstrap_middleware_test.php create mode 100644 lib/tests/router/middleware/moodle_route_attribute_middleware_test.php create mode 100644 lib/tests/router/middleware/uri_normalisation_middleware_test.php create mode 100644 lib/tests/router/middleware/validation_middleware_test.php create mode 100644 lib/tests/router/parameters/header_language_test.php create mode 100644 lib/tests/router/parameters/path_component_test.php create mode 100644 lib/tests/router/parameters/path_course_test.php create mode 100644 lib/tests/router/parameters/path_themename_test.php create mode 100644 lib/tests/router/parameters/path_user_test.php create mode 100644 lib/tests/router/request_validator_test.php create mode 100644 lib/tests/router/response/access_denied_response_test.php create mode 100644 lib/tests/router/response/empty_response_test.php create mode 100644 lib/tests/router/response/exception_response_test.php create mode 100644 lib/tests/router/response/invalid_parameter_response_test.php create mode 100644 lib/tests/router/response/not_found_response_test.php create mode 100644 lib/tests/router/response_handler_test.php create mode 100644 lib/tests/router/response_validator_test.php create mode 100644 lib/tests/router/route_controller_test.php create mode 100644 lib/tests/router/route_loader_test.php create mode 100644 lib/tests/router/route_test.php create mode 100644 lib/tests/router/schema/example_test.php create mode 100644 lib/tests/router/schema/header_object_test.php create mode 100644 lib/tests/router/schema/objects/array_of_strings_test.php create mode 100644 lib/tests/router/schema/objects/array_of_things_test.php create mode 100644 lib/tests/router/schema/objects/scalar_type_test.php create mode 100644 lib/tests/router/schema/objects/schema_object_test.php create mode 100644 lib/tests/router/schema/objects/stacktrace_test.php create mode 100644 lib/tests/router/schema/parameter_test.php create mode 100644 lib/tests/router/schema/parameters/header_object_test.php create mode 100644 lib/tests/router/schema/parameters/path_parameter_test.php create mode 100644 lib/tests/router/schema/parameters/query_parameter_test.php create mode 100644 lib/tests/router/schema/request_body_test.php create mode 100644 lib/tests/router/schema/response/content/json_media_type_test.php create mode 100644 lib/tests/router/schema/response/content/payload_response_type_test.php create mode 100644 lib/tests/router/schema/response/payload_response_test.php create mode 100644 lib/tests/router/schema/response/response_test.php create mode 100644 lib/tests/router/schema/response/view_response_test.php create mode 100644 lib/tests/router/schema/specification_test.php create mode 100644 lib/tests/router/util_test.php create mode 100644 lib/tests/router_test.php create mode 100644 r.php create mode 100644 user/tests/route/api/preferences_test.php diff --git a/lang/en/cache.php b/lang/en/cache.php index 6c7dd1abbdb..ca008b15895 100644 --- a/lang/en/cache.php +++ b/lang/en/cache.php @@ -63,6 +63,7 @@ $string['cachedef_eventinvalidation'] = 'Event invalidation'; $string['cachedef_externalbadges'] = 'External badges for particular user'; $string['cachedef_fontawesomeiconmapping'] = 'Mapping of icons for font awesome'; $string['cachedef_file_imageinfo'] = 'File image info e.g. dimensions'; +$string['cachedef_routes'] = 'Route data'; $string['cachedef_suspended_userids'] = 'List of suspended users per course'; $string['cachedef_groupdata'] = 'Course group information'; $string['cachedef_h5p_content_type_translations'] = 'H5P content-type libraries translations'; diff --git a/lang/en/error.php b/lang/en/error.php index d5c3234420a..fd4d40da25e 100644 --- a/lang/en/error.php +++ b/lang/en/error.php @@ -390,6 +390,7 @@ $string['invalidxmlfile'] = '"{$a}" is not a valid XML file'; $string['iplookupfailed'] = 'Cannot find geo information about this IP address {$a}'; $string['iplookupprivate'] = 'Cannot display lookup of private IP address'; $string['ipmismatch'] = 'Client IP address mismatch'; +$string['itemnotfound'] = 'No {$a->itemtype} was found with an identifier of \'{$a->identifier}\''; $string['listcantmovedown'] = 'Failed to move item down, as it is the last of its peers.'; $string['listcantmoveleft'] = 'Failed to move item left, as it has no parent'; $string['listcantmoveright'] = 'Failed to move item right, as there is no peer to make it a child of. Move it below another peer and then you can move it right.'; diff --git a/lib/apis.json b/lib/apis.json index 7d6288fe648..ce492a5bb8f 100644 --- a/lib/apis.json +++ b/lib/apis.json @@ -234,6 +234,16 @@ "allowedlevel2": true, "allowedspread": true }, + "route": { + "component": "core", + "allowedlevel2": true, + "allowedspread": true + }, + "router": { + "component": "core", + "allowedlevel2": true, + "allowedspread": true + }, "rss": { "component": "core_rss", "allowedlevel2": false, diff --git a/lib/classes/component.php b/lib/classes/component.php index ab495b07671..7da97fbf726 100644 --- a/lib/classes/component.php +++ b/lib/classes/component.php @@ -452,7 +452,7 @@ class component { $keyclasses = [ \core\exception\moodle_exception::class, \core\output\bootstrap_renderer::class, - \core_filters\filter_manager::class, + \core\router::class, ]; foreach ($keyclasses as $classname) { if (!array_key_exists($classname, $cache['classmap'])) { diff --git a/lib/classes/exception/access_denied_exception.php b/lib/classes/exception/access_denied_exception.php new file mode 100644 index 00000000000..b8e474269b0 --- /dev/null +++ b/lib/classes/exception/access_denied_exception.php @@ -0,0 +1,33 @@ +. + +namespace core\exception; + +use core\router\response\access_denied_response; + +/** + * An exception to describe the case where access has been denied to a resource. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class access_denied_exception extends moodle_exception implements response_aware_exception { + #[\Override] + public function get_response_classname(): string { + return access_denied_response::class; + } +} diff --git a/lib/classes/exception/not_found_exception.php b/lib/classes/exception/not_found_exception.php new file mode 100644 index 00000000000..d85e2ac0265 --- /dev/null +++ b/lib/classes/exception/not_found_exception.php @@ -0,0 +1,52 @@ +. + +namespace core\exception; +use core\router\response\not_found_response; + +/** + * An exception to describe the case where a requested item was not found. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + + */ +class not_found_exception extends \moodle_exception implements response_aware_exception { + /** + * Constructor for a new not found exception. + * + * @param string $itemtype The type of item that was not found. + * @param string $identifier The identifier of the item that was not found. + */ + public function __construct( + string $itemtype, + string $identifier, + ) { + parent::__construct( + errorcode: 'itemnotfound', + a: [ + 'itemtype' => $itemtype, + 'identifier' => $identifier, + ], + ); + } + + #[\Override] + public function get_response_classname(): string { + return not_found_response::class; + } +} diff --git a/lib/classes/exception/response_aware_exception.php b/lib/classes/exception/response_aware_exception.php new file mode 100644 index 00000000000..b6c1ca953cb --- /dev/null +++ b/lib/classes/exception/response_aware_exception.php @@ -0,0 +1,33 @@ +. + +namespace core\exception; + +/** + * An exception which is aware of the response class that should be used to handle it. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface response_aware_exception { + /** + * Get the class name of the response that should be used to handle this exception. + * + * @return string + */ + public function get_response_classname(): string; +} diff --git a/lib/classes/hook/di_configuration.php b/lib/classes/hook/di_configuration.php index 806abec5ae5..e7a7142c30c 100644 --- a/lib/classes/hook/di_configuration.php +++ b/lib/classes/hook/di_configuration.php @@ -17,6 +17,7 @@ namespace core\hook; use DI\ContainerBuilder; +use DI\Definition; use core\attribute\label; /** @@ -67,13 +68,14 @@ class di_configuration { * * * @param string $id The identifier of the container entry - * @param callable $definition The definition of the container entry + * @param callable|Definition\Definition|Definition\SelfResolvingDefinition|Definition\Helper\DefinitionHelper $definition + * The definition of the container entry * @return self * @example */ public function add_definition( string $id, - callable $definition, + callable|Definition\Definition|Definition\SelfResolvingDefinition|Definition\Helper\DefinitionHelper $definition, ): self { $this->builder->addDefinitions([ $id => $definition, diff --git a/lib/classes/output/requirements/page_requirements_manager.php b/lib/classes/output/requirements/page_requirements_manager.php index a417d4e9ac6..039d68a0ef2 100644 --- a/lib/classes/output/requirements/page_requirements_manager.php +++ b/lib/classes/output/requirements/page_requirements_manager.php @@ -315,6 +315,7 @@ class page_requirements_manager { $this->M_cfg = [ 'wwwroot' => $CFG->wwwroot, + 'apibase' => $this->get_api_base(), 'homeurl' => $page->navigation->action, 'sesskey' => sesskey(), 'sessiontimeout' => $CFG->sessiontimeout, @@ -346,6 +347,29 @@ class page_requirements_manager { return $this->M_cfg; } + /** + * Return the base URL for the API. + * + * If the router has been fully configured on the web server then we can use the shortened route, otherwise the r.php. + * + * @return string + */ + protected function get_api_base(): string { + global $CFG; + + if (!empty($CFG->router_configured)) { + return sprintf( + "%s/api/", + $CFG->wwwroot, + ); + } + + return sprintf( + "%s/r.php/api/", + $CFG->wwwroot, + ); + } + /** * Initialise with the bits of JavaScript that every Moodle page should have. * diff --git a/lib/classes/output/routed_error_handler.php b/lib/classes/output/routed_error_handler.php new file mode 100644 index 00000000000..6a80126e7e7 --- /dev/null +++ b/lib/classes/output/routed_error_handler.php @@ -0,0 +1,50 @@ +. + +namespace core\output; + +use Slim\Interfaces\ErrorRendererInterface; +use Throwable; + +// phpcs:disable moodle.NamingConventions.ValidVariableName.VariableNameLowerCase + +/** + * Class routed_error_handler + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class routed_error_handler implements ErrorRendererInterface { + #[\Override] + public function __invoke(Throwable $exception, bool $displayErrorDetails): string { + // @codeCoverageIgnoreStart + if (defined('ABORT_AFTER_CONFIG') && !defined('ABORT_AFTER_CONFIG_CANCEL')) { + define('ABORT_AFTER_CONFIG_CANCEL', true); + require(__DIR__ . '/../../setup.php'); + } + // @codeCoverageIgnoreEnd + + if ($whoops = get_whoops()) { + $whoops->sendHttpCode($exception->getCode()); + $whoops->handleException($exception); + } else { + default_exception_handler($exception); + } + + return ''; + } +} diff --git a/lib/classes/router.php b/lib/classes/router.php new file mode 100644 index 00000000000..2a0a94d3412 --- /dev/null +++ b/lib/classes/router.php @@ -0,0 +1,259 @@ +. + +namespace core; + +use core\output\routed_error_handler; +use core\router\middleware\cors_middleware; +use core\router\middleware\error_handling_middleware; +use core\router\middleware\moodle_bootstrap_middleware; +use core\router\middleware\moodle_route_attribute_middleware; +use core\router\middleware\uri_normalisation_middleware; +use core\router\middleware\validation_middleware; +use core\router\request_validator_interface; +use core\router\response_handler; +use core\router\response_validator_interface; +use core\router\route_loader_interface; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Slim\App; +use Slim\Interfaces\RouteGroupInterface; + +/** + * Moodle Router. + * + * This class represents the Moodle Router, which handles all aspects of Routing in Moodle. + * + * It should not normally be accessed or used outside of its own unit tests, the route_testcase, and the `r.php` handler. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class router { + /** @var string The base path to use for all requests */ + public readonly string $basepath; + + /** @var App The SlimPHP App */ + protected readonly App $app; + + /** + * Create a new Router. + * + * @param response_handler $responsehandler + * @param route_loader_interface $routeloader + * @param request_validator_interface $requestvalidator + * @param response_validator_interface $responsevalidator + * @param null|string $basepath + */ + public function __construct( + /** @var response_handler */ + protected response_handler $responsehandler, + + /** @var route_loader_interface The router loader to use */ + protected readonly route_loader_interface $routeloader, + + /** @var request_validator_interface */ + protected request_validator_interface $requestvalidator, + + /** @var response_validator_interface */ + protected response_validator_interface $responsevalidator, + + ?string $basepath = null, + ) { + if ($basepath === null) { + $basepath = $this->guess_basepath(); + } + $this->basepath = $basepath; + } + + /** + * Guess the basepath for the Router. + * + * @return string + */ + protected function guess_basepath(): string { + global $CFG; + + // Moodle is not guaranteed to exist at the domain root. + // Strip out the current script. + $scriptroot = parse_url($CFG->wwwroot, PHP_URL_PATH); + $relativeroot = sprintf( + '%s%s', + $scriptroot, + str_replace( + realpath($CFG->dirroot), + '', + realpath($_SERVER['SCRIPT_FILENAME']), + ), + ); + + // The server is not configured to rewrite unknown requests to automatically use the router. + if ($_SERVER && array_key_exists('REQUEST_URI', $_SERVER)) { + if (str_starts_with($_SERVER['REQUEST_URI'], $relativeroot)) { + $scriptroot .= '/r.php'; + } + } + + return $scriptroot; + } + + /** + * Get the configured SlimPHP Application. + * + * @return App + */ + public function get_app(): App { + if (!isset($this->app)) { + $this->create_app($this->basepath); + } + + return $this->app; + } + + /** + * Get the Response Factory for the Router. + * + * @return ResponseFactoryInterface + */ + public function get_response_factory(): ResponseFactoryInterface { + return $this->get_app()->getResponseFactory(); + } + + /** + * Create the configured SlimPHP Application. + * + * @param string $basepath The base path of the Moodle instance + */ + protected function create_app( + string $basepath = '', + ): void { + global $CFG; + + // PHP Does not support autoloading functions. + require_once("{$CFG->libdir}/nikic/fast-route/src/functions.php"); + + // Create an App using the DI Bridge. + $this->app = router\bridge::create(); + + // Add Middleware to the App. + // Note: App Middleware is called before any Group or Route middleware. + $this->add_middleware(); + $this->configure_caching(); + $this->configure_routes(); + + // Configure the basepath for Moodle. + $this->app->setBasePath($basepath); + } + + /** + * Add Middleware to the App. + */ + protected function add_middleware(): void { + // Middleware is added like an onion. + // For a Response, the outer-most middleware is executed first, and the inner-most middleware is executed last. + // For a Request, the inner-most middleware is executed first, and the outer-most middleware is executed last. + + // Add the body parsing middleware from Slim. + // See https://www.slimframework.com/docs/v4/middleware/body-parsing.html for further information. + $this->app->addBodyParsingMiddleware(); + + // Add Middleware to Bootstrap Moodle from a request. + $this->app->add(di::get(moodle_bootstrap_middleware::class)); + + // Add the Moodle route attribute to the request. + // This must be processed after the Routing Middleware has been processed on the request. + $this->app->add(di::get(moodle_route_attribute_middleware::class)); + + // Add the Routing Middleware as one of the outer-most middleware. + // This allows the Route to be accessed before it is handled. + // See https://www.slimframework.com/docs/v4/cookbook/retrieving-current-route.html for further information. + $this->app->addRoutingMiddleware(); + + // Add request normalisation middleware to standardise the URI. + // This must be done before the Routing Middleware to ensure that the route is matched correctly. + $this->app->add(di::get(uri_normalisation_middleware::class)); + + // Add the Error Handling Middleware and configure it to show Moodle Errors for HTML pages. + $errormiddleware = $this->app->addErrorMiddleware(true, true, true); + $errorhandler = $errormiddleware->getDefaultErrorHandler(); + $errorhandler->registerErrorRenderer('text/html', routed_error_handler::class); + } + + /** + * Configure the API routes. + */ + protected function configure_routes(): void { + $routegroups = $this->routeloader->configure_routes($this->app); + foreach ($routegroups as $name => $collection) { + match ($name) { + route_loader_interface::ROUTE_GROUP_API => $this->configure_api_route($collection), + default => null, + }; + } + } + + /** + * Configure the API Route Middleware. + * + * @param RouteGroupInterface $group + */ + protected function configure_api_route(RouteGroupInterface $group): void { + $group + ->add(di::get(error_handling_middleware::class)) + // Add a Middleware to set the CORS headers for all REST Responses. + ->add(di::get(cors_middleware::class)) + ->add(di::get(validation_middleware::class)); + } + + /** + * Configure caching for the routes. + */ + protected function configure_caching(): void { + global $CFG; + + // Note: Slim uses a file cache and is not compatible with MUC. + $this->app->getRouteCollector()->setCacheFile( + sprintf( + "%s/routes.%s.cache", + $CFG->cachedir, + sha1($this->basepath), + ), + ); + } + + /** + * Handle the specified Request. + * + * @param ServerRequestInterface $request + * @return ResponseInterface + */ + public function handle_request( + ServerRequestInterface $request, + ): ResponseInterface { + return $this->get_app()->handle($request); + } + + /** + * Serve the current request using global variables. + * + * @codeCoverageIgnore + */ + public function serve(): void { + $this->get_app()->run(); + } +} diff --git a/lib/classes/router/abstract_route_loader.php b/lib/classes/router/abstract_route_loader.php new file mode 100644 index 00000000000..93e0de3c12d --- /dev/null +++ b/lib/classes/router/abstract_route_loader.php @@ -0,0 +1,209 @@ +. + +namespace core\router; + +use Slim\Interfaces\RouteInterface; + +/** + * A base Route Loader + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class abstract_route_loader { + /** + * Get all routes in the namespace. + * + * @param string $namespace The namespace to get the routes for + * @param callable $componentpathcallback A callback to get the component path for a class + * @return array[] + */ + protected function get_all_routes_in_namespace( + string $namespace, + callable $componentpathcallback, + ): array { + $routes = []; + + // Get all classes in the namespace. + $classes = \core_component::get_component_classes_in_namespace(namespace: $namespace); + foreach (array_keys($classes) as $classname) { + $classinfo = new \ReflectionClass($classname); + $component = \core_component::get_component_from_classname($classname); + $componentpath = $componentpathcallback($component); + + // Add all public methods with a #[route] attribute in this class. + array_push($routes, ...$this->get_all_routes_in_class( + componentpath: $componentpath, + classinfo: $classinfo, + )); + } + + return $routes; + } + + /** + * Get all routes in a class. + * + * @param string $componentpath The path to the component that the class belongs to + * @param \ReflectionClass $classinfo The class to get the routes for + * @return array[] + */ + protected function get_all_routes_in_class( + string $componentpath, + \ReflectionClass $classinfo, + ): array { + // Filter out any methods which are public but do not have any route attached. + return array_filter( + array_map( + fn ($methodinfo) => $this->get_route_data_for_method( + componentpath: $componentpath, + classinfo: $classinfo, + methodinfo: $methodinfo, + ), + $classinfo->getMethods(\ReflectionMethod::IS_PUBLIC), + ) + ); + } + + /** + * Get route data for a single method in a class. + * + * @param string $componentpath The path to the component that the class belongs to + * @param \ReflectionClass $classinfo The class to get the route data for + * @param \ReflectionMethod $methodinfo The method to get the route data for + * @return null|array[] + */ + protected function get_route_data_for_method( + string $componentpath, + \ReflectionClass $classinfo, + \ReflectionMethod $methodinfo, + ): ?array { + $routeattribute = $this->get_route_attribute_for_method( + $classinfo, + $methodinfo, + ); + + if ($routeattribute === null) { + // No route on this method. + return null; + } + + // Build the pattern for this route. + $path = $routeattribute->get_path(); + $pattern = "/{$componentpath}{$path}"; + + // Remove duplicate slashes. + $pattern = preg_replace('@/+@', '/', $pattern); + + // Get the HTTP methods for this route. + $httpmethods = $routeattribute->get_methods(['GET']); + + return [ + 'methods' => $httpmethods, + 'pattern' => $pattern, + 'callable' => [$classinfo->getName(), $methodinfo->getName()], + ]; + } + + /** + * Get the route attribute for the specified method. + * + * Note: If a parent has a route, but the method does not, no route will be returned. + * + * @param \ReflectionClass $classinfo The class to get the route attribute for + * @param \ReflectionMethod $methodinfo The method to get the route attribute for + * @return null|route + */ + protected function get_route_attribute_for_method( + \ReflectionClass $classinfo, + \ReflectionMethod $methodinfo, + ): ?route { + // Fetch the route attribute from the method. + // Each method can only have a single route attribute. + $routeattributes = $methodinfo->getAttributes(route::class); + if (empty($routeattributes)) { + return null; + } + + // Get the instance. + $methodroute = $routeattributes[0]->newInstance(); + + // Set the parent route if the class has one. + $classattributes = $classinfo->getAttributes(route::class); + if ($classattributes) { + // The class has a #route attribute. + $methodroute->set_parent($classattributes[0]->newInstance()); + } + + return $methodroute; + } + + /** + * Normalise the component for use as part of the path. + * + * If the component is a subsystem, the `core_` prefix will be removed. + * If the component is 'core', it will be kept. + * All other components will use their frankenstyle name. + * + * @param string $component + * @return string + */ + protected function normalise_component_path( + string $component, + ): string { + if ($component === 'core') { + return $component; + } + [$type, $subsystem] = \core_component::normalize_component($component); + if ($type === 'core') { + $component = $subsystem; + } + + if ($component === null) { + $component = ''; + } + + return $component; + } + + /** + * Set a route name for the specified callable. + * + * @param RouteInterface $slimroute + * @param string|array|callable $callable + * @return string|null The name of the route if it was set, otherwise null + */ + protected function set_route_name_for_callable( + RouteInterface $slimroute, + string|array|callable $callable, + ): ?string { + if (is_string($callable)) { + $slimroute->setName($callable); + return $callable; + } + + if (is_array($callable)) { + $name = implode('::', $callable); + $slimroute->setName($name); + return $name; + } + + // Unable to set a name. Return null. + return null; + } +} diff --git a/lib/classes/router/apidocs.php b/lib/classes/router/apidocs.php new file mode 100644 index 00000000000..f2e1c7940ca --- /dev/null +++ b/lib/classes/router/apidocs.php @@ -0,0 +1,159 @@ +. + +namespace core\router; + +use core\component; +use core\router\schema\specification; +use Psr\Http\Message\ResponseInterface; +use ReflectionClass; +use Throwable; + +/** + * Moodle Router. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class apidocs { + /** + * Generate the API docs for the API. + * + * @param ResponseInterface $response + * @return ResponseInterface + */ + public function openapi_docs( + ResponseInterface $response, + ): ResponseInterface { + global $PAGE; + $PAGE->set_context(\core\context\system::instance()); + + $api = new specification(); + + $classes = $this->get_openapi_classes_names(); + foreach (array_keys($classes) as $classname) { + $classinfo = new ReflectionClass($classname); + [$component] = explode('\\', $classinfo->getNamespaceName()); + + $classroutes = $classinfo->getAttributes(route::class); + + if ($classroutes) { + foreach ($classroutes as $classroute) { + $parentroute = $classroute->newInstance(); + $this->get_api_docs_for_route( + component: $component, + classinfo: $classinfo, + api: $api, + parentcontexts: [$parentroute], + ); + } + } else { + $this->get_api_docs_for_route( + component: $component, + classinfo: $classinfo, + api: $api, + ); + } + } + + // At the moment only json is supported. This could be extended to support other formats in future. + return $response + ->withHeader('Content-Type', 'application/json') + ->withBody(\GuzzleHttp\Psr7\Utils::streamFor( + json_encode( + $api, + JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES, + ), + )); + } + + /** + * Get the list of OpenAPI Class Names. + * + * @return string[] + */ + protected function get_openapi_classes_names(): array { + global $CFG; + + $classes = []; + foreach (component::get_component_names(true) as $component) { + try { + $classes = array_merge( + $classes, + component::get_component_classes_in_namespace( + component: $component, + namespace: 'route\api', + ), + ); + // @codeCoverageIgnoreStart + } catch (Throwable $error) { + // Some kind of error occurred whilst loading routes in this component. + // When debugging, this is useful to know. + // When not, log to error_log. + if (!$CFG->debugdisplay) { + debugging('Error loading route data: ' . $error->getMessage()); + } else { + default_exception_handler($error); + } + } + // @codeCoverageIgnoreEnd + } + return $classes; + } + + + /** + * Get the API Docs for the specified Route. + * + * @param string $component The component that the route relates to + * @param ReflectionClass $classinfo + * @param specification $api + * @param array $parentcontexts + * @return self + */ + protected function get_api_docs_for_route( + string $component, + ReflectionClass $classinfo, + specification $api, + array $parentcontexts = [], + ): self { + $methods = $classinfo->getMethods(); + foreach ($methods as $method) { + if (!$method->isPublic()) { + continue; + } + + // Get the route attribute for this method. + $routeattribute = util::get_route_instance_for_method( + [$classinfo->getName(), $method->getName()], + ); + + if ($routeattribute === null) { + // This method has no route attribute. Maybe just a helper method. + continue; + } + + // Add this path to the OpenAPI specification. + $api->add_path( + component: $component, + route: $routeattribute, + ); + } + + return $this; + } +} diff --git a/lib/classes/router/bridge.php b/lib/classes/router/bridge.php new file mode 100644 index 00000000000..f5028240add --- /dev/null +++ b/lib/classes/router/bridge.php @@ -0,0 +1,95 @@ +. + +declare(strict_types=1); + +namespace core\router; + +use core\di; +use Invoker\Invoker; +use Invoker\ParameterResolver\AssociativeArrayResolver; +use Invoker\ParameterResolver\Container\TypeHintContainerResolver; +use Invoker\ParameterResolver\DefaultValueResolver; +use Invoker\ParameterResolver\ResolverChain; +use Psr\Container\ContainerInterface; +use Slim\App; +use Slim\Factory\AppFactory; +use Slim\Interfaces\CallableResolverInterface; +use Slim\Interfaces\InvocationStrategyInterface; + +// phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod + +/** + * This factory creates a Slim application correctly configured with PHP-DI. + * + * To use this, replace `Slim\Factory\AppFactory::create()` + * with `DI\Bridge\Slim\Bridge::create()`. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class bridge { + /** + * Create a new Slim application with PHP-DI. + * + * @param ContainerInterface|null $container + * @return App + */ + public static function create(?ContainerInterface $container = null): App { + $container = $container ?: di::get_container(); + + di::set( + CallableResolverInterface::class, + new callable_resolver(new \Invoker\CallableResolver($container)), + ); + + $app = AppFactory::createFromContainer($container); + + di::set(App::class, $app); + + $controllerinvoker = static::create_controller_invoker($container); + $app->getRouteCollector()->setDefaultInvocationStrategy($controllerinvoker); + + return $app; + } + + /** + * Create a controller invoker + * + * @param ContainerInterface $container + * @return InvocationStrategyInterface + */ + protected static function create_controller_invoker(ContainerInterface $container): InvocationStrategyInterface { + $resolvers = [ + // Inject parameters by name first. + new AssociativeArrayResolver(), + + // Then inject services by type-hints for those that weren't resolved. + new TypeHintContainerResolver($container), + + // Then fall back on parameters default values for optional route parameters. + new DefaultValueResolver(), + ]; + + $invoker = new Invoker(new ResolverChain($resolvers), $container); + + return new controller_invoker( + container: $container, + invoker: $invoker, + ); + } +} diff --git a/lib/classes/router/callable_resolver.php b/lib/classes/router/callable_resolver.php new file mode 100644 index 00000000000..5eb79f8f0f1 --- /dev/null +++ b/lib/classes/router/callable_resolver.php @@ -0,0 +1,106 @@ +. + +namespace core\router; + +use Invoker\Exception\NotCallableException; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Slim\Interfaces\AdvancedCallableResolverInterface; + +// phpcs:disable moodle.NamingConventions.ValidVariableName.VariableNameLowerCase +// phpcs:disable moodle.NamingConventions.ValidFunctionName.LowercaseMethod + +/** + * Resolve middleware and route callables using PHP-DI. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class callable_resolver implements AdvancedCallableResolverInterface { + /** + * Create a new instance of the Callable Resolver. + * + * @param \Invoker\CallableResolver $callableresolver The DI Callable Resolver instance + */ + public function __construct( + /** @var \Invoker\CallableResolver The DI Callable Resolver instance */ + protected \Invoker\CallableResolver $callableresolver, + ) { + } + + #[\Override] + public function resolve($toResolve): callable { + return $this->callableresolver->resolve($this->translate_notation($toResolve)); + } + + #[\Override] + public function resolveRoute($toResolve): callable { + return $this->resolve_possible_signature($toResolve, 'handle', RequestHandlerInterface::class); + } + + #[\Override] + public function resolveMiddleware($toResolve): callable { + return $this->resolve_possible_signature($toResolve, 'process', MiddlewareInterface::class); + } + + /** + * Translate Slim string callable notation ('nameOrKey:method') to PHP-DI notation ('nameOrKey::method'). + * + * For a full list of supported callables, see the Slim Docs at + * https://www.slimframework.com/docs/v4/objects/routing.html#container-resolution. + * + * @param mixed $toresolve + * @return mixed + */ + private function translate_notation(mixed $toresolve): mixed { + if (is_string($toresolve) && preg_match(\Slim\CallableResolver::$callablePattern, $toresolve)) { + $toresolve = str_replace(':', '::', $toresolve); + } + + return $toresolve; + } + + /** + * Resolve a possible signature for a callable. + * + * @param mixed $toresolve The callable to resolve + * @param string $method The method to resolve + * @param string $typename The type name to resolve + */ + private function resolve_possible_signature( + mixed $toresolve, + string $method, + string $typename, + ): callable { + if (is_string($toresolve)) { + $toresolve = $this->translate_notation($toresolve); + + try { + $callable = $this->callableresolver->resolve([$toresolve, $method]); + + if (is_array($callable) && $callable[0] instanceof $typename) { + return $callable; + } + } catch (NotCallableException $e) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + // Fall back to looking for a generic callable. + } + } + + return $this->callableresolver->resolve($toresolve); + } +} diff --git a/lib/classes/router/controller_invoker.php b/lib/classes/router/controller_invoker.php new file mode 100644 index 00000000000..a3d480d85ef --- /dev/null +++ b/lib/classes/router/controller_invoker.php @@ -0,0 +1,98 @@ +. + +namespace core\router; + +use Invoker\InvokerInterface; +use Psr\Container\ContainerInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +/** + * Controller Invoker for the Moodle Router. + * + * This class handles invocation of the route callable, and the conversion of the response into an appropriate format. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class controller_invoker implements \Slim\Interfaces\InvocationStrategyInterface { + /** + * Create a new controller invoker. + * + * @param ContainerInterface $container + * @param InvokerInterface $invoker + */ + public function __construct( + /** @var ContainerInterface The DI container */ + protected ContainerInterface $container, + /** @var InvokerInterface The invoker */ + protected InvokerInterface $invoker, + ) { + } + + #[\Override] + public function __invoke( + callable $callable, + ServerRequestInterface $request, + ResponseInterface $response, + array $routeArguments, // phpcs:ignore moodle.NamingConventions.ValidVariableName.VariableNameLowerCase + ): ResponseInterface { + // Inject the request and response by parameter name. + $parameters = [ + 'request' => self::inject_route_arguments( + $request, + $routeArguments, // phpcs:ignore moodle.NamingConventions.ValidVariableName.VariableNameLowerCase + ), + 'response' => $response, + ]; + + // Inject the route arguments by name. + $parameters += $routeArguments; // phpcs:ignore moodle.NamingConventions.ValidVariableName.VariableNameLowerCase + + // Inject the attributes defined on the request. + $parameters += $request->getAttributes(); + + $result = $this->invoker->call($callable, $parameters); + + return $this->container->get(response_handler::class)->standardise_response($result); + } + + /** + * Helper to inject route arguments. + * + * This is based on the ControllerInvoker. + * + * @param ServerRequestInterface $request + * @param array $routeargs + * @return ServerRequestInterface + */ + private static function inject_route_arguments( + ServerRequestInterface $request, + array $routeargs, + ): ServerRequestInterface { + $args = $request; + foreach ($routeargs as $key => $value) { + // Note: This differs to upstream where route args always override attributes. + // We apply mapped parameters via route attributes and must therefore override the route args. + if (!$args->getAttribute($key)) { + $args = $args->withAttribute($key, $value); + } + } + return $args; + } +} diff --git a/lib/classes/router/hook_callbacks.php b/lib/classes/router/hook_callbacks.php new file mode 100644 index 00000000000..44bf7c2ae21 --- /dev/null +++ b/lib/classes/router/hook_callbacks.php @@ -0,0 +1,49 @@ +. + +namespace core\router; + +/** + * Class hook_callbacks + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class hook_callbacks { + /** + * Provide DI Configuration for the Router system. + * + * @param \core\hook\di_configuration $hook + * @codeCoverageIgnore + */ + public static function provide_di_configuration( + \core\hook\di_configuration $hook, + ): void { + $hook->add_definition( + request_validator_interface::class, + \DI\get(request_validator::class), + ); + $hook->add_definition( + response_validator_interface::class, + \DI\get(response_validator::class), + ); + $hook->add_definition( + route_loader_interface::class, + \DI\get(route_loader::class), + ); + } +} diff --git a/lib/classes/router/middleware/cors_middleware.php b/lib/classes/router/middleware/cors_middleware.php new file mode 100644 index 00000000000..eac27b58033 --- /dev/null +++ b/lib/classes/router/middleware/cors_middleware.php @@ -0,0 +1,47 @@ +. + +namespace core\router\middleware; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Slim\Routing\RouteContext; + +/** + * Middleware to add CORS headers to the response. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cors_middleware implements MiddlewareInterface { + #[\Override] + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { + $routecontext = RouteContext::fromRequest($request); + $routingresults = $routecontext->getRoutingResults(); + $methods = $routingresults->getAllowedMethods(); + + $response = $handler->handle($request); + return $response + ->withHeader('Content-Type', 'application/json') + ->withHeader('Content-Disposition', 'inline') + ->withHeader('Access-Control-Allow-Origin', '*') + ->withHeader('Access-Control-Allow-Methods', implode(',', $methods)) + ->withHeader('Access-Control-Allow-Headers', 'Content-Type, api_key, Authorization'); + } +} diff --git a/lib/classes/router/middleware/error_handling_middleware.php b/lib/classes/router/middleware/error_handling_middleware.php new file mode 100644 index 00000000000..ea44ebcb254 --- /dev/null +++ b/lib/classes/router/middleware/error_handling_middleware.php @@ -0,0 +1,61 @@ +. + +namespace core\router\middleware; + +use core\router\response_handler; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; + +/** + * Middleware to handle errors in a route callable. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class error_handling_middleware implements MiddlewareInterface { + /** + * Create a new instance of the error handling middleware. + * + * @param response_handler $responsehandler A handler to standardise a response + */ + public function __construct( + /** @var response_handler A handler to standardise a response */ + protected response_handler $responsehandler, + ) { + } + + #[\Override] + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { + try { + $response = $handler->handle($request); + } catch (\Exception $e) { + // @codeCoverageIgnoreStart + if (defined('ABORT_AFTER_CONFIG') && !defined('ABORT_AFTER_CONFIG_CANCEL')) { + define('ABORT_AFTER_CONFIG_CANCEL', true); + require(__DIR__ . '/../../../setup.php'); + } + // @codeCoverageIgnoreEnd + + $response = $this->responsehandler->get_response_from_exception($request, $e); + } + + return $response; + } +} diff --git a/lib/classes/router/middleware/moodle_bootstrap_middleware.php b/lib/classes/router/middleware/moodle_bootstrap_middleware.php new file mode 100644 index 00000000000..004cfc5f784 --- /dev/null +++ b/lib/classes/router/middleware/moodle_bootstrap_middleware.php @@ -0,0 +1,101 @@ +. + +namespace core\router\middleware; + +use core\router\util; +use core\router\route_loader_interface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; + +/** + * Middleware to set flags and define setup. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class moodle_bootstrap_middleware implements MiddlewareInterface { + #[\Override] + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { + global $PAGE; + + if (str_contains($request->getUri(), route_loader_interface::ROUTE_GROUP_API)) { + // @codeCoverageIgnoreStart + if (!defined('AJAX_SCRIPT')) { + define('AJAX_SCRIPT', true); + } + // @codeCoverageIgnoreEnd + } + + $routeattribute = util::get_route_instance_for_request($request); + if ($routeattribute && !$routeattribute->cookies) { + // @codeCoverageIgnoreStart + // This request should not access Moodle cookies. + if (!defined('NO_MOODLE_COOKIES')) { + define('NO_MOODLE_COOKIES', true); + } + // @codeCoverageIgnoreEnd + } + + if (!$routeattribute || !$routeattribute->abortafterconfig) { + // Do not load the full Moodle stack. This is a lightweight request. + $this->load_full_moodle(); + } + + $PAGE->set_url((string) $request->getUri()); + + return $handler->handle($request); + } + + /** + * Check whether Moodle is fully loaded. + * + * @return bool + * @codeCoverageIgnore + */ + public function is_full_moodle_loaded(): bool { + if (defined('ABORT_AFTER_CONFIG')) { + return defined('ABORT_AFTER_CONFIG_CANCEL'); + } + + return true; + } + + /** + * Load the full Moodle Framework. + * + * @codeCoverageIgnore + */ + protected function load_full_moodle(): void { + // Note: These globals should be defined even if they are not used as they are used in the require. + global $CFG, $DB, $SESSION, $OUTPUT, $PAGE; + + if ($this->is_full_moodle_loaded()) { + return; + } + + // Ok, now we need to start normal moodle script, we need to load all libs and $DB. + if (defined('ABORT_AFTER_CONFIG_CANCEL') && ABORT_AFTER_CONFIG_CANCEL) { + return; + } + define('ABORT_AFTER_CONFIG_CANCEL', true); + + require("{$CFG->dirroot}/lib/setup.php"); + } +} diff --git a/lib/classes/router/middleware/moodle_route_attribute_middleware.php b/lib/classes/router/middleware/moodle_route_attribute_middleware.php new file mode 100644 index 00000000000..8dbd4e20614 --- /dev/null +++ b/lib/classes/router/middleware/moodle_route_attribute_middleware.php @@ -0,0 +1,39 @@ +. + +namespace core\router\middleware; + +use core\router\route; +use core\router\util; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; + +/** + * Middleware to add the Moodle route attribute. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class moodle_route_attribute_middleware implements MiddlewareInterface { + #[\Override] + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { + $routeattribute = util::get_route_instance_for_request($request); + return $handler->handle($request->withAttribute(route::class, $routeattribute)); + } +} diff --git a/lib/classes/router/middleware/uri_normalisation_middleware.php b/lib/classes/router/middleware/uri_normalisation_middleware.php new file mode 100644 index 00000000000..453834a4933 --- /dev/null +++ b/lib/classes/router/middleware/uri_normalisation_middleware.php @@ -0,0 +1,61 @@ +. + +namespace core\router\middleware; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; + +/** + * Middleware to normalise the URI path. + * + * This middleware will: + * - remove duplicate / + * - remove any trailing / + * - ensure that there is a leading / + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class uri_normalisation_middleware implements MiddlewareInterface { + #[\Override] + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { + $uri = $request->getUri(); + $path = $uri->getPath(); + + // Remove duplicate slashes. + $path = preg_replace('@/+@', '/', $path); + + // Remove trailing slashes. + $path = rtrim($path, '/'); + + // Ensure that there is always a path. + // Note: This must be performed after handling removal of duplicate and trailing slashes. + if ($path === '') { + $path = '/'; + } + + if ($uri->getPath() !== $path) { + // Path has changed. Update it. + $request = $request->withUri($uri->withPath($path)); + } + + return $handler->handle($request); + } +} diff --git a/lib/classes/router/middleware/validation_middleware.php b/lib/classes/router/middleware/validation_middleware.php new file mode 100644 index 00000000000..ce1eead6e97 --- /dev/null +++ b/lib/classes/router/middleware/validation_middleware.php @@ -0,0 +1,72 @@ +. + +namespace core\router\middleware; + +use core\router\request_validator_interface; +use core\router\response_handler; +use core\router\response_validator_interface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; + +/** + * Middleware to handle validation of request and response based on the route data. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class validation_middleware implements MiddlewareInterface { + /** + * Create a new instance of the validation middleware. + * + * @param response_handler $responsehandler A handler to standardise a response + * @param request_validator_interface $requestvalidator A request validator + * @param response_validator_interface $responsevalidator A response validator + */ + public function __construct( + /** @var response_handler A handler to standardise a response */ + protected response_handler $responsehandler, + + /** @var request_validator_interface The request validator used to validate incoming data */ + protected request_validator_interface $requestvalidator, + + /** @var response_validator_interface The response validator used to validate incoming data */ + protected response_validator_interface $responsevalidator, + ) { + } + + #[\Override] + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { + try { + $request = $this->requestvalidator->validate_request($request); + } catch (\Exception $e) { + return $this->responsehandler->get_response_from_exception($request, $e); + } + + $response = $handler->handle($request); + + try { + $this->responsevalidator->validate_response($request, $response); + } catch (\Exception $e) { + return $this->responsehandler->get_response_from_exception($request, $e); + } + + return $response; + } +} diff --git a/lib/classes/router/parameters/header_language.php b/lib/classes/router/parameters/header_language.php new file mode 100644 index 00000000000..2941d769c2b --- /dev/null +++ b/lib/classes/router/parameters/header_language.php @@ -0,0 +1,69 @@ +. + +namespace core\router\parameters; + +use core\param; +use core\router\schema\example; +use core\router\schema\referenced_object; + +/** + * A header to accept an optional language for the requested content. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class header_language extends \core\router\schema\parameters\header_object implements referenced_object { + /** + * Create a new path_component parameter. + * + * @param string $name The name of the parameter to use for the component name + * @param mixed ...$extra Additional arguments + */ + public function __construct( + string $name = 'language', + ...$extra, + ) { + global $CFG; + + $extra['name'] = $name; + $extra['type'] = param::LANG; + $extra['description'] = 'The language of the requested response.'; + + // Generally speaking, the default language should be the site default. + // This is a value which is usually stored in DB, so we have a fallback for when the full + // Moodle configuration has not been loaded. + $extra['default'] = $CFG->lang ?? 'en'; + + $extra['examples'] = [ + new example( + name: 'Site default', + value: null, + ), + new example( + name: 'English', + value: 'en', + ), + new example( + name: 'Deutsch (kids)', + value: 'de_kids', + ), + ]; + + parent::__construct(...$extra); + } +} diff --git a/lib/classes/router/parameters/path_component.php b/lib/classes/router/parameters/path_component.php new file mode 100644 index 00000000000..0140e0e2725 --- /dev/null +++ b/lib/classes/router/parameters/path_component.php @@ -0,0 +1,65 @@ +. + +namespace core\router\parameters; + +use core\param; +use core\router\schema\example; +use core\router\schema\referenced_object; + +/** + * A component path parameter. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class path_component extends \core\router\schema\parameters\path_parameter implements referenced_object { + /** + * Create a new path_component parameter. + * + * @param string $name The name of the parameter to use for the component name + * @param mixed ...$extra Additional arguments + */ + public function __construct( + string $name = 'component', + ...$extra, + ) { + $extra['name'] = $name; + $extra['type'] = param::COMPONENT; + $extra['description'] = 'The name of a Moodle component, in frankenstyle format.'; + $extra['examples'] = [ + new example( + name: 'The core subsystem', + value: 'core', + ), + new example( + name: 'The Course subsystem', + value: 'core_course', + ), + new example( + name: 'An activity module', + value: 'mod_assign', + ), + new example( + name: 'An assignment subplugin', + value: 'assignsubmission_file', + ), + ]; + + parent::__construct(...$extra); + } +} diff --git a/lib/classes/router/parameters/path_course.php b/lib/classes/router/parameters/path_course.php new file mode 100644 index 00000000000..8db8f9d1f83 --- /dev/null +++ b/lib/classes/router/parameters/path_course.php @@ -0,0 +1,135 @@ +. + +namespace core\router\parameters; + +use core\exception\not_found_exception; +use core\param; +use core\router\schema\example; +use core\router\schema\parameters\mapped_property_parameter; +use core\router\schema\referenced_object; +use Psr\Http\Message\ServerRequestInterface; + +/** + * A Moodle parameter referenced in the path. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class path_course extends \core\router\schema\parameters\path_parameter implements + mapped_property_parameter, + referenced_object +{ + /** + * Create a new path_course parameter. + * + * @param string $name The name of the parameter to use for the course identifier + * @param mixed ...$extra Additional arguments + */ + public function __construct( + string $name = 'course', + ...$extra, + ) { + $extra['name'] = $name; + $extra['type'] = param::RAW; + $extra['description'] = <<get_record('course', [ + 'id' => $value, + ]); + } else if (str_starts_with($value, 'idnumber:')) { + $data = $DB->get_record('course', [ + 'idnumber' => substr($value, strlen('idnumber:')), + ]); + } else if (str_starts_with($value, 'name:')) { + $data = $DB->get_record('course', [ + 'shortname' => substr($value, strlen('name:')), + ]); + } + + if ($data) { + return $data; + } + + throw new not_found_exception('course', $value); + } + + #[\Override] + public function add_attributes_for_parameter_value( + ServerRequestInterface $request, + string $value, + ): ServerRequestInterface { + $course = $this->get_course_for_value($value); + + return $request + ->withAttribute($this->name, $course) + ->withAttribute("{$this->name}context", \core\context\course::instance($course->id)); + } + + #[\Override] + public function get_schema_from_type(param $type): \stdClass { + $schema = parent::get_schema_from_type($type); + + $schema->pattern = "^("; + $schema->pattern .= implode("|", [ + '\d+', + 'idnumber:.+', + 'name:.+', + ]); + $schema->pattern .= ")$"; + + return $schema; + } +} diff --git a/lib/classes/router/parameters/path_themename.php b/lib/classes/router/parameters/path_themename.php new file mode 100644 index 00000000000..fe4bf79668e --- /dev/null +++ b/lib/classes/router/parameters/path_themename.php @@ -0,0 +1,58 @@ +. + +namespace core\router\parameters; + +use core\param; +use core\router\schema\referenced_object; +use core\router\schema\example; + +/** + * Routing parameter for validation. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class path_themename extends \core\router\schema\parameters\path_parameter implements referenced_object { + /** + * Create a new path_themename parameter. + * + * @param string $name The name of the parameter to use for the theme name + * @param mixed ...$args Additional arguments + */ + public function __construct( + string $name = 'themename', + ...$args, + ) { + $args['name'] = $name; + + $args['type'] = param::ALPHANUMEXT; + $args['description'] = 'The name of a Moodle theme.'; + $args['examples'] = [ + new example( + name: 'The Boost theme', + value: 'boost', + ), + new example( + name: 'The Classic theme', + value: 'classic', + ), + ]; + + parent::__construct(...$args); + } +} diff --git a/lib/classes/router/parameters/path_user.php b/lib/classes/router/parameters/path_user.php new file mode 100644 index 00000000000..3d448063d2e --- /dev/null +++ b/lib/classes/router/parameters/path_user.php @@ -0,0 +1,143 @@ +. + +namespace core\router\parameters; + +use core\exception\not_found_exception; +use core\param; +use core\user; +use core\router\schema\example; +use core\router\schema\parameters\mapped_property_parameter; +use core\router\schema\referenced_object; +use Psr\Http\Message\ServerRequestInterface; + +/** + * A parameter representing a user. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class path_user extends \core\router\schema\parameters\path_parameter implements + mapped_property_parameter, + referenced_object +{ + /** + * Create a new instance of the path_user. + * + * @param string $name The name of the parameter to use for the identifier + * @param mixed ...$extra Additional arguments + */ + public function __construct( + string $name = 'user', + ...$extra, + ) { + $extra['name'] = $name; + $extra['type'] = param::RAW; + $extra['description'] = <<get_user_for_value($value); + + $request = $request->withAttribute($this->name, $user); + + if ($user->id) { + $request = $request->withAttribute("{$this->name}context", \core\context\user::instance($user->id)); + } + + return $request; + } + + #[\Override] + public function get_schema_from_type(param $type): \stdClass { + $schema = parent::get_schema_from_type($type); + + $schema->pattern = "^("; + $schema->pattern .= implode("|", [ + 'current', + '\d+', + 'idnumber:.+', + 'username:.+', + ]); + $schema->pattern .= ")$"; + + return $schema; + } +} diff --git a/lib/classes/router/request_validator.php b/lib/classes/router/request_validator.php new file mode 100644 index 00000000000..d37f2cc150d --- /dev/null +++ b/lib/classes/router/request_validator.php @@ -0,0 +1,197 @@ +. + +namespace core\router; + +use invalid_parameter_exception; +use Psr\Http\Message\ServerRequestInterface; +use Slim\Exception\HttpNotFoundException; +use Slim\Interfaces\RouteInterface; +use Slim\Routing\RouteContext; + +/** + * Routing attribute. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class request_validator implements request_validator_interface { + /** + * Validate the request content. + * + * @param ServerRequestInterface $request + * @return ServerRequestInterface + */ + public function validate_request( + ServerRequestInterface $request, + ): ServerRequestInterface { + $moodleroute = $request->getAttribute(route::class); + if (!$moodleroute) { + return $request; + } + + // Add a Route middleware to validate the path, and parameters. + $slimroute = RouteContext::fromRequest($request)->getRoute(); + + // Validate that the path arguments are valid. + // If they are not, then an Exception should be thrown. + $request = $this->validate_path($request, $moodleroute, $slimroute); + + // Validate query parameters. + $request = $this->validate_query($request, $moodleroute); + + // Validate request headers. + $request = $this->validate_request_header($request, $moodleroute); + + // Validate request body parameters. + // Found in POST, PUT, DELETE, etc. + $request = $this->validate_request_body($request, $moodleroute); + + return $request; + } + + /** + * Validate that the path arguments match those supplied in the route. + * + * @param ServerRequestInterface $request + * @param route $moodleroute + * @param RouteInterface $slimroute The route to validate. + * @return ServerRequestInterface + * @throws \coding_exception + */ + protected function validate_path( + ServerRequestInterface $request, + route $moodleroute, + RouteInterface $slimroute, + ): ServerRequestInterface { + $requiredparams = count(array_filter( + $moodleroute->get_path_parameters(), + fn ($pathtype) => $pathtype->is_required($moodleroute), + )); + if ($requiredparams > count($slimroute->getArguments())) { + throw new \coding_exception(sprintf( + "Route %s has %d arguments, but %d pathtypes were specified.", + $slimroute->getPattern(), + count($slimroute->getArguments()), + count($moodleroute->get_path_parameters()), + )); + } + + foreach ($moodleroute->get_path_parameters() as $pathtype) { + try { + $request = $pathtype->validate($request, $slimroute); + } catch (invalid_parameter_exception $e) { + throw new HttpNotFoundException($request, $e->getMessage()); + } + } + + return $request; + } + + /** + * Validate that the query parameters match those supplied in the route. + * + * @param ServerRequestInterface $request + * @param route $moodleroute + * @return ServerRequestInterface + */ + protected function validate_query( + ServerRequestInterface $request, + route $moodleroute, + ): ServerRequestInterface { + $requestparams = $request->getQueryParams(); + $paramnames = array_map( + fn ($param) => $param->get_name($this), + $moodleroute->get_query_parameters(), + ); + + // Check for any undeclared parameters. + $unknownparams = array_diff( + array_keys($requestparams), + $paramnames, + ); + + // Remove these from the URL. + // They will still be accessible via optional_param. + $request = $request->withQueryParams( + array_diff_key( + $requestparams, + array_flip($unknownparams), + ), + ); + + foreach ($moodleroute->get_query_parameters() as $queryparam) { + $request = $queryparam->validate($request, $request->getQueryParams()); + } + + return $request; + } + + /** + * Validate that the request headers match the schema. + * + * @param ServerRequestInterface $request + * @param route $moodleroute + * @return ServerRequestInterface + */ + protected function validate_request_header( + ServerRequestInterface $request, + route $moodleroute, + ): ServerRequestInterface { + $headerparams = $moodleroute->get_header_parameters(); + + foreach ($headerparams as $headerparam) { + $request = $headerparam->validate($request); + } + + return $request; + } + + /** + * Validate that the request body matches the schema. + * + * @param ServerRequestInterface $request + * @param route $moodleroute + * @return ServerRequestInterface + */ + protected function validate_request_body( + ServerRequestInterface $request, + route $moodleroute, + ): ServerRequestInterface { + if ($moodleroute->get_request_body() === null) { + // Clear the parsed body if there should not be one. + return $request->withParsedBody([]); + } + + $bodyconfig = $moodleroute->get_request_body()->get_body_for_request($request); + $bodyschema = $bodyconfig->get_schema(); + + $parsedbody = $request->getParsedBody(); + if (empty($parsedbody)) { + if ($moodleroute->get_request_body()->is_required()) { + throw new invalid_parameter_exception('Missing request body.'); + } + + // No body to validate. + return $request; + } + + return $request->withParsedBody( + $bodyschema->validate_data($request->getParsedBody()), + ); + } +} diff --git a/lib/classes/router/request_validator_interface.php b/lib/classes/router/request_validator_interface.php new file mode 100644 index 00000000000..8668c50218b --- /dev/null +++ b/lib/classes/router/request_validator_interface.php @@ -0,0 +1,38 @@ +. + +namespace core\router; + +use Psr\Http\Message\ServerRequestInterface; + +/** + * Routing attribute. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface request_validator_interface { + /** + * Validate the request content. + * + * @param ServerRequestInterface $request + * @return ServerRequestInterface + */ + public function validate_request( + ServerRequestInterface $request, + ): ServerRequestInterface; +} diff --git a/lib/classes/router/response/access_denied_response.php b/lib/classes/router/response/access_denied_response.php new file mode 100644 index 00000000000..7199f675ad6 --- /dev/null +++ b/lib/classes/router/response/access_denied_response.php @@ -0,0 +1,36 @@ +. + +namespace core\router\response; + +/** + * A response for when access is denied to a resource. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class access_denied_response extends exception_response { + #[\Override] + public static function get_exception_status_code(): int { + return 403; + } + + #[\Override] + protected static function get_response_description(): string { + return 'Access was denied to the resource.'; + } +} diff --git a/lib/classes/router/response/empty_response.php b/lib/classes/router/response/empty_response.php new file mode 100644 index 00000000000..9e1929d8bce --- /dev/null +++ b/lib/classes/router/response/empty_response.php @@ -0,0 +1,37 @@ +. + +namespace core\router\response; + +use core\router\schema\referenced_object; + +/** + * A standard empty 204 response. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class empty_response extends \core\router\schema\response\response implements + referenced_object +{ + public function __construct(...$args) { + parent::__construct( + statuscode: 204, + description: 'A successful response with no content.', + ); + } +} diff --git a/lib/classes/router/response/exception_response.php b/lib/classes/router/response/exception_response.php new file mode 100644 index 00000000000..8d850097ae0 --- /dev/null +++ b/lib/classes/router/response/exception_response.php @@ -0,0 +1,135 @@ +. + +namespace core\router\response; + +use core\param; +use core\router\schema\objects\scalar_type; +use core\router\schema\objects\schema_object; +use core\router\schema\objects\stacktrace; +use core\router\schema\referenced_object; +use core\router\schema\response\content\payload_response_type; +use core\router\schema\response\payload_response; +use GuzzleHttp\Psr7\Response; +use Psr\Http\Message\ServerRequestInterface; + +/** + * A standard response for user preferences. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class exception_response extends \core\router\schema\response\response implements + referenced_object +{ + /** + * Constructor for a new exception-related response. + */ + public function __construct() { + parent::__construct( + statuscode: static::get_exception_status_code(), + description: static::get_response_description(), + content: new payload_response_type( + schema: static::get_response_schema(), + ), + ); + } + + /** + * Get the response for the exception. + * + * @param ServerRequestInterface $request + * @param \Exception $exception + * @param mixed[] ...$extra + * @return payload_response + */ + public static function get_response( + ServerRequestInterface $request, + \Exception $exception, + ...$extra, + ): payload_response { + return new payload_response( + payload: static::get_payload_data($exception, ...$extra), + request: $request, + response: new Response( + status: static::get_exception_status_code(), + body: $exception->getMessage(), + reason: explode("\n", $exception->getMessage())[0], + ), + ); + } + + /** + * Get the schema for the response. + * + * @return schema_object + */ + protected static function get_response_schema(): schema_object { + return new schema_object( + content: [ + 'message' => new scalar_type( + type: param::ALPHANUMEXT, + description: 'The message of the exception.', + ), + 'errorcode' => new scalar_type( + type: param::ALPHANUMEXT, + description: 'The error code of the exception.', + ), + 'stacktrace' => new stacktrace(), + ], + ); + } + + /** + * The status code that this exception should return. + * + * @return int + */ + protected static function get_exception_status_code(): int { + return 500; + } + + /** + * Get the description of this response. + * + * @return string + */ + abstract protected static function get_response_description(): string; + + /** + * Get the response payload data. + * + * @param \Exception $exception + * @param mixed ...$extra + * @return array + */ + protected static function get_payload_data( + \Exception $exception, + ...$extra, + ): array { + $data = [ + 'message' => $exception->getMessage(), + 'stacktrace' => $exception->getTrace(), + ]; + + if (is_a($exception, \moodle_exception::class)) { + $data['errorcode'] = $exception->errorcode; + } + + return $data; + } +} diff --git a/lib/classes/router/response/invalid_parameter_response.php b/lib/classes/router/response/invalid_parameter_response.php new file mode 100644 index 00000000000..778b6f918f4 --- /dev/null +++ b/lib/classes/router/response/invalid_parameter_response.php @@ -0,0 +1,36 @@ +. + +namespace core\router\response; + +/** + * A standard response for user preferences. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class invalid_parameter_response extends exception_response { + #[\Override] + public static function get_exception_status_code(): int { + return 400; + } + + #[\Override] + protected static function get_response_description(): string { + return 'The parameter provided was invalid in some way.'; + } +} diff --git a/lib/classes/router/response/not_found_response.php b/lib/classes/router/response/not_found_response.php new file mode 100644 index 00000000000..0f5b21c1343 --- /dev/null +++ b/lib/classes/router/response/not_found_response.php @@ -0,0 +1,36 @@ +. + +namespace core\router\response; + +/** + * A standard response for user preferences. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class not_found_response extends exception_response { + #[\Override] + public static function get_exception_status_code(): int { + return 404; + } + + #[\Override] + protected static function get_response_description(): string { + return 'The item was not found.'; + } +} diff --git a/lib/classes/router/response_handler.php b/lib/classes/router/response_handler.php new file mode 100644 index 00000000000..8fe96df96d1 --- /dev/null +++ b/lib/classes/router/response_handler.php @@ -0,0 +1,103 @@ +. + +namespace core\router; + +use core\exception\invalid_parameter_exception; +use core\exception\response_aware_exception; +use core\router; +use core\router\response\exception_response; +use core\router\response\invalid_parameter_response; +use core\router\schema\response\response_type; +use Psr\Container\ContainerInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +/** + * Controller Invoker for the Moodle Router. + * + * This class handles invocation of the route callable, and the conversion of the response into an appropriate format. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class response_handler { + /** + * Create a new response handler. + * + * @param ContainerInterface $container + */ + public function __construct( + /** @var ContainerInterface */ + private readonly ContainerInterface $container, + ) { + } + + /** + * Invoke a route callable. + * + * Note: Much of this is copied from the parent class, but we need to handle the response differently. + * + * @param ResponseInterface|response_type $response The response object. + * @return ResponseInterface The response from the callable. + */ + public function standardise_response( + ResponseInterface | response_type $response, + ): ResponseInterface { + if ($response instanceof ResponseInterface) { + // An object implementing ResponseInterface is returned, so we can just return it. + return $response; + } + + $responsefactory = $this->container->get(router::class)->get_response_factory(); + + // This must be a response\response_type. + return $response->get_response($responsefactory); + } + + /** + * Get the response from an exception. + * + * @param ServerRequestInterface $request + * @param \Exception $exception + * + * @return ResponseInterface + */ + public function get_response_from_exception( + ServerRequestInterface $request, + \Exception $exception, + ): ResponseInterface { + $response = match (true) { + // Newer exceptions may be response-aware, so we can use the response class they specify. + (is_a($exception, response_aware_exception::class)) => $exception->get_response_classname()::get_response( + $request, + $exception, + ), + + // Some legacy expressions are here for the moment. + is_a($exception, invalid_parameter_exception::class) => invalid_parameter_response::get_response( + $request, + $exception, + ), + + // Otherwise use the default. + default => exception_response::get_response($request, $exception), + }; + + return $this->standardise_response($response); + } +} diff --git a/lib/classes/router/response_validator.php b/lib/classes/router/response_validator.php new file mode 100644 index 00000000000..2eb28c0bf23 --- /dev/null +++ b/lib/classes/router/response_validator.php @@ -0,0 +1,50 @@ +. + +namespace core\router; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +/** + * Response Validator. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class response_validator implements response_validator_interface { + #[\Override] + public function validate_response( + ServerRequestInterface $request, + ResponseInterface $response, + ): void { + $moodleroute = $request->getAttribute(route::class); + if (!$moodleroute) { + return; + } + + $expectedresponse = $moodleroute->get_response_with_status_code($response->getStatusCode()); + if (!$expectedresponse) { + // Decide what we should do here. + // Probably just throw heaps of debugging information. + // Maybe Except with debugging enabled. + return; + } else { + $expectedresponse->validate($response); + } + } +} diff --git a/lib/classes/router/response_validator_interface.php b/lib/classes/router/response_validator_interface.php new file mode 100644 index 00000000000..67c2bd1216c --- /dev/null +++ b/lib/classes/router/response_validator_interface.php @@ -0,0 +1,40 @@ +. + +namespace core\router; + +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +/** + * Response Validator interface. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface response_validator_interface { + /** + * Validate the request content. + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + */ + public function validate_response( + ServerRequestInterface $request, + ResponseInterface $response, + ): void; +} diff --git a/lib/classes/router/route.php b/lib/classes/router/route.php new file mode 100644 index 00000000000..b2050f5717a --- /dev/null +++ b/lib/classes/router/route.php @@ -0,0 +1,318 @@ +. + +namespace core\router; + +use core\exception\coding_exception; +use core\router\schema\parameter; +use core\router\schema\response\response; +use core\router\schema\request_body; +use Attribute; + +/** + * Routing attribute. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] +class route { + /** @var string[] The list of HTTP Methods */ + protected null|array $method = null; + + /** + * The parent route, if relevant. + * + * A method-level route may have a class-level route as a parent. The two are combined to provide + * a fully-qualified path. + * + * @var route|null + */ + protected readonly ?route $parentroute; + + /** + * Constructor for a new Moodle route. + * + * @param string $title A title to briefly describe the route (not translated) + * @param string $description A verbose explanation of the operation behavior (not translated) + * @param string $summary A short summary of what the operation does (not translated) + * @param null|string[] $security A list of security mechanisms + * @param null|string $path The path to match + * @param null|array|string $method The method, or methods, supported + * @param parameter[] $pathtypes Validators for the path arguments + * @param parameter[] $queryparams Validators for the path arguments + * @param parameter[] $headerparams Validators for the path arguments + * @param request_body|null $requestbody Validators for the path arguments + * @param response[] $responses A list of possible response types + * @param bool $deprecated Whether this endpoint is deprecated + * @param string[] $tags A list of tags + * @param bool $cookies Whether this request requires cookies + * @param bool $abortafterconfig Whether to abort after configuration + * @param mixed[] ...$extra Any additional arguments not yet supported in this version of Moodle + * @throws coding_exception + */ + public function __construct( + /** @var string A title to briefly describe the route (not translated) */ + public readonly string $title = '', + + /** @var string A verbose explanation of the operation behavior (not translated) */ + public readonly string $description = '', + + /** @var string A short summary of what the operation does (not translated) */ + public readonly string $summary = '', + + /** @var array A list of security mechanisms */ + public readonly ?array $security = null, + + /** + * The path to the route. + * + * This is relative to the parent route, if one exists. + * A route must be set on one, or both, of the class and method level routes. + * + * @var string|null + */ + public ?string $path = null, + + null|array|string $method = null, + + /** @var parameter[] A list of param types for path arguments */ + protected readonly array $pathtypes = [], + + /** @var parameter[] A list of query parameters with matching types */ + protected readonly array $queryparams = [], + + /** @var parameter[] A list of header parameters */ + protected readonly array $headerparams = [], + + /** @var null|request_body A list of parameters found in the body */ + public readonly ?request_body $requestbody = null, + + /** @var response[] A list of possible response types */ + protected readonly array $responses = [], + + /** @var bool Whether this endpoint is deprecated */ + public readonly bool $deprecated = false, + + /** @var string[] A list of tags */ + public readonly array $tags = [], + + /** @var bool Whether this request may use cookies */ + public readonly bool $cookies = true, + + /** @var bool Whether to abort after configuration */ + public readonly bool $abortafterconfig = false, + + // Note. We do not make use of these extras. + // These allow us to add additional arguments in future versions, whilst allowing plugins to use this version. + ...$extra, + ) { + // Normalise the method. + if (is_string($method)) { + $method = [$method]; + } + $this->method = $method; + + // Validate the query parameters. + if (count(array_filter($this->queryparams, fn($pathtype) => !is_a($pathtype, parameter::class)))) { + throw new coding_exception('All query parameters must be an instance of \core\router\parameter.'); + } + if (count(array_filter($this->queryparams, fn($pathtype) => $pathtype->get_in() !== 'query'))) { + throw new coding_exception('All query parameters must be in the query.'); + } + + // Validate the path parameters. + if (count(array_filter($this->pathtypes, fn($pathtype) => !is_a($pathtype, parameter::class)))) { + throw new coding_exception('All path parameters must be an instance of \core\router\parameter.'); + } + if (count(array_filter($this->pathtypes, fn($pathtype) => $pathtype->get_in() !== 'path'))) { + throw new coding_exception('All path properties must be in the path.'); + } + + // Validate the header parameters. + if (count(array_filter($this->headerparams, fn($pathtype) => !is_a($pathtype, parameter::class)))) { + throw new coding_exception('All path parameters must be an instance of \core\router\parameter.'); + } + if (count(array_filter($this->headerparams, fn($pathtype) => $pathtype->get_in() !== 'header'))) { + throw new coding_exception('All header properties must be in the path.'); + } + } + + /** + * Set the parent route, usually a Class-level route. + * + * @param route $parent + * @return self + */ + public function set_parent(route $parent): self { + $this->parentroute = $parent; + return $this; + } + + /** + * Get the fully-qualified path for this route relative to root. + * + * This includes the path of any parent route. + * + * @return string + */ + public function get_path(): string { + $path = $this->path ?? ''; + + if (isset($this->parentroute)) { + $path = $this->parentroute->get_path() . $path; + } + return $path; + } + + /** + * Get the list of HTTP methods associated with this route. + * + * @param null|string[] $default The default methods to use if none are set + * @return null|string[] + */ + public function get_methods(?array $default = null): ?array { + $methods = $this->method; + + if (isset($this->parentroute)) { + $parentmethods = $this->parentroute->get_methods(); + if ($methods) { + $methods = array_unique( + array_merge($parentmethods ?? [], $methods), + ); + } else { + $methods = $parentmethods; + } + } + + // If there are no methods from either this attribute or any parent, use the default. + $methods = $methods ?? $default; + + if ($methods) { + sort($methods); + } + + return $methods; + } + + /** + * Get the list of path parameters, including any from the parent. + * + * @return array + */ + public function get_path_parameters(): array { + $parameters = []; + + if (isset($this->parentroute)) { + $parameters = $this->parentroute->get_path_parameters(); + } + foreach ($this->pathtypes as $parameter) { + $parameters[$parameter->get_name()] = $parameter; + } + + return $parameters; + } + + /** + * Get the list of path parameters, including any from the parent. + * + * @return array + */ + public function get_header_parameters(): array { + $parameters = []; + + if (isset($this->parentroute)) { + $parameters = $this->parentroute->get_header_parameters(); + } + foreach ($this->headerparams as $parameter) { + $parameters[$parameter->get_name()] = $parameter; + } + + return $parameters; + } + + /** + * Get the list of path parameters, including any from the parent. + * + * @return array + */ + public function get_query_parameters(): array { + $parameters = []; + + if (isset($this->parentroute)) { + $parameters = $this->parentroute->get_query_parameters(); + } + foreach ($this->queryparams as $parameter) { + $parameters[$parameter->get_name()] = $parameter; + } + + return $parameters; + } + + /** + * Get the request body for this route. + * + * @return request_body|null + */ + public function get_request_body(): ?request_body { + return $this->requestbody; + } + + /** + * Whether this route expects a request body. + * + * @return bool + */ + public function has_request_body(): bool { + return $this->requestbody !== null; + } + + /** + * Get all responses. + * + * @return response[] + */ + public function get_responses(): array { + return $this->responses; + } + + /** + * Get the response with the specified response code. + * + * @param int $statuscode + * @return response|null + */ + public function get_response_with_status_code(int $statuscode): ?response { + foreach ($this->get_responses() as $response) { + if ($response->get_status_code() === $statuscode) { + return $response; + } + } + + return null; + } + + /** + * Whether this route expects any validatable parameters. + * That is, any parameter in the path, query params, or the request body. + * + * @return bool + */ + public function has_any_validatable_parameter(): bool { + return count($this->get_path_parameters()) || count($this->get_query_parameters()) || $this->has_request_body(); + } +} diff --git a/lib/classes/router/route_controller.php b/lib/classes/router/route_controller.php new file mode 100644 index 00000000000..b0764dbb921 --- /dev/null +++ b/lib/classes/router/route_controller.php @@ -0,0 +1,139 @@ +. + +namespace core\router; + +use moodle_url; +use Psr\Container\ContainerInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +/** + * A controller to make it easier to implement a route. + * + * This controller adds the Container to the constructor which allows controllers to support DI. + * + * This trait is entirely optional. + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +trait route_controller { + /** + * Constructor for Route Controllers. + * + * @param ContainerInterface $container + */ + public function __construct( + /** @var ContainerInterface The DI Container */ + protected ContainerInterface $container, + ) { + } + + /** + * Generate a Page Not Found result. + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @return ResponseInterface + * @throws \Slim\Exception\HttpNotFoundException + */ + protected function page_not_found( + ServerRequestInterface $request, + ResponseInterface $response, + ): ResponseInterface { + throw new \Slim\Exception\HttpNotFoundException($request); + } + + /** + * Redirect to a URL. + * + * @param ResponseInterface $response + * @param string|moodle_url $url + * @return ResponseInterface + */ + protected function redirect( + ResponseInterface $response, + string|moodle_url $url, + ): ResponseInterface { + return $response + ->withStatus(302) + ->withHeader('Location', (string) $url); + } + + /** + * Redirect to the requested callable. + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @param array|callable|string $callable + * @param null|array $pathparams + * @param null|array $queryparams + * @param null|array $excludeparams A list of any parameters to remove the URI during the redirect + * @return ResponseInterface + */ + protected function redirect_to_callable( + ServerRequestInterface $request, + ResponseInterface $response, + array|callable|string $callable, + ?array $pathparams = null, + ?array $queryparams = null, + ?array $excludeparams = null, + ): ResponseInterface { + // Provide defaults for the path and query params if not specified. + if ($pathparams === null) { + $pathparams = $request->getQueryParams(); + } + if ($queryparams === null) { + $queryparams = $request->getQueryParams(); + } + + // Generate a URI from the callable and the parameters. + $url = util::get_path_for_callable( + $callable, + $pathparams ?? [], + $queryparams ?? [], + ); + + // Remove any params. + $url->remove_params($excludeparams); + + return $this->redirect($response, $url); + } + + /** + * Get a parameter from the query params after validation. + * + * @param ServerRequestInterface $request + * @param string $key + * @param mixed $default + * @return mixed + */ + protected function get_param( + ServerRequestInterface $request, + string $key, + mixed $default = null, + ): mixed { + $params = $request->getQueryParams(); + if (array_key_exists($key, $params)) { + return $params[$key]; + } else { + debugging("Missing parameter: $key"); + } + + return $default; + } +} diff --git a/lib/classes/router/route_loader.php b/lib/classes/router/route_loader.php new file mode 100644 index 00000000000..13b5494e21d --- /dev/null +++ b/lib/classes/router/route_loader.php @@ -0,0 +1,83 @@ +. + +namespace core\router; + +use Slim\App; +use Slim\Interfaces\RouteGroupInterface; +use Slim\Routing\RouteCollectorProxy; + +/** + * Route Loader and Discovery agent. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class route_loader extends abstract_route_loader implements route_loader_interface { + #[\Override] + public function configure_routes(App $app): array { + return [ + route_loader_interface::ROUTE_GROUP_API => $this->configure_api_routes($app, route_loader_interface::ROUTE_GROUP_API), + ]; + } + + /** + * Configure all API routes. + * + * @param App $app + * @param string $path + * @return RouteGroupInterface + */ + protected function configure_api_routes(App $app, string $path): RouteGroupInterface { + return $app->group($path, function ( + RouteCollectorProxy $group, + ): void { + // Add all API routes located in the route\api L2\L3 namespace. + foreach ($this->get_all_api_routes() as $apiroute) { + $slimroute = $group->map(...$apiroute); + $this->set_route_name_for_callable($slimroute, $apiroute['callable']); + } + + // Add the OpenAPI docs route. + $callable = [apidocs::class, 'openapi_docs']; + $slimroute = $group->get('/openapi.json', $callable); + $this->set_route_name_for_callable($slimroute, $callable); + }); + } + + /** + * Fetch all API routes. + * + * Note: This method caches results in MUC. + * + * @return array[] + */ + protected function get_all_api_routes(): array { + $cache = \cache::make('core', 'routes'); + + if (!($routes = $cache->get('api_routes'))) { + $routes = $this->get_all_routes_in_namespace( + namespace: 'route\api', + componentpathcallback: $this->normalise_component_path(...), + ); + + $cache->set('api_routes', $routes); + } + + return $routes; + } +} diff --git a/lib/classes/router/route_loader_interface.php b/lib/classes/router/route_loader_interface.php new file mode 100644 index 00000000000..f469710792f --- /dev/null +++ b/lib/classes/router/route_loader_interface.php @@ -0,0 +1,43 @@ +. + +namespace core\router; + +use Slim\App; +use Slim\Interfaces\RouteGroupInterface; +use Slim\Interfaces\RouteInterface; + +/** + * A route loader. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface route_loader_interface { + /** @var string The route path prefix to use for API calls */ + public const ROUTE_GROUP_API = '/api/rest/v2'; + + /** + * Configure all routes for the Application. + * + * This method returns a set of RouteGroupInterface instances for each route prefix. + * + * @param App $app The application to configure routes for + * @return RouteInterface[]|RouteGroupInterface + */ + public function configure_routes(App $app): array; +} diff --git a/lib/classes/router/schema/example.php b/lib/classes/router/schema/example.php new file mode 100644 index 00000000000..9a2bdf0ac76 --- /dev/null +++ b/lib/classes/router/schema/example.php @@ -0,0 +1,109 @@ +. + +namespace core\router\schema; + +use core\exception\coding_exception; + +/** + * A Response Example Object. + * + * https://spec.openapis.org/oas/v3.1.0#example-object + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class example extends openapi_base { + /** + * Create a new example. + * + * @param string $name The name of the example. + * @param string|null $summary A summary of the example. + * @param string|null $description A long description fo the example. CommonMark syntax may be used. + * @param mixed $value Embedded literal example. + * @param string|null $externalvalue A URI that points to the literal example. + * @param mixed ...$extra + * @throws coding_exception if both the value and externalvalue are null + */ + public function __construct( + /** @var string The name of the example */ + protected string $name, + /** @var string|null A summary of the example */ + protected ?string $summary = null, + /** @var string|null A long description fo the example. CommonMark syntax may be used */ + protected ?string $description = null, + /** + * Embedded literal example. + * + * The value field and externalValue field are mutually exclusive. + * To represent examples of media types that cannot naturally represented in JSON or YAML, + * use a string value to contain the example, escaping where necessary. + * + * @var mixed + */ + protected mixed $value = null, + /** + * A URI that points to the literal example. + * + * This provides the capability to reference examples that cannot easily be included in JSON or YAML documents. + * The value field and externalValue field are mutually exclusive. See the rules for resolving Relative References. + * + * @var string|null + */ + protected ?string $externalvalue = null, + ...$extra, + ) { + if (!($value === null || $externalvalue === null)) { + throw new coding_exception('Only one of value or externalvalue can be specified.'); + } + + parent::__construct(...$extra); + } + + /** + * Get the name of this example. + * + * @return string + */ + public function get_name(): string { + return $this->name; + } + + #[\Override] + public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?\stdClass { + $data = (object) []; + + if ($this->summary !== null) { + $data->summary = $this->summary; + } + + if ($this->description !== null) { + $data->description = $this->description; + } + + if ($this->value !== null) { + $data->value = $this->value; + } else if ($this->externalvalue !== null) { + $data->externalValue = $this->externalvalue; + } + + return $data; + } +} diff --git a/lib/classes/router/schema/header_object.php b/lib/classes/router/schema/header_object.php new file mode 100644 index 00000000000..c869359cbac --- /dev/null +++ b/lib/classes/router/schema/header_object.php @@ -0,0 +1,46 @@ +. + +namespace core\router\schema; + +/** + * A Header Object. + * + * https://spec.openapis.org/oas/v3.1.0#headerObject + * + * The Header Object follows the structure of the Parameter Object with the following changes: + * + * - name MUST NOT be specified, it is given in the corresponding headers map. + * - in MUST NOT be specified, it is implicitly in header. + * - All traits that are affected by the location MUST be applicable to a location of header (for example, style). + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class header_object extends parameters\header_object { + #[\Override] + final public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?\stdClass { + $data = parent::get_openapi_description($api); + unset($data->in); + unset($data->name); + + return $data; + } +} diff --git a/lib/classes/router/schema/objects/array_of_strings.php b/lib/classes/router/schema/objects/array_of_strings.php new file mode 100644 index 00000000000..72b9bd9e4f2 --- /dev/null +++ b/lib/classes/router/schema/objects/array_of_strings.php @@ -0,0 +1,78 @@ +. + +namespace core\router\schema\objects; + +use core\param; +use core\router\schema\referenced_object; + +/** + * A schema to describe an array of strings. + * + * TODO: This should really take a param:: type for validation of both name and value. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +final class array_of_strings extends array_of_things implements referenced_object { + /** + * Create a new array_of_strings schema. + * + * @param param $keyparamtype The type of the key parameter + * @param param $valueparamtype The type of the value parameter + * @param mixed ...$extra Additional arguments + */ + public function __construct( + /** @var param The type param type for the key */ + protected param $keyparamtype = param::RAW, + /** @var param The type param type for the value */ + protected param $valueparamtype = param::RAW, + ...$extra, + ) { + $extra['thingtype'] = 'string'; + parent::__construct(...$extra); + } + + #[\Override] + public function validate_data(mixed $data) { + foreach ($data as $name => $value) { + $this->keyparamtype->validate_param( + param: $name, + debuginfo: $this->get_debug_info_for_validation_failure($this->keyparamtype, $name), + ); + $this->valueparamtype->validate_param( + param: $value, + debuginfo: $this->get_debug_info_for_validation_failure($this->valueparamtype, $value), + ); + } + return $data; + } + + /** + * Get the debug info for a validation failure. + * + * @param param $type + * @param string $value + * @return string + */ + protected function get_debug_info_for_validation_failure( + param $type, + string $value, + ): string { + return "The value '{$value}' was not of type {$type->value}."; + } +} diff --git a/lib/classes/router/schema/objects/array_of_things.php b/lib/classes/router/schema/objects/array_of_things.php new file mode 100644 index 00000000000..d8d5dc603fc --- /dev/null +++ b/lib/classes/router/schema/objects/array_of_things.php @@ -0,0 +1,93 @@ +. + +namespace core\router\schema\objects; + +use core\param; +use core\router\schema\specification; + +/** + * A schema to describe an array of things. These could be any type, including other schema definitions. + * + * See https://spec.openapis.org/oas/v3.0.0#model-with-map-dictionary-properties for relevant documentation. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class array_of_things extends type_base { + /** + * An array of things. + * + * @param string|type_base|param|null $thingtype The OpenAPI type, or null if any type is allowed. + * @param mixed[] ...$extra + */ + public function __construct( + /** @var string|type_base|param|null The child item type */ + protected string|type_base|param|null $thingtype = null, + ...$extra, + ) { + parent::__construct(...$extra); + } + + #[\Override] + public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?\stdClass { + return $this->get_schema(); + } + + /** + * Get the OpenAPI schema for this object. + * + * @return \stdClass + */ + public function get_schema(): \stdClass { + return (object) [ + 'type' => 'object', + 'additionalProperties' => $this->get_additional_properties($this->thingtype), + ]; + } + + #[\Override] + public function validate_data(mixed $data) { + if (!is_array($data)) { + throw new \invalid_parameter_exception('Invalid data type, expected array.'); + } + + if ($this->thingtype === null) { + return $data; + } + + if (is_a($this->thingtype, type_base::class)) { + $validator = fn ($value) => $this->thingtype->validate_data($value); + } else { + if (is_string($this->thingtype)) { + $param = param::from($this->thingtype); + } else { + $param = $this->thingtype; + } + + $validator = fn ($value) => $param->validate_param($value); + } + + foreach ($data as $value) { + $validator($value); + } + return $data; + } +} diff --git a/lib/classes/router/schema/objects/scalar_type.php b/lib/classes/router/schema/objects/scalar_type.php new file mode 100644 index 00000000000..0f7214e93ff --- /dev/null +++ b/lib/classes/router/schema/objects/scalar_type.php @@ -0,0 +1,65 @@ +. + +namespace core\router\schema\objects; + +use core\param; +use core\router\schema\specification; + +/** + * A scalar type. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class scalar_type extends type_base { + /** + * Instantiate a new Scalar Type + * + * @param param $type The Moodle PARAM_ type + * @param bool $required Whether the value is required or not + * @param mixed $default The value used if none was supplied (request bodies only) + * @param mixed[] ...$extra + */ + public function __construct( + /** @var param The type of the parameter content */ + protected param $type, + /** @var bool Whether the value is required or not */ + protected bool $required = false, + /** @var mixed $default The value used if none was supplied (request bodies only) */ + protected mixed $default = null, + ...$extra, + ) { + parent::__construct(...$extra); + } + + #[\Override] + public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?\stdClass { + return $this->get_schema_from_type($this->type); + } + + #[\Override] + public function validate_data(mixed $data) { + return $this->type->validate_param( + param: $data, + allownull: $this->required ? NULL_NOT_ALLOWED : NULL_ALLOWED, + ); + } +} diff --git a/lib/classes/router/schema/objects/schema_object.php b/lib/classes/router/schema/objects/schema_object.php new file mode 100644 index 00000000000..5c586bcc5a5 --- /dev/null +++ b/lib/classes/router/schema/objects/schema_object.php @@ -0,0 +1,113 @@ +. + +namespace core\router\schema\objects; + +use core\exception\coding_exception; +use core\router\schema\specification; + +/** + * A schema to describe an array of things. These could be any type, including other schema definitions. + * + * See https://spec.openapis.org/oas/v3.1.0#model-with-map-dictionary-properties for relevant documentation. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class schema_object extends type_base { + /** + * An array of things. + * + * @param type_base[] $content The child content + * @param mixed[] ...$extra + * @throws coding_exception + */ + public function __construct( + /** @var type_base[] The child content */ + protected array $content, + + ...$extra, + ) { + foreach ($content as $child) { + if (!$child instanceof type_base) { + throw new coding_exception('Content must be an array of type_base objects'); + } + } + + parent::__construct(...$extra); + } + + /** + * Whether this schema object has this key as a type. + * + * @param string $key + * @return bool + */ + public function has(string $key): bool { + return isset($this->content[$key]); + } + + /** + * Get the schema object for this key. + * + * @param string $key + * @return type_base + */ + public function get(string $key): type_base { + return $this->content[$key]; + } + + #[\Override] + public function validate_data(mixed $data) { + foreach ($data as $key => $values) { + if (!$this->has($key)) { + // We do not know about this one. + // Remove it from the params array. + $data = array_diff_key( + $data, + [$key => $values], + ); + continue; + } + + // Validate this parameter. + $child = $this->content[$key]; + $data[$key] = $child->validate_data($values); + } + + return $data; + } + + #[\Override] + public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?\stdClass { + $data = (object) [ + 'type' => 'object', + 'properties' => (object) [], + ]; + + foreach ($this->content as $name => $content) { + $data->properties->{$name} = $content->get_openapi_schema( + $api, + ); + } + + return $data; + } +} diff --git a/lib/classes/router/schema/objects/stacktrace.php b/lib/classes/router/schema/objects/stacktrace.php new file mode 100644 index 00000000000..7a14fd3cb97 --- /dev/null +++ b/lib/classes/router/schema/objects/stacktrace.php @@ -0,0 +1,128 @@ +. + +namespace core\router\schema\objects; + +use core\param; +use core\router\schema\example; +use core\router\schema\referenced_object; +use core\router\schema\specification; + +/** + * A standard response for user preferences. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class stacktrace extends type_base implements referenced_object { + /** @var array The stacks in the trace */ + protected array $content; + + /** + * Constructor for a new stacktrace object. + */ + public function __construct() { + $this->content = [ + 'file' => new scalar_type(param::PATH), + 'line' => new scalar_type(param::INT), + 'function' => new scalar_type(param::RAW), + 'args' => new array_of_things(), + 'class' => new scalar_type(param::RAW), + 'type' => new scalar_type(param::RAW), + ]; + + $pathroot = '/Users/example/Sites/moodle'; + parent::__construct( + examples: [ + new example( + name: 'A sample stacktrace', + value: [ + [ + "file" => "{$pathroot}/lib/classes/router/schema/objects/array_of_strings.php", + "line" => 48, + "function" => "validate_param", + "args" => [ + "string", + "int", + false, + "The value 'string' was not of type string.", + ], + ], + [ + "file" => "{$pathroot}/lib/classes/router/schema/objects/schema_object.php", + "line" => 85, + "function" => "validate_data", + "class" => "core\\router\\schema\\objects\\array_of_strings", + "type" => "->", + "args" => [ + [ + "additionalProp1" => "string", + "additionalProp2" => "string", + "additionalProp3" => "string", + ], + ], + ], + [ + "file" => "{$pathroot}/lib/classes/router/route.php", + "line" => 264, + "function" => "validate_data", + "class" => "core\\router\\schema\\objects\\schema_object", + "type" => "->", + "args" => [ + [ + "preferences" => [ + "additionalProp1" => "string", + "additionalProp2" => "string", + "additionalProp3" => "string", + ], + ], + ], + ], + ], + ), + ], + ); + } + + #[\Override] + public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?\stdClass { + $additionalproperties = new \stdClass(); + + foreach ($this->content as $name => $content) { + $additionalproperties->{$name} = $content->get_openapi_description($api, $path); + } + + $data = parent::get_openapi_description($api, $path); + $data->type = 'array'; + $data->items = (object) [ + 'type' => 'object', + 'properties' => $additionalproperties, + ]; + + return $data; + } + + #[\Override] + public function validate_data($data) { + // Do not validate the data at all. + // Stacktraces tend to be used with exceptions and we want whatever was passed through to come out. + return $data; + } +} diff --git a/lib/classes/router/schema/objects/type_base.php b/lib/classes/router/schema/objects/type_base.php new file mode 100644 index 00000000000..537a912ef3c --- /dev/null +++ b/lib/classes/router/schema/objects/type_base.php @@ -0,0 +1,95 @@ +. + +namespace core\router\schema\objects; + +use core\router\schema\openapi_base; +use core\router\schema\specification; + +/** + * Part of the OpenAPI Schema. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class type_base extends openapi_base { + /** + * Note: We do not implement the $example, because it has been deprecated in OpenApi 3.0. + * + * @param array $examples + * @param mixed[] ...$extra + */ + public function __construct( + /** @var array Any examples that may be present for the type */ + protected array $examples = [], + ...$extra, + ) { + parent::__construct(...$extra); + } + + #[\Override] + public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?\stdClass { + $data = (object) []; + + if (count($this->examples)) { + $data->examples = []; + foreach ($this->examples as $example) { + $data->examples[] = $example->get_openapi_schema( + api: $api, + ); + } + } + + return $data; + } + + /** + * Get the additional OpenAPI properties if relevant. + * + * @param string|null|type_base $type + * @return bool|array + */ + protected function get_additional_properties(string|null|type_base $type): bool|array { + // The additionalProperties are described here: + // https://spec.openapis.org/oas/v3.1.0#schema-object-examples. + if ($type === null) { + return true; + } + + if (is_a($type, self::class)) { + // This type is a reference to another schema object. + return [ + '$ref' => $type->get_reference(), + ]; + } + + // TODO MDL-82243: Validate against supported OpenAPI types. + return [ + 'type' => $type, + ]; + } + + /** + * Validate the data against the type. + * + * @param mixed $data + */ + abstract public function validate_data(mixed $data); +} diff --git a/lib/classes/router/schema/openapi_base.php b/lib/classes/router/schema/openapi_base.php new file mode 100644 index 00000000000..c464acfa7e5 --- /dev/null +++ b/lib/classes/router/schema/openapi_base.php @@ -0,0 +1,239 @@ +. + +namespace core\router\schema; + +use coding_exception; +use core\param; +use core\router\schema\objects\type_base; +use core\router\schema\response\response; +use stdClass; + +/** + * A generic part of the OpenAPI Schema object. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class openapi_base { + /** + * Base constructor which does nothing. + * + * We keep an $extra parameter here for future-proofing. + * This allows named parameters to be used and allows contrib plugins to + * make use of parameters in newer versions even if they don't exist in older versions. + * + * @param mixed ...$extra Extra arguments to allow for future versions of Moodle to add options without breaking plugins + */ + public function __construct( + mixed ...$extra, + ) { + } + + /** + * Get the $ref for this class. + * + * @param bool $qualify Whether to qualify the reference with the #/components/ part. + * @return string + */ + public function get_reference( + bool $qualify = true, + ): string { + return static::get_reference_for_class( + classname: get_class($this), + qualify: $qualify, + ); + } + + /** + * Get the OpenAPI data to include in the OpenAPI specification. + * + * @param specification $api + * @param null|string $path + * @return null|stdClass + * @throws coding_exception + */ + final public function get_openapi_schema( + specification $api, + ?string $path = null, + ): ?stdClass { + if (is_a($this, referenced_object::class)) { + // This class is a referenced object, so we need to add it to the specification. + if (!$api->is_reference_defined($this->get_reference())) { + $api->add_component($this); + } + + return (object) [ + '$ref' => $this->get_reference(), + ]; + } + + return $this->get_openapi_description( + api: $api, + path: $path, + ); + } + + /** + * Get the OpenAPI data to include in the OpenAPI specification. + * + * @param specification $api + * @param null|string $path + * @return null|stdClass + */ + abstract public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?stdClass; + + /** + * Get the $ref a class name. + * + * https://swagger.io/docs/specification/using-ref/ + * + * @param string $classname The class to get a reference for + * @param bool $qualify Whether to qualify the reference with the #/components/ part + * @return string The reference + * @throws coding_exception + */ + public static function get_reference_for_class( + string $classname, + bool $qualify = true, + ): string { + $reference = static::escape_reference($classname); + if (!$qualify) { + return $reference; + } + + // Note: The following list must be kept in-sync with specification::add_component(). + return match (true) { + is_a($classname, header_object::class, true) => static::get_reference_for_header($reference), + is_a($classname, parameter::class, true) => static::get_reference_for_parameter($reference), + is_a($classname, response::class, true) => static::get_reference_for_response($reference), + is_a($classname, example::class, true) => static::get_reference_for_example($reference), + is_a($classname, request_body::class, true) => static::get_reference_for_request_body($reference), + is_a($classname, type_base::class, true) => static::get_reference_for_schema($reference), + default => throw new coding_exception("Class {$classname} is not a schema."), + }; + } + + + /** + * Get the qualified $ref for a parameter. + * + * @param string $reference + * @return string + */ + public static function get_reference_for_header(string $reference): string { + return "#/components/headers/{$reference}"; + } + + /** + * Get the qualified $ref for a parameter. + * + * @param string $reference + * @return string + */ + public static function get_reference_for_parameter(string $reference): string { + return "#/components/parameters/{$reference}"; + } + + /** + * Get the qualified $ref for a response. + * + * @param string $reference + * @return string + */ + public static function get_reference_for_response(string $reference): string { + return "#/components/responses/{$reference}"; + } + + /** + * Get the qualified $ref for an example. + * + * @param string $reference + * @return string + */ + public static function get_reference_for_example(string $reference): string { + return "#/components/examples/{$reference}"; + } + + /** + * Get the qualified $ref for a request body. + * + * @param string $reference + * @return string + */ + public static function get_reference_for_request_body(string $reference): string { + return "#/components/requestBodies/{$reference}"; + } + + /** + * Get the qualified $ref for a schema. + * + * @param string $reference + * @return string + */ + public static function get_reference_for_schema(string $reference): string { + return "#/components/schemas/{$reference}"; + } + + /** + * Escape a reference following rules defined at https://swagger.io/docs/specification/using-ref/. + * + * @param string $reference + * @return string + */ + public static function escape_reference(string $reference): string { + // Note https://swagger.io/docs/specification/using-ref/ defines the following replacements: + // ~ => ~0 + // / => ~1 + // We also add some other replacements: + // \ => -- + // These must be used in all reference names. + // See also https://spec.openapis.org/oas/v3.1.0#components-object + // And the following regular expression: + // ^[a-zA-Z0-9\.\-_]+$. + return str_replace( + ['~', '/', '\\'], + ['~0', '~1', '--'], + $reference, + ); + } + + /** + * Get the schema for a given type. + * + * @param param $type + * @return stdClass + */ + public function get_schema_from_type(param $type): stdClass { + $data = new stdClass(); + + $data->type = match ($type) { + // OpenAPI uses an extension of the JSON Schema to define both integers and numbers (float). + param::INT => 'integer', + param::FLOAT => 'number', + param::BOOL => 'boolean', + + // All other types are string types and most have a pattern. + default => 'string', + }; + + return $data; + } +} diff --git a/lib/classes/router/schema/parameter.php b/lib/classes/router/schema/parameter.php new file mode 100644 index 00000000000..7e0aa0303ed --- /dev/null +++ b/lib/classes/router/schema/parameter.php @@ -0,0 +1,176 @@ +. + +namespace core\router\schema; + +use core\exception\coding_exception; +use core\param; +use core\router\route; +use core\router\schema\objects\type_base; +use stdClass; + +/** + * OpenAPI parameter. + * + * https://spec.openapis.org/oas/v3.1.0#parameter-object + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class parameter extends openapi_base { + /** @var string A query parameter */ + public const IN_QUERY = 'query'; + + /** @var string A header parameter */ + public const IN_HEADER = 'header'; + + /** @var string A URI path parameter */ + public const IN_PATH = 'path'; + + /** @var string A cookie parameter */ + public const IN_COOKIE = 'cookie'; + + /** + * Constructor for a Parameter Object. + * + * @param string $name The name of the parameter. Parameter names are case sensitive. + * - If in is "path", the name field MUST correspond to a template expression occurring within the + * path field in the Paths Object. + * See Path Templating for further information. + * - If in is "header" and the name field is "Accept", "Content-Type" or "Authorization", + * the parameter definition SHALL be ignored. + * - For all other cases, the name corresponds to the parameter name used by the in property. + * @param string $in The location of the parameter. Possible values are "query", "header", "path" or "cookie". + * @param null|string $description + * @param null|bool $required + * @param null|bool $deprecated Specifies that a parameter is deprecated and SHOULD be transitioned out of usage. + * @param null|param $type A Moodle parameter type, which can be used instead of a schema. + * @param mixed $default The default value + * @param null|type_base $schema + * @param null|example $example + * @param example[] $examples + * @param mixed[] ...$extra + * @throws coding_exception + */ + public function __construct( + /** @var string The name of the parameter. Parameter names are case sensitive */ + protected string $name, + /** @var string The location of the parameter */ + protected string $in, + /** @var string|null A description of the parameter */ + protected ?string $description = null, + /** @var bool|null Whether the parameter is required */ + protected ?bool $required = null, + /** @var bool|null Whether the parameter is deprecated */ + protected ?bool $deprecated = false, + /** @var param|null A Moodle parameter type */ + protected ?param $type = null, + /** @var mixed|null The default value of the parameter */ + protected mixed $default = null, + /** @var type_base|null The schema */ + protected ?type_base $schema = null, + /** @var example|null An example */ + protected ?example $example = null, + /** @var example[] An array of examples */ + protected array $examples = [], + ...$extra, + ) { + if ($example) { + if (count($examples)) { + throw new coding_exception('Only one of example or examples can be specified.'); + } + $this->examples[$example->get_name()] = $example; + } + + if ($required === true && $default !== null) { + throw new coding_exception('A parameter cannot be required and have a default value.'); + } + + parent::__construct(...$extra); + } + + #[\Override] + public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?stdClass { + $data = (object) [ + // The `name`, and `in` values are required. + 'name' => $this->name, + 'in' => $this->in, + ]; + + if ($this->description !== null) { + $data->description = $this->description; + } + + // Allow another schema to be passed. + if ($this->schema !== null) { + $data->schema = $this->schema->get_openapi_schema($api, $path); + } else { + $data->schema = $this->get_schema_from_type($this->type); + } + + if (count($this->examples) > 0) { + $data->examples = []; + foreach ($this->examples as $example) { + $data->examples[$example->get_name()] = $example->get_openapi_schema( + api: $api, + ); + } + } + + return $data; + } + + /** + * Get the OpenAPI 'in' property. + * + * @return string + */ + public function get_in(): string { + return $this->in; + } + + /** + * Fetch the underlying param. + * + * @return param + */ + public function get_type(): param { + return $this->type; + } + + /** + * Whether this property is required. + * + * @param route $route + * @return bool + */ + public function is_required(route $route): bool { + return $this->required ?? false; + } + + /** + * Get the name of the parameter. + * + * @return string + */ + public function get_name(): string { + return $this->name; + } +} diff --git a/lib/classes/router/schema/parameters/header_object.php b/lib/classes/router/schema/parameters/header_object.php new file mode 100644 index 00000000000..e5028528a1c --- /dev/null +++ b/lib/classes/router/schema/parameters/header_object.php @@ -0,0 +1,106 @@ +. + +namespace core\router\schema\parameters; + +use core\exception\invalid_parameter_exception; +use core\param; +use core\router\schema\parameter; +use Psr\Http\Message\ServerRequestInterface; + +/** + * A Header Object. + * + * https://spec.openapis.org/oas/v3.1.0#headerObject + * + * The Header Object follows the structure of the Parameter Object with the following changes: + * + * - name MUST NOT be specified, it is given in the corresponding headers map. + * - in MUST NOT be specified, it is implicitly in header. + * - All traits that are affected by the location MUST be applicable to a location of header (for example, style). + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class header_object extends parameter { + /** + * Create a new header object. + * + * @param bool $multiple Whether this parameter can be specified multiple times. + * @param mixed ...$extra Header arguments to pass to the parameter constructor. + */ + public function __construct( + /** @var bool Whether multiple instances of this header are supported */ + protected bool $multiple = false, + ...$extra, + ) { + $extra['in'] = parameter::IN_HEADER; + parent::__construct(...$extra); + } + + /** + * Validate the parameter. + * + * @param ServerRequestInterface $request The request to validate. + * @return ServerRequestInterface The request with the validated parameter. + * @throws invalid_parameter_exception If the parameter is invalid. + */ + public function validate( + ServerRequestInterface $request, + ): ServerRequestInterface { + if ($request->hasHeader($this->name)) { + $headervalues = $request->getHeader($this->name); + + if (!$this->multiple && count($headervalues) > 1) { + throw new invalid_parameter_exception( + "The parameter {$this->name} was specified multiple times, but it can only be specified once", + ); + } + + // This parameter was specified. Validate it. + if ($this->get_type() === param::BOOL) { + $headervalues = array_map(fn ($headervalue) => match ($headervalue) { + 'true' => 1, + 'false' => 0, + default => throw new \ValueError('Invalid boolean value.'), + }, $headervalues); + return $request->withHeader($this->name, $headervalues); + } + + foreach ($headervalues as $headervalue) { + $this->type->validate_param($headervalue); + } + + return $request; + } + + if ($this->required) { + throw new invalid_parameter_exception( + "A required parameter {$this->name} was not provided and must be specified", + ); + } + + if ($this->default !== null) { + // This parameter is optional. Fill the default. + return $request->withHeader($this->name, $this->default); + } + + // This parameter is optional and there is no default. + // Fill a null value. + return $request->withHeader($this->name, null); + } +} diff --git a/lib/classes/router/schema/parameters/mapped_property_parameter.php b/lib/classes/router/schema/parameters/mapped_property_parameter.php new file mode 100644 index 00000000000..c9a2943d776 --- /dev/null +++ b/lib/classes/router/schema/parameters/mapped_property_parameter.php @@ -0,0 +1,40 @@ +. + +namespace core\router\schema\parameters; + +use Psr\Http\Message\ServerRequestInterface; + +/** + * An OpenAPI Parameter which supports validation. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface mapped_property_parameter { + /** + * Add attribute for the current parameter to the request. + * + * @param ServerRequestInterface $request + * @param string $value + * @return ServerRequestInterface + */ + public function add_attributes_for_parameter_value( + ServerRequestInterface $request, + string $value, + ): ServerRequestInterface; +} diff --git a/lib/classes/router/schema/parameters/path_parameter.php b/lib/classes/router/schema/parameters/path_parameter.php new file mode 100644 index 00000000000..5cf7e2c9039 --- /dev/null +++ b/lib/classes/router/schema/parameters/path_parameter.php @@ -0,0 +1,113 @@ +. + +namespace core\router\schema\parameters; + +use core\router\route; +use core\router\schema\parameter; +use core\router\schema\specification; +use Psr\Http\Message\ServerRequestInterface; +use Slim\Routing\Route as RoutingRoute; +use stdClass; + +/** + * Routing parameter for validation. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class path_parameter extends parameter { + /** + * Create an instance of a new path parameter. + * + * @param mixed ...$extra Additional parameters for the parameter + */ + public function __construct( + ...$extra, + ) { + $extra['in'] = parameter::IN_PATH; + parent::__construct( + ...$extra, + ); + } + + /** + * Validate the path parameter. + * + * @param ServerRequestInterface $request + * @param RoutingRoute $route + * @return ServerRequestInterface The modified request with parameters updated + */ + public function validate( + ServerRequestInterface $request, + RoutingRoute $route, + ): ServerRequestInterface { + $args = $route->getArguments(); + + $value = $route->getArgument($this->name); + + $this->type->validate_param( + param: $value, + allownull: NULL_ALLOWED, + ); + + if (is_a($this, mapped_property_parameter::class)) { + // Unfortunately args must be a string, but mapped properties can be an object. + // Remove the argument, and instead provide the mapped property as an attribute. + unset($args[$this->name]); + $route->setArguments($args); + $request = $this->add_attributes_for_parameter_value($request, $value); + } + + return $request; + } + + #[\Override] + final public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?stdClass { + if ($path && !str_contains($path, "{{$this->name}}")) { + // In OpenAPI, Path parameters can never be optional. + return null; + } + $data = parent::get_openapi_description( + api: $api, + path: $path, + ); + $data->required = true; + + return $data; + } + + /** + * Check whether this parameter is required for the given route. + * + * @param route $route + * @return bool + */ + public function is_required(route $route): bool { + $path = $route->get_path(); + + // Find the position of the parameter in the path. + $paramposition = strpos($path, '{' . $this->name . '}'); + + // If _any_ part of the path before the parameter contains a '[' character, then this _must_ be optional. + // A required parameter cannot follow an optional parameter. + return !str_contains(substr($path, 0, $paramposition), '['); + } +} diff --git a/lib/classes/router/schema/parameters/query_parameter.php b/lib/classes/router/schema/parameters/query_parameter.php new file mode 100644 index 00000000000..e3f7c402206 --- /dev/null +++ b/lib/classes/router/schema/parameters/query_parameter.php @@ -0,0 +1,147 @@ +. + +namespace core\router\schema\parameters; + +use core\exception\coding_exception; +use core\param; +use core\router\schema\parameter; +use core\router\schema\specification; +use Psr\Http\Message\ServerRequestInterface; + +/** + * Routing query parameter for validation. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class query_parameter extends parameter { + /** + * Query parameter constructor to override the location of the parameter. + * + * @param bool|null $allowreserved Determines whether the parameter value SHOULD allow reserved characters. + * @param array ...$extra + */ + public function __construct( + /** + * Determines whether the parameter value SHOULD allow reserved characters. + * + * As defined by [RFC3986], these characters are :/?#[]@!$&'()*+,;= to be included without percent-encoding. + * This property only applies to parameters with an in value of query. The default value is false. + * + * @var bool|null + */ + protected ?bool $allowreserved = null, + ...$extra, + ) { + $extra['in'] = parameter::IN_QUERY; + parent::__construct(...$extra); + } + + /** + * Validate query parameters. + * + * @param ServerRequestInterface $request + * @param array $params + * @return ServerRequestInterface + * @throws coding_exception + * @throws \ValueError + */ + public function validate( + ServerRequestInterface $request, + array $params, + ): ServerRequestInterface { + if (array_key_exists($this->name, $params)) { + // This parameter was specified. Validate it. + if ($this->get_type() === param::BOOL) { + match ($params[$this->name]) { + 'true' => $params[$this->name] = 1, + 'false' => $params[$this->name] = 0, + default => throw new \ValueError('Invalid boolean value.'), + }; + } + $this->type->validate_param($params[$this->name]); + + return $this->update_request_params( + $request, + array_merge( + $params, + [$this->name => $params[$this->name]], + ), + ); + } + + if ($this->required) { + throw new coding_exception( + "A required parameter {$this->name} was not provided and must be specified", + ); + } + + if ($this->default !== null) { + // This parameter is optional. Fill the default. + return $this->update_request_params( + $request, + array_merge( + $params, + [$this->name => $this->default], + ), + ); + } + + // This parameter is optional and there is no default. + // Fill a null value. + return $this->update_request_params( + $request, + array_merge( + $params, + [$this->name => null], + ), + ); + } + + /** + * Update the request parameters. + * + * @param ServerRequestInterface $request + * @param array $params + * @return ServerRequestInterface + */ + protected function update_request_params( + ServerRequestInterface $request, + array $params, + ): ServerRequestInterface { + return $request->withQueryParams($params); + } + + #[\Override] + final public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?\stdClass { + $data = parent::get_openapi_description($api, $path); + + if ($this->allowreserved) { + // Determines whether the parameter value SHOULD allow reserved characters, as defined by [RFC3986] + // :/?#[]@!$&'()*+,;= + // to be included without percent-encoding. + // This property only applies to parameters with an in value of query. The default value is false. + $data->allowReserved = $this->allowreserved; + } + + return $data; + } +} diff --git a/lib/classes/router/schema/referenced_object.php b/lib/classes/router/schema/referenced_object.php new file mode 100644 index 00000000000..92c9d9011a6 --- /dev/null +++ b/lib/classes/router/schema/referenced_object.php @@ -0,0 +1,29 @@ +. + +namespace core\router\schema; + +/** + * An OpenAPI Schema Object which is referenced rather than inserted directly. + * + * This mean that it is inserted into the /components/ part of the schema rather than duplicated. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface referenced_object { +} diff --git a/lib/classes/router/schema/request_body.php b/lib/classes/router/schema/request_body.php new file mode 100644 index 00000000000..d800b087a78 --- /dev/null +++ b/lib/classes/router/schema/request_body.php @@ -0,0 +1,141 @@ +. + +namespace core\router\schema; + +use core\router\schema\response\content\media_type; +use core\router\schema\response\content\payload_response_type; +use Psr\Http\Message\ServerRequestInterface; + +/** + * Routing request body for validation. + * + * https://spec.openapis.org/oas/v3.1.0#request-body-object + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class request_body extends openapi_base { + /** + * Create a new request body. + * + * @param string $description A brief description of the request body. + * @param payload_response_type|payload_response_type[] $content The content of the request body. + * @param bool $required Whether the request body is required + * @param mixed ...$args Extra args for future compatibility. + * @throws \coding_exception if the content is not an instance of media_type. + */ + public function __construct( + /** + * A brief description of the request body. + * + * This could contain examples of use. CommonMark syntax MAY be used for rich text representation. + * @var string + */ + protected string $description = '', + + /** + * The content of the request body. + * + * @var payload_response_type|media_type[] + */ + protected array|payload_response_type $content = [], + + /** @var bool Whether the request body is required */ + protected bool $required = false, + ...$args, + ) { + if (!empty($content)) { + if (is_array($content)) { + foreach ($content as $contentitem) { + if (!($contentitem instanceof media_type)) { + throw new \coding_exception('Content must be an instance of media_type.'); + } + } + } + } + parent::__construct(...$args); + } + + #[\Override] + public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?\stdClass { + $data = (object) [ + 'description' => $this->description, + 'required' => $this->required, + 'content' => [], + ]; + + if ($this->content instanceof response\content\payload_response_type) { + $data->content = $this->content->get_openapi_schema( + api: $api, + ); + return $data; + } + + foreach ($this->content as $content) { + $data->content[$content->get_encoding()] = $content->get_openapi_schema( + api: $api, + ); + } + + return $data; + } + + /** + * Get the relevant body for the specified request. + * + * Request bodies can be different for different content-types, as noted in the request. + * + * @param ServerRequestInterface $request + * @return media_type + * @throws \invalid_parameter_exception + */ + public function get_body_for_request( + ServerRequestInterface $request, + ): media_type { + if ($this->content instanceof payload_response_type) { + $content = $this->content->get_media_type_instance( + mimetype: $request->getHeaderLine('Content-Type'), + required: $this->is_required(), + ); + + if ($content) { + return $content; + } + } else { + foreach ($this->content as $content) { + if ($content::get_encoding() === $request->getHeaderLine('Content-Type')) { + return $content; + } + } + } + + throw new \invalid_parameter_exception('No matching content type found.'); + } + + /** + * Whether this query parameter is required. + * + * @return bool + */ + public function is_required(): bool { + return $this->required; + } +} diff --git a/lib/classes/router/schema/response/abstract_response.php b/lib/classes/router/schema/response/abstract_response.php new file mode 100644 index 00000000000..e080e09f27a --- /dev/null +++ b/lib/classes/router/schema/response/abstract_response.php @@ -0,0 +1,59 @@ +. + +namespace core\router\schema\response; + +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +/** + * An abstract response to a request. + * + * This approach is inspired and based upon slim-routing https://github.com/juliangut/slim-routing + * We only need a fraction of this functionality. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class abstract_response implements response_type { + /** + * Create a new abstract response. + * + * @param ServerRequestInterface $request The request + * @param ResponseInterface|null $response The response + */ + public function __construct( + /** @var ServerRequestInterface The Request */ + public readonly ServerRequestInterface $request, + /** @var ResponseInterface|null The Response */ + public readonly ?ResponseInterface $response = null, + ) { + } + + #[\Override] + public function get_request(): ServerRequestInterface { + return $this->request; + } + + #[\Override] + public function get_response( + ResponseFactoryInterface $responsefactory, + ): ?ResponseInterface { + return $this->response ?? $responsefactory->createResponse(); + } +} diff --git a/lib/classes/router/schema/response/content/json_media_type.php b/lib/classes/router/schema/response/content/json_media_type.php new file mode 100644 index 00000000000..22d41fc0501 --- /dev/null +++ b/lib/classes/router/schema/response/content/json_media_type.php @@ -0,0 +1,31 @@ +. + +namespace core\router\schema\response\content; + +/** + * A JSON Message body. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class json_media_type extends media_type { + #[\Override] + public static function get_encoding(): string { + return 'application/json'; + } +} diff --git a/lib/classes/router/schema/response/content/media_type.php b/lib/classes/router/schema/response/content/media_type.php new file mode 100644 index 00000000000..7204cbba5d5 --- /dev/null +++ b/lib/classes/router/schema/response/content/media_type.php @@ -0,0 +1,128 @@ +. + +namespace core\router\schema\response\content; + +use core\exception\coding_exception; +use core\router\schema\example; +use core\router\schema\openapi_base; +use core\router\schema\objects\type_base; +use core\router\schema\specification; + +/** + * An OpenAPI MediaType. + * https://swagger.io/specification/#media-type-object + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class media_type extends openapi_base { + /** + * Create a new instance of a media_type definition. + * + * @param type_base|null $schema The OpenAPI Schema to use + * @param example|null $example An example of the media type + * @param example[] $examples An array of examples of the media type + * @param bool $required Whether the media_type is required + * @param mixed[] ...$extra + * @throws coding_exception + */ + public function __construct( + /** @var type_base|null The OpenAPI Schema to use */ + protected ?type_base $schema = null, + /** @var example|null An example of the media type */ + protected ?example $example = null, + /** @var example[] An array of examples of the media type */ + protected array $examples = [], + + /** @var bool Whether the media_type is required */ + protected bool $required = false, + + ...$extra, + ) { + if ($example) { + if (count($examples)) { + throw new coding_exception('Only one of example or examples can be specified.'); + } + $this->examples[$example->get_name()] = $example; + } + + parent::__construct(...$extra); + } + + #[\Override] + public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?\stdClass { + $data = (object) []; + + if ($this->schema) { + $data->schema = $this->schema->get_openapi_schema( + api: $api, + ); + } + + if (count($this->examples)) { + $data->examples = []; + foreach ($this->examples as $example) { + $data->examples[$example->get_name()] = $example->get_openapi_schema($api); + } + } + + if ($this->required) { + $data->required = true; + } + + return $data; + } + + /** + * Get the schema for this media type. + * + * @return type_base + */ + public function get_schema(): type_base { + return $this->schema; + } + + /** + * Get the mimetype for this media type. + * + * @return string + */ + public function get_mimetype(): string { + return static::get_encoding(); + } + + /** + * Get the encoding for this media type. + * + * @return string + */ + abstract public static function get_encoding(): string; + + + /** + * Whether this query parameter is required. + * + * @return bool + */ + public function is_required(): bool { + return $this->required; + } +} diff --git a/lib/classes/router/schema/response/content/payload_response_type.php b/lib/classes/router/schema/response/content/payload_response_type.php new file mode 100644 index 00000000000..693bd65c41c --- /dev/null +++ b/lib/classes/router/schema/response/content/payload_response_type.php @@ -0,0 +1,112 @@ +. + +namespace core\router\schema\response\content; + +use core\router\schema\openapi_base; +use core\router\schema\specification; + +/** + * A standard Moodle response for all supported payload types. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class payload_response_type extends openapi_base { + /** @var array Arguments to pass the media instantiator */ + protected array $args; + + /** + * Crate a new payload response type. + * + * @param bool $required Whether this query parameter is required. + * @param array ...$args Extra args for future compatibility. + */ + public function __construct( + /** @var bool Whether a payload response is required */ + protected bool $required = false, + ...$args, + ) { + parent::__construct(); + $this->args = $args; + } + + /** + * Get the supported content types. + * + * @return \class-string[] + */ + public function get_supported_content_types(): array { + return [ + json_media_type::class, + ]; + } + + /** + * Get a media type instance for the given mimetype. + * + * @param string|null $mimetype The mimetype to get the instance for. + * @param string|null $classname The classname to get the instance for. + * @param bool $required Whether the media type is required. + * @return media_type|null + */ + public function get_media_type_instance( + ?string $mimetype = null, + ?string $classname = null, + bool $required = false, + ): ?media_type { + if ($classname) { + return new $classname(...$this->args); + } + + foreach ($this->get_supported_content_types() as $contenttypeclass) { + if (empty($mimetype) || $contenttypeclass::get_encoding() === $mimetype) { + $args = $this->args; + $args['required'] = $required; + return new $contenttypeclass(...$args); + } + } + + return null; // @codeCoverageIgnore + } + + #[\Override] + public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?\stdClass { + $content = (object) []; + + foreach ($this->get_supported_content_types() as $contenttypeclass) { + $contenttype = new $contenttypeclass(...$this->args); + $content->{$contenttype->get_mimetype()} = $contenttype->get_openapi_schema( + api: $api, + ); + } + + return $content; + } + + /** + * Whether this query parameter is required. + * + * @return bool + */ + public function is_required(): bool { + return $this->required; + } +} diff --git a/lib/classes/router/schema/response/payload_response.php b/lib/classes/router/schema/response/payload_response.php new file mode 100644 index 00000000000..ada5d380edb --- /dev/null +++ b/lib/classes/router/schema/response/payload_response.php @@ -0,0 +1,83 @@ +. + +namespace core\router\schema\response; + +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +/** + * A Payload Response for a Routed request. + * + * This response is a container for a response which contains a set of data. + * It is used to pass data from a controller to the routing engine, where it will be formatted into the + * response type requested by the client. + * + * This approach is inspired and based upon slim-routing https://github.com/juliangut/slim-routing + * We only need a fraction of this functionality. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class payload_response extends abstract_response { + /** + * Create a new payload response. + * + * @param array $payload The payload + * @param ServerRequestInterface $request The request + * @param ResponseInterface|null $response The response + */ + public function __construct( + /** @var array The payload */ + public readonly array $payload, + ServerRequestInterface $request, + ?ResponseInterface $response = null, + ) { + parent::__construct($request, $response); + } + + #[\Override] + public function get_response( + ResponseFactoryInterface $responsefactory, + ): ?ResponseInterface { + $response = parent::get_response($responsefactory); + + $response->getBody()->write((string) json_encode( + $this->payload, + $this->get_json_flags(), + )); + return $response->withHeader('Content-Type', 'application/json; charset=utf-8'); + } + + /** + * Get the flags to use when encoding JSON. + * + * @return int + */ + private function get_json_flags(): int { + global $CFG; + + $flags = \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_PRESERVE_ZERO_FRACTION; + + if ($CFG->debugdeveloper) { + $flags |= \JSON_PRETTY_PRINT; + } + + return $flags; + } +} diff --git a/lib/classes/router/schema/response/response.php b/lib/classes/router/schema/response/response.php new file mode 100644 index 00000000000..bb6cb87f333 --- /dev/null +++ b/lib/classes/router/schema/response/response.php @@ -0,0 +1,135 @@ +. + +namespace core\router\schema\response; + +use core\exception\coding_exception; +use core\router\schema\openapi_base; +use core\router\schema\response\content\media_type; +use core\router\schema\specification; +use core\router\schema\response\content\payload_response_type; +use Psr\Http\Message\ResponseInterface; + +/** + * An OpenAPI Response. + * + * https://spec.openapis.org/oas/v3.1.0#response-object + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class response extends openapi_base { + /** + * Create a new described response. + * + * @param int $statuscode The status code for this response + * @param string $description A description of this response + * @param array $headers The headers associated with this response + * @param array|payload_response_type $content The content of this response + * @param mixed ...$extra Any extra data to store + * @throws coding_exception + */ + public function __construct( + /** @var int The status code for this response */ + public readonly int $statuscode = 200, + /** @var string A description of this response */ + public readonly string $description = '', + /** @var array The headers associated with this response */ + private readonly array $headers = [], + /** @var array|payload_response_type The content of this response */ + public readonly array|payload_response_type $content = [], + ...$extra, + ) { + if (is_array($content)) { + foreach ($content as $contentitem) { + if (!$contentitem instanceof media_type) { + throw new coding_exception('Content must be an array of payload response types'); + } + } + } + + parent::__construct(...$extra); + } + + /** + * Validate the response. + * + * @param ResponseInterface $response The response to validate + */ + public function validate( + ResponseInterface $response, + ): void { + $response; + } + + /** + * Get the description for this response. + * + * @return string + */ + protected function get_description(): string { + if ($this->description !== '') { + return $this->description; + } + + return match ($this->statuscode) { + 200 => 'OK', + default => '', + }; + } + + #[\Override] + public function get_openapi_description( + specification $api, + ?string $path = null, + ): ?\stdClass { + $data = (object) [ + 'description' => $this->get_description(), + ]; + + if (count($this->headers)) { + foreach ($this->headers as $header) { + $data->headers[$header->get_name()] = $header->get_openapi_schema( + api: $api, + ); + } + } + + if ($this->content instanceof content\payload_response_type) { + $data->content = $this->content->get_openapi_schema( + api: $api, + ); + } else if (count($this->content)) { + foreach ($this->content as $body) { + $data->content[$body->get_mimetype()] = $body->get_openapi_schema( + api: $api, + ); + } + } + + return $data; + } + + /** + * Get the status code for this response. + * + * @return int + */ + public function get_status_code(): int { + return $this->statuscode; + } +} diff --git a/lib/classes/router/schema/response/response_type.php b/lib/classes/router/schema/response/response_type.php new file mode 100644 index 00000000000..3743af53343 --- /dev/null +++ b/lib/classes/router/schema/response/response_type.php @@ -0,0 +1,47 @@ +. + +namespace core\router\schema\response; + +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +/** + * An abstract response to a request. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface response_type { + /** + * Get the Request object for this response. + * + * @return ServerRequestInterface + */ + public function get_request(): ServerRequestInterface; + + /** + * Get the Response object for this response. + * + * @param ResponseFactoryInterface $responsefactory The response factory + * @return null|ResponseInterface + */ + public function get_response( + ResponseFactoryInterface $responsefactory, + ): ?ResponseInterface; +} diff --git a/lib/classes/router/schema/response/view_response.php b/lib/classes/router/schema/response/view_response.php new file mode 100644 index 00000000000..af408832728 --- /dev/null +++ b/lib/classes/router/schema/response/view_response.php @@ -0,0 +1,88 @@ +. + +namespace core\router\schema\response; + +use GuzzleHttp\Psr7\Utils; +use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +/** + * A response which will render the specified template. + * + * This approach is inspired and based upon slim-routing https://github.com/juliangut/slim-routing + * We only need a fraction of this functionality. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class view_response extends abstract_response { + /** + * Create a new view response. + * + * @param string $template The template name + * @param array $parameters The parameters to pass + * @param ServerRequestInterface $request The request + * @param ResponseInterface|null $response The response + */ + public function __construct( + /** @var string The template name */ + private readonly string $template, + /** @var array The parameters to pass */ + private readonly array $parameters, + ServerRequestInterface $request, + ?ResponseInterface $response = null, + ) { + parent::__construct($request, $response); + } + + /** + * Get the template name. + * + * @return string + */ + public function get_template_name(): string { + return $this->template; + } + + /** + * Get the parameters. + * + * @return array + */ + public function get_parameters(): array { + return $this->parameters; + } + + #[\Override] + public function get_response( + ResponseFactoryInterface $responsefactory, + ): ?ResponseInterface { + global $OUTPUT; + + $response = parent::get_response($responsefactory); + return $response + ->withHeader('Content-Type', 'text/html; charset=utf-8') + ->withBody(Utils::streamFor( + $OUTPUT->render_from_template( + $this->get_template_name(), + $this->get_parameters(), + ), + )); + } +} diff --git a/lib/classes/router/schema/specification.php b/lib/classes/router/schema/specification.php new file mode 100644 index 00000000000..0bd3488d31e --- /dev/null +++ b/lib/classes/router/schema/specification.php @@ -0,0 +1,521 @@ +. + +namespace core\router\schema; + +use coding_exception; +use core\router\response\invalid_parameter_response; +use core\router\response\not_found_response; +use core\router\route; +use core\router\route_loader_interface; +use core\router\schema\objects\type_base; +use core\router\schema\response\response; +use core\url; +use stdClass; + +/** + * Moodle OpenApi Specification class. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class specification implements + \JsonSerializable +{ + /** @var string The OpenAPI version represented in this specification */ + public const OPENAPI_VERSION = '3.1.0'; + + /** @var stdClass The data which forms the specification */ + protected stdClass $data; + + /** @var bool Whether the data has been finalised for output yet */ + protected bool $finalised = false; + + /** @var callable[] A list of common responses that are frequently found in paths */ + protected array $commonresponses = []; + + /** + * Constructor to configure base information. + */ + public function __construct() { + $this->data = (object) [ + 'openapi' => self::OPENAPI_VERSION, + 'info' => (object) [ + 'title' => 'Moodle LMS', + 'description' => 'REST API for Moodle LMS', + 'summary' => 'Moodle LMS REST API', + 'license' => (object) [ + 'name' => 'GNU GPL v3 or later', + 'url' => 'https://www.gnu.org/licenses/gpl-3.0.html', + ], + ], + + // Servers are added during output. + 'servers' => [], + + // Paths are added after initialisation. + 'paths' => (object) [], + + 'components' => (object) [ + // Note: This list must be kept in-sync with add_component. + 'schemas' => (object) [], + 'responses' => (object) [], + 'parameters' => (object) [], + 'examples' => (object) [], + 'requestBodies' => (object) [], + 'headers' => (object) [], + + // The add_component method does not support securitySchemes because we hard-code these. + 'securitySchemes' => (object) [ + 'api_key' => (object) [ + 'type' => 'apiKey', + 'name' => 'api_key', + 'in' => parameter::IN_HEADER, + ], + 'cookie' => (object) [ + 'type' => 'apiKey', + 'name' => 'MoodleSession', + 'in' => parameter::IN_COOKIE, + ], + // TODO MDL-82242: Add support for OAuth2. + ], + ], + // TODO MDL-82242: Add support for OAuth2. + 'security' => [ + (object) [ + 'api_key' => [], + 'cookie' => [], + ], + ], + 'externalDocs' => (object) [ + 'description' => 'Moodle Developer Docs', + 'url' => 'https://moodledev.io', + ], + ]; + + $this->generate_common_responses(); + } + + /** + * Generate the callables for common responses that are frequently found in paths. + * + * @return specification + */ + protected function generate_common_responses(): self { + $invalidresponse = new invalid_parameter_response(); + $notfoundresponse = new not_found_response(); + + $this->commonresponses[] = function ( + route $route, + stdClass $data + ) use ( + $invalidresponse, + $notfoundresponse, + ): stdClass { + if ($route->has_any_validatable_parameter()) { + if (!array_key_exists($invalidresponse::get_exception_status_code(), $data->responses)) { + $data->responses[$invalidresponse::get_exception_status_code()] = $invalidresponse->get_openapi_schema($this); + } + if (!array_key_exists($notfoundresponse::get_exception_status_code(), $data->responses)) { + $data->responses[$notfoundresponse::get_exception_status_code()] = $notfoundresponse->get_openapi_schema($this); + } + } + + return $data; + }; + return $this; + } + + /** + * Get the common request responses. + * + * @return callable[] + */ + public function get_common_request_responses(): array { + if (empty($this->commonresponses)) { + $this->generate_common_responses(); // @codeCoverageIgnore + } + + return $this->commonresponses; + } + + /** + * Finalise the data and prepare it for consumption. + */ + protected function finalise(): self { + global $CFG; + + if ($this->finalised) { + return $this; + } + + // Add the Moodle site version here. + $this->data->info->version = $CFG->version; + + // Add the server configuration. + $serverdescription = str_replace("'", "\'", format_string(get_site()->fullname)); + $this->add_server( + url::routed_path(route_loader_interface::ROUTE_GROUP_API)->out(), + $serverdescription, + ); + + $this->finalised = true; + + return $this; + } + + /** + * Implement the json serialisation interface. + * + * @return mixed + */ + public function jsonSerialize(): mixed { + return $this->get_schema(); + } + + /** + * Get the OpenAPI schema. + * + * @return stdClass + */ + final public function get_schema(): stdClass { + return $this + ->finalise() + ->data; + } + + /** + * Add a component to the components object. + * + * https://spec.openapis.org/oas/v3.1.0#components-object + * + * Note: The following component types are supported: + * + * - schemas + * - responses + * - parameters + * - examples + * - requestBodies + * - headers + * + * At this time, other component types are not supported. + * + * @param openapi_base $object + * @return specification + * @throws coding_exception If the component type is unknown. + */ + public function add_component(openapi_base $object): self { + match (true) { + is_a($object, header_object::class) => $this->add_header($object), + is_a($object, parameter::class) => $this->add_parameter($object), + is_a($object, response::class) => $this->add_response($object), + is_a($object, example::class) => $this->add_example($object), + is_a($object, request_body::class) => $this->add_request_body($object), + is_a($object, type_base::class) => $this->add_schema($object), + default => throw new coding_exception("Unknown object type."), + }; + + return $this; + } + + /** + * Add a server to the specification. + * + * @param string $url The URL of the API base + * @param string $description + * @return specification + */ + public function add_server( + string $url, + string $description, + ): self { + $this->data->servers[] = (object) [ + 'url' => $url, + 'description' => $description, + ]; + + return $this; + } + + /** + * Add an API Path. + * + * @param string $component The Moodle component + * @param route $route The route which handles this request + * @return specification + */ + public function add_path( + string $component, + route $route, + ): self { + // Compile the final path, complete with component prefix. + [$type, $subsystem] = \core_component::normalize_component($component); + + if ($type === 'core') { + if ($subsystem) { + $path = "/{$subsystem}"; + } else { + $path = "/core"; + } + } else { + $path = "/{$component}"; + } + $path .= $route->get_path(); + + // Helper to add the path to the specification. + // Note: We use this helper because OpenAPI does not support optional parameters. + // Therefore we must handle that in Moodle, adding path variants with and without each optional parameter. + $addpath = function (string $path) use ($route, $component) { + // Remove the optional parameters delimiters from the path. + $path = str_replace( + ['[', ']'], + '', + $path, + ); + + // Get the OpenAPI description for this path with the updated path. + $pathdocs = $this->get_openapi_schema_for_route( + route: $route, + component: $component, + path: $path, + ); + + if (!property_exists($this->data->paths, $path)) { + $this->data->paths->$path = (object) []; + } + + foreach ((array) $pathdocs as $method => $methoddata) { + // Copy each of the pathdocs into place. + $this->data->paths->{$path}->{$method} = $methoddata; + } + }; + + // First add the entire path complete with all optional parameters. + // The optional parameter delimiters are `[` and `]`, and are removed in `$addpath`. + $addpath($path); + + // Check for any optional parameters. + // OpenAPI does not support optional parameters so we have to duplicate routes instead. + // We can determine if this is optional if there is any `[` character before it in the path. + // There can be no required parameter after any optional parameter. + $optionalparameters = array_filter( + array: $route->get_path_parameters(), + callback: fn ($parameter) => !$parameter->is_required($route), + ); + + if (!empty($optionalparameters)) { + // Go through the path from end to start removing optional parameters and adding them to the path list. + while (strrpos($path, '[') !== false) { + $path = substr($path, 0, strrpos($path, '[')); + $addpath($path); + } + } + + return $this; + } + + /** + * Add a schema to the shared components section of the specification. + * + * @param type_base $schema + * @return specification + */ + protected function add_schema( + type_base $schema, + ): self { + $name = $schema->get_reference(qualify: false); + if (!property_exists($this->data->components->schemas, $name)) { + $this->data->components->schemas->$name = $schema->get_openapi_description($this); + } + + return $this; + } + + /** + * Add a schema to the shared components section of the specification. + * + * @param parameter $parameter + * @return specification + */ + protected function add_parameter( + parameter $parameter, + ): self { + $name = $parameter->get_reference(qualify: false); + $this->data->components->parameters->$name = $parameter->get_openapi_description($this); + + return $this; + } + + /** + * Add a header to the shared components section of the specification. + * + * @param header_object $header + * @return self + */ + protected function add_header( + header_object $header, + ): self { + $name = $header->get_reference(qualify: false); + $this->data->components->headers->$name = $header->get_openapi_description($this); + + return $this; + } + + /** + * Add a response to the shared components section of the specification. + * + * @param response $response + * @return specification + */ + protected function add_response( + response $response, + ): self { + $name = $response->get_reference(qualify: false); + $this->data->components->responses->$name = $response->get_openapi_description($this); + + return $this; + } + + /** + * Add an example to the shared components section of the specification. + * + * @param example $example + * @return specification + */ + protected function add_example( + example $example, + ): self { + $name = $example->get_reference(qualify: false); + $this->data->components->examples->$name = $example->get_openapi_description($this); + + return $this; + } + + /** + * Add a request body to the shared components section of the specification. + * + * @param request_body $body + * @return specification + */ + protected function add_request_body( + request_body $body, + ): self { + $name = $body->get_reference(qualify: false); + $this->data->components->requestBodies->$name = $body->get_openapi_description($this); + + return $this; + } + + /** + * Check whether a reference is defined + * + * @param string $ref + * @return bool + */ + public function is_reference_defined( + string $ref, + ): bool { + if (!str_starts_with($ref, '#/components/')) { + return false; + } + + // Remove the leading #/components/ part. + $ref = substr($ref, strlen('#/components/')); + + // Split the path and name. + [$path, $name] = explode('/', $ref, 2); + + if (!property_exists($this->data->components, $path)) { + return false; + } + + return property_exists($this->data->components->$path, $name); + } + + + /** + * Get the OpenAPI description for this route. + * + * @param route $route + * @param string $component + * @param string $path + * @return stdClass + */ + public function get_openapi_schema_for_route( + route $route, + string $component, + string $path, + ): stdClass { + $data = (object) [ + 'description' => $route->description, + 'summary' => $route->title, + 'tags' => [$component, ...$route->tags], + 'parameters' => [], + 'responses' => [], + ]; + + if ($route->get_request_body()) { + $data->requestBody = $route->get_request_body()->get_openapi_schema( + api: $this, + path: $path, + ); + } + + if ($route->security !== null) { + $data->security = $route->security; + } + + if ($route->deprecated) { + $data->deprecated = true; + } + + foreach ($route->get_responses() as $response) { + $data->responses[$response->get_status_code()] = $response->get_openapi_schema( + api: $this, + path: $path, + ); + } + + $data->parameters = array_values(array_filter( + array_map( + fn($param) => $param->get_openapi_schema( + api: $this, + path: $path, + ), + array_merge( + $route->get_path_parameters(), + $route->get_query_parameters(), + $route->get_header_parameters(), + ), + ), + fn($param) => $param !== null, + )); + + foreach ($this->get_common_request_responses() as $callable) { + $data = $callable($route, $data); + } + + $methoddata = []; + $methods = $route->get_methods(['GET']); + + foreach ($methods as $method) { + $methoddata[strtolower($method)] = $data; + } + + return (object) $methoddata; + } +} diff --git a/lib/classes/router/util.php b/lib/classes/router/util.php new file mode 100644 index 00000000000..1d9fe8bf6cc --- /dev/null +++ b/lib/classes/router/util.php @@ -0,0 +1,230 @@ +. + +namespace core\router; + +use moodle_url; +use GuzzleHttp\Psr7\Uri; +use Psr\Http\Message\ServerRequestInterface; +use Slim\Routing\RouteContext; + +/** + * Routing Helper Utilities. + * + * This class includes a variety of helpers for working with routes, including: + * - redirectors + * - callable to route name converters + * - callable to path converters + * - helpers to fetch the \core\router\route instance + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class util { + /** + * Redirect to the specified URL, carrying all parameters across too. + * + * @param string|moodle_url $path + * @param array $excludeparams Any parameters to exclude from the query params + * @codeCoverageIgnore + */ + public static function redirect_with_params( + string|moodle_url $path, + array $excludeparams = [], + ): never { + $params = $_GET; + $url = new moodle_url( + $path, + $params, + ); + $url->remove_params($excludeparams); + + redirect($url); + } + + /** + * Redirect to the route at the callable supplied. + * + * @param callable|array|string $callable + * @param array $params Any parameters to include in the path + * @codeCoverageIgnore + */ + public static function redirect_to_callable( + callable|array|string $callable, + array $params = [], + ): never { + $params = array_merge( + $_GET, + $params, + ); + + $url = self::get_path_for_callable($callable, $params, $params); + + redirect($url); + } + + /** + * Get the route name for the specified callable. + * + * @param callable|array|string $callable + * @return string + * @throws \coding_exception If the callable could not be resolved into an Array format + */ + public static function get_route_name_for_callable( + callable|array|string $callable, + ): string { + $resolver = \core\di::get(\Invoker\CallableResolver::class); + $callable = $resolver->resolve($callable); + + if (!is_array($callable)) { + throw new \coding_exception('Resolved callable must be in array form'); + } + + return get_class($callable[0]) . '::' . $callable[1]; + } + + /** + * Get the URI path for the specified callable. + * + * @param string|array|callable $callable the Callable to get the URI for + * @param array $params Any parameters to include in the path + * @param array $queryparams Any parameters to include in the query string + * @return moodle_url + */ + public static function get_path_for_callable( + string|array|callable $callable, + array $params, + array $queryparams, + ): moodle_url { + global $CFG; + + $router = \core\di::get(\core\router::class); + $app = $router->get_app(); + $parser = $app->getRouteCollector()->getRouteParser(); + + $routename = self::get_route_name_for_callable($callable); + + return new moodle_url( + url: $parser->fullUrlFor( + new Uri($CFG->wwwroot), + $routename, + $params, + $queryparams, + ), + ); + } + + /** + * Get the route attribute for the specified request. + * + * @param ServerRequestInterface $request + * @return null|route + */ + public static function get_route_instance_for_request(ServerRequestInterface $request): ?route { + if ($route = $request->getAttribute(route::class)) { + return $route; + } + + $context = RouteContext::fromRequest($request); + if ($slimroute = $context->getRoute()) { + return self::get_route_instance_for_method($slimroute->getCallable()); + } + + // This should not be encountered - the route should always be set. + return null; // @codeCoverageIgnore + } + + /** + * Get the instance of the \route\router\route attribute for the specified callable if one is available. + * + * @param callable|array|string $callable + * @return null|route The route if one was found. + */ + public static function get_route_instance_for_method(callable|array|string $callable): ?route { + // Normalise the callable using the resolver. + // This happens in the same way that Slim does so. + $resolver = \core\di::get(\Invoker\CallableResolver::class); + $callable = $resolver->resolve($callable); + + if (!is_array($callable)) { + // The callable could not be resolved into an array. + return null; + } + + // Locate the Class for this callable. + $classinfo = new \ReflectionClass($callable[0]); + + // Locate the method for this callable. + $methodinfo = $classinfo->getMethod($callable[1]); + if (!$methodinfo) { + // The method does not exist. This shouldn't be possible because the resolver will throw an exception. + return null; // @codeCoverageIgnore + } + + return self::attempt_get_route_instance_for_method($classinfo, $methodinfo); + } + + /** + * Attempt to get the route instance for the specified method, handling any errors in the code along the way. + * + * @param \ReflectionClass $classinfo + * @param \ReflectionMethod $methodinfo + * @return null|route + */ + private static function attempt_get_route_instance_for_method( + \ReflectionClass $classinfo, + \ReflectionMethod $methodinfo, + ): ?route { + $instantiator = function (array $attributes) { + global $CFG; + try { + return $attributes ? $attributes[0]->newInstance() : null; + // @codeCoverageIgnoreStart + } catch (\Throwable $e) { + // The route attribute could not be instantiated. + // When debugging, this is useful to know. + // When not, log to error_log. + if (!$CFG->debugdisplay) { + debugging('Could not instantiate route attribute: ' . $e->getMessage()); + return null; + } + + default_exception_handler($e); + } + // @codeCoverageIgnoreEnd + }; + + $methodattributes = $methodinfo->getAttributes(route::class); + $methodroute = $instantiator($methodattributes); + + if (!$methodroute) { + // No route found. + return null; + } + + $classattributes = $classinfo->getAttributes(route::class); + if ($classattributes) { + $classinstance = $instantiator($classattributes); + if ($classinstance) { + // The class has a #route attribute. + $methodroute->set_parent($classinstance); + } + } + + return $methodroute; + } +} diff --git a/lib/classes/tests/route_testcase.php b/lib/classes/tests/route_testcase.php new file mode 100644 index 00000000000..22b75459d86 --- /dev/null +++ b/lib/classes/tests/route_testcase.php @@ -0,0 +1,549 @@ +. + +namespace core\tests; + +use core\router; +use core\router\bridge; +use core\router\mocking_route_loader; +use core\router\route_loader_interface; +use core\router\schema\openapi_base; +use core\router\schema\referenced_object; +use core\router\schema\specification; +use stdClass; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\ServerRequest; +use GuzzleHttp\Psr7\Uri; +use PHPUnit\Framework\ExpectationFailedException; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamInterface; +use Slim\App; +use Slim\Middleware\RoutingMiddleware; +use Slim\Routing\Route; +use Slim\Routing\RouteContext; + +/** + * Tests for user preference API handler. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class route_testcase extends \advanced_testcase { + /** + * Update the test route loader using the supplied callback. + * + * @param callable $modifier + */ + protected function update_test_route_loader( + callable $modifier, + ): void { + self::load_fixture('core', 'router/mocking_route_loader.php'); + + $routeloader = \core\di::get(mocking_route_loader::class); + $modifier($routeloader); + \core\di::set(route_loader_interface::class, $routeloader); + } + + /** + * Add a route from a class method. + * + * @param string $classname The class to add the route from + * @param string $methodname The method name to add + * @param null|string $grouppath The path to the route group + */ + protected function add_route_to_route_loader( + string $classname, + string $methodname, + ?string $grouppath = null, + ) { + $grouppath = $grouppath ?? $this->guess_group_path_from_classname($classname); + $this->update_test_route_loader(fn (mocking_route_loader $routeloader) => $routeloader->mock_route_from_class_method( + $grouppath, + new \ReflectionMethod($classname, $methodname), + )); + } + + /** + * Add all routes from the specified class to the test loader. + * + * Only methods within the class with a #[route] attribute will be added. + * + * @param string $classname The class to add routes from + * @param null|string $grouppath The path of the route group + */ + protected function add_class_routes_to_route_loader( + string $classname, + ?string $grouppath = null, + ): void { + $this->update_test_route_loader( + fn (mocking_route_loader $routeloader) => $routeloader->add_all_routes_in_class( + grouppath: $grouppath ?? $this->guess_group_path_from_classname($classname), + class: $classname, + ), + ); + } + + /** + * Guess the group path from a class name. + * + * @param string $classname + * @return string + */ + protected function guess_group_path_from_classname( + string $classname, + ): string { + [, , $l3] = explode('\\', $classname, 4); + + if ($l3 === 'api') { + return route_loader_interface::ROUTE_GROUP_API; + } + + throw new \coding_exception("Unable to determine route path for '{$classname}'"); + } + + /** + * Mock a route from a route attribute. + * + * @param string $grouppath + * @param \core\router\route $route + * @param string $name + * @param callable|null $callable + */ + protected function mock_route_from_route_attribute( + string $grouppath, + \core\router\route $route, + string $name = 'route', + ?callable $callable = null, + ): void { + if ($callable === null) { + $callable = fn ($request, $response) => $response->withStatus(200); + } + + $this->update_test_route_loader(fn (mocking_route_loader $routeloader) => $routeloader->mock_route_from_callable( + grouppath: $grouppath, + methods: $route->get_methods(['GET']), + pattern: $route->get_path(), + callable: $callable, + name: $name, + )); + } + + /** + * Get a fully-configured instance of the Moodle Routing Application. + * + * @return App + */ + protected function get_app(): App { + $router = $this->get_router(); + + return $router->get_app(); + } + + /** + * Get a fully-configured instance of the Moodle Routing Application. + * + * @param string $basepath The basepath for the router + * @return router + */ + protected function get_router(string $basepath = ''): router { + \core\di::set( + router::class, + \DI\autowire(router::class)->constructorParameter('basepath', $basepath), + ); + + return \core\di::get(router::class); + } + + /** + * Get an unconfigured instance of the Slim Application. + * + * @return App + */ + protected function get_simple_app(): App { + global $CFG; + require_once("{$CFG->libdir}/nikic/fast-route/src/functions.php"); + $app = bridge::create( + container: \core\di::get_container(), + ); + + return $app; + } + + /** + * Get the request for a route which is known to the router. + * + * @param \core\router\route $route + * @param string $path + * @return ServerRequestInterface + */ + protected function get_request_for_routed_route( + \core\router\route $route, + string $path, + ): ServerRequestInterface { + $this->mock_route_from_route_attribute('', $route); + + // Grab just one method. + $methods = $route->get_methods(); + $method = $methods ? reset($methods) : 'GET'; + + $request = $this->create_request( + method: $method, + path: $path, + prefix: '', + route: $route, + ); + + $request = $this->route_request( + $this->get_app(), + $request, + ); + + return $request; + } + + /** + * Create a Request object. + * + * @param string $method + * @param string $path + * @param string $prefix + * @param array $headers + * @param array $cookies + * @param array $serverparams + * @param null|\core\router\route $route + * @return ServerRequestInterface + */ + protected function create_request( + string $method, + string $path, + string $prefix = route_loader_interface::ROUTE_GROUP_API, + array $headers = ['Content-Type' => 'application/json'], + array $cookies = [], + array $serverparams = [], + ?\core\router\route $route = null, + ): ServerRequestInterface { + $uri = new Uri($prefix . $path); + + $request = new ServerRequest( + method: $method, + headers: $headers, + uri: $uri, + serverParams: $serverparams, + ); + + // Sadly Guzzle's Uri only deals with query strings, not query params. + $query = $uri->getQuery(); + if ($query) { + $queryparams = []; + foreach (explode('&', $query) as $queryparam) { + [$key, $value] = explode('=', $queryparam, 2); + $queryparams[$key] = $value; + } + $request = $request->withQueryParams($queryparams); + } + + if ($route) { + $request = $request->withAttribute(\core\router\route::class, $route); + } + + return $request + ->withCookieParams($cookies); + } + + /** + * Process a request with the app. + * + * @param string $method + * @param string $path + * @param string $prefix + * @param array $headers + * @param null|StreamInterface $body + * @param null|string $contenttype + * @param array $cookies + * @param array $serverparams + * @return ResponseInterface + */ + protected function process_request( + string $method, + string $path, + string $prefix = '', + array $headers = ['HTTP_ACCEPT' => 'application/json'], + ?StreamInterface $body = null, + ?string $contenttype = 'application/json', + array $cookies = [], + array $serverparams = [], + ): ResponseInterface { + $app = $this->get_app(); + if ($contenttype !== null) { + $headers['Content-Type'] = $contenttype; + } + $request = $this->create_request( + $method, + $path, + $prefix, + $headers, + $cookies, + $serverparams, + ); + + if ($body) { + $request = $request->withBody($body); + } + + return $app->handle($request); + } + + /** + * Process a request with the app. + * + * @param string $method + * @param string $path + * @param array $headers + * @param null|StreamInterface $body + * @param array $cookies + * @param array $serverparams + * @return ResponseInterface + */ + protected function process_api_request( + string $method, + string $path, + array $headers = ['HTTP_ACCEPT' => 'application/json'], + ?StreamInterface $body = null, + array $cookies = [], + array $serverparams = [], + ): ResponseInterface { + return $this->process_request( + method: $method, + path: $path, + prefix: route_loader_interface::ROUTE_GROUP_API, + headers: $headers, + body: $body, + cookies: $cookies, + serverparams: $serverparams, + ); + } + + /** + * Route a request within the app. + * + * @param App $app + * @param ServerRequestInterface $request + * @return ServerRequestInterface + */ + protected function route_request( + App $app, + ServerRequestInterface $request, + ): ServerRequestInterface { + $routingmiddleware = new RoutingMiddleware( + $app->getRouteResolver(), + $app->getRouteCollector()->getRouteParser(), + ); + + return $routingmiddleware->performRouting($request); + } + + /** + * Create a route and route it to create a request. + * + * @param string $routepath + * @param string $requestpath + * @return ServerRequestInterface + */ + protected function create_route( + string $routepath, + string $requestpath, + ): ServerRequestInterface { + $app = $this->get_simple_app(); + $app->get($routepath, fn () => new Response()); + $request = $this->route_request($app, new ServerRequest('GET', $requestpath)); + + return $request; + } + + /** + * Get the Slim Route object from a Request object. + * + * @param ServerRequestInterface $request + * @return Route + */ + protected function get_slim_route_from_request( + ServerRequestInterface $request, + ): Route { + return $request->getAttribute(RouteContext::ROUTE); + } + + /** + * Assert that a Response object was valid. + * + * @param ResponseInterface $response + * @param null|int $statuscode The expected status code + * @throws ExpectationFailedException + */ + protected function assert_valid_response( + ResponseInterface $response, + ?int $statuscode = 200, + ): void { + $this->assertInstanceOf(Response::class, $response); + $this->assertEquals( + $statuscode, + $response->getStatusCode(), + "Response status code is not $statuscode", + ); + } + + /** + * Assert that the supplied response related to an exception. + * + * @param ResponseInterface $response + * @param null|int $responsecode The expected response code + */ + protected function assert_exception_response( + ResponseInterface $response, + ?int $responsecode = null, + ): void { + $this->assertInstanceOf(Response::class, $response); + $this->assertNotEquals( + 200, + $response->getStatusCode(), + ); + + if ($responsecode !== null) { + $this->assertEquals( + $responsecode, + $response->getStatusCode(), + ); + } + + $payload = $this->decode_response($response); + $this->assertObjectHasProperty('message', $payload); + $this->assertObjectHasProperty('stacktrace', $payload); + } + + /** + * Assert that the supplied response was an invalid_parameter_exception response. + * + * @param ResponseInterface $response + */ + protected function assert_invalid_parameter_response( + ResponseInterface $response, + ): void { + $this->assert_exception_response($response, 400); + + $payload = $this->decode_response($response); + $this->assertObjectHasProperty('errorcode', $payload); + $this->assertEquals('invalidparameter', $payload->errorcode); + } + + /** + * Assert that the supplied response was an access_denied exception response. + * + * @param ResponseInterface $response + */ + protected function assert_access_denied_response( + ResponseInterface $response, + ): void { + $this->assert_exception_response($response, 403); + + $payload = $this->decode_response($response); + $this->assertObjectHasProperty('errorcode', $payload); + } + + /** + * Assert that the supplied response was a not_found exception response. + * + * @param \Psr\Http\Message\ResponseInterface $response + */ + protected function assert_not_found_response( + ResponseInterface $response, + ): void { + $this->assert_exception_response($response, 404); + + $payload = $this->decode_response($response); + $this->assertObjectHasProperty('errorcode', $payload); + } + + /** + * Decode the JSON response for a Response object. + * + * @param ResponseInterface $response + * @param bool $forcearray Force the contents to Array instead of Object + * @return stdClass|array + */ + protected function decode_response( + ResponseInterface $response, + bool $forcearray = false, + ): stdClass|array { + if ($forcearray) { + return json_decode( + json: (string) $response->getBody(), + associative: true, + ); + } else { + return (object) json_decode( + json: (string) $response->getBody(), + associative: false, + flags: JSON_FORCE_OBJECT, + ); + } + } + + /** + * Get the schema for an OpenAPI Component. + * + * Components include headers, parameters, responses, examples, requestBodies, and schemas. + * + * All components are subclasses of the openapi_base class and may be referenced. + * + * Any component which implements the referenced_object interface will return a reference + * to the stored internal object. + * + * @param specification $api + * @param openapi_base $component + * @return stdClass|null + */ + protected function get_api_component_schema( + specification $api, + openapi_base $component, + ): ?stdClass { + $this->assertInstanceOf(referenced_object::class, $component); + + if (is_a($component, \core\router\schema\header_object::class)) { + $type = 'headers'; + } else if (is_a($component, \core\router\schema\parameter::class)) { + $type = 'parameters'; + } else if (is_a($component, \core\router\schema\response\response::class)) { + $type = 'responses'; + } else if (is_a($component, \core\router\schema\example::class)) { + $type = 'examples'; + } else if (is_a($component, \core\router\schema\request_body::class)) { + $type = 'requestBodies'; + } else if (is_a($component, \core\router\schema\objects\type_base::class)) { + $type = 'schemas'; + } else { + $this->fail('Component is not a recognised type'); + } + + $ref = $component->get_reference(false); + + $schema = $api->get_schema(); + $components = $schema->components; + $component = $components->{$type}->{$ref} ?? null; + + return $component; + } +} diff --git a/lib/classes/user.php b/lib/classes/user.php index 6c62af7a0a1..9c12cd6aa7e 100644 --- a/lib/classes/user.php +++ b/lib/classes/user.php @@ -183,6 +183,37 @@ class user { return $DB->get_record('user', ['username' => $username, 'mnethostid' => $mnethostid], $fields, $strictness); } + /** + * Return User object based on their idnumber. + * + * @param string $idnumber The idnumber of the user searched. + * @param string $fields A comma separated list of user fields to be returned, support and noreply user. + * @param null|int $mnethostid The id of the remote host. + * @param int $strictness IGNORE_MISSING means compatible mode, false returned if user not found, debug message if more found; + * IGNORE_MULTIPLE means return first user, ignore multiple user records found(not recommended); + * MUST_EXIST means throw an exception if no user record or multiple records found. + * @return stdClass|bool user record if found, else false. + */ + public static function get_user_by_idnumber( + string $idnumber, + string $fields = '*', + ?int $mnethostid = null, + int $strictness = IGNORE_MISSING, + ): stdClass|bool { + global $DB, $CFG; + + // Because we use the username as the search criteria, we must also restrict our search based on mnet host. + if (empty($mnethostid)) { + // If empty, we restrict to local users. + $mnethostid = $CFG->mnet_localhost_id; + } + + return $DB->get_record('user', [ + 'idnumber' => $idnumber, + 'mnethostid' => $mnethostid, + ], $fields, $strictness); + } + /** * Searches for users by name, possibly within a specified context, with current user's access. * diff --git a/lib/db/caches.php b/lib/db/caches.php index 09b36ffc4f3..3c939101479 100644 --- a/lib/db/caches.php +++ b/lib/db/caches.php @@ -618,4 +618,11 @@ $definitions = array( 'simpledata' => true, 'staticacceleration' => true, ], + + 'routes' => [ + 'mode' => cache_store::MODE_APPLICATION, + 'simplekeys' => true, + 'simpledata' => true, + 'canuselocalstore' => true, + ], ); diff --git a/lib/db/hooks.php b/lib/db/hooks.php index 11a8aa56e81..b7f8e7bf31b 100644 --- a/lib/db/hooks.php +++ b/lib/db/hooks.php @@ -106,4 +106,8 @@ $callbacks = [ 'hook' => \core\hook\task\after_failed_task_max_delay::class, 'callback' => core\task\failed_task_callbacks::class . '::send_failed_task_max_delay_message', ], + [ + 'hook' => \core\hook\di_configuration::class, + 'callback' => [\core\router\hook_callbacks::class, 'provide_di_configuration'], + ], ]; diff --git a/lib/dmllib.php b/lib/dmllib.php index 497a00f8d2e..f9efbed1e65 100644 --- a/lib/dmllib.php +++ b/lib/dmllib.php @@ -15,6 +15,8 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +use core\exception\response_aware_exception; +use core\router\response\not_found_response; /** * This library contains all the Data Manipulation Language (DML) functions @@ -176,7 +178,7 @@ class dml_multiple_records_exception extends dml_exception { * @copyright 2008 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class dml_missing_record_exception extends dml_exception { +class dml_missing_record_exception extends dml_exception implements response_aware_exception { /** @var string A table's name.*/ public $tablename; /** @var string An SQL query.*/ @@ -218,6 +220,11 @@ class dml_missing_record_exception extends dml_exception { $errorinfo = $sql."\n[".var_export($params, true).']'; parent::__construct($errcode, $tablename, $errorinfo); } + + #[\Override] + public function get_response_classname(): string { + return not_found_response::class; + } } /** diff --git a/lib/tests/fixtures/router/mocking_route_loader.php b/lib/tests/fixtures/router/mocking_route_loader.php new file mode 100644 index 00000000000..7ac516b078e --- /dev/null +++ b/lib/tests/fixtures/router/mocking_route_loader.php @@ -0,0 +1,150 @@ +. + +namespace core\router; +use Slim\App; +use Slim\Routing\RouteCollectorProxy; + +/** + * A route loader containing mocked routes. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mocking_route_loader extends abstract_route_loader implements route_loader_interface { + /** @var array[] The mocked routes to configure in the loader */ + private array $groupdata = []; + + #[\Override] + public function configure_routes(App $app): array { + $routegroups = []; + + foreach ($this->groupdata as $path => $groupdata) { + $routegroups[$path] = $app->group($path, function ( + RouteCollectorProxy $group, + ) use ( + $groupdata, + ): void { + foreach ($groupdata as $data) { + $group + ->map(...$data['mapdata']) + ->setName($data['name']); + } + }); + } + + return $routegroups; + } + + /** + * Add a mocked route to the loader. + * + * @param string $grouppath The path of the RouteGroup to add the route to + * @param array $methods The HTTP methods to add the route for + * @param string $pattern The path to add the route for + * @param callable $callable The callable to add the route for + * @param string $name The name of the route + */ + public function mock_route_from_callable( + string $grouppath, + array $methods, + string $pattern, + callable $callable, + string $name, + ): void { + $this->add_groupdata( + $grouppath, + [ + 'methods' => $methods, + 'pattern' => $pattern, + 'callable' => $callable, + ], + $name, + ); + } + + /** + * Add all routes in a class to the loader. + * + * @param string $grouppath Thegroup to add the route to + * @param string|\ReflectionMethod $class The class to add to the loader + */ + public function add_all_routes_in_class( + string $grouppath, + \ReflectionMethod|string $class, + ) { + $classinfo = $class instanceof \ReflectionClass ? $class : new \ReflectionClass($class); + + $routes = $this->get_all_routes_in_class( + componentpath: '', + classinfo: $classinfo, + ); + + foreach ($routes as $mapdata) { + $this->add_groupdata( + $grouppath, + $mapdata, + implode('::', $mapdata['callable']), + ); + } + } + + /** + * Mock a route from a class method. + * + * @param string $grouppath The path to add the route to + * @param \ReflectionMethod $method The method to mock the route from + */ + public function mock_route_from_class_method( + string $grouppath, + \ReflectionMethod $method, + ) { + $mapdata = $this->get_route_data_for_method( + componentpath: '', + classinfo: $method->getDeclaringClass(), + methodinfo: $method, + ); + + $this->add_groupdata( + $grouppath, + $mapdata, + implode('::', $mapdata['callable']), + ); + } + + /** + * Add group data to the loader. + * + * @param string $grouppath The path of the RouteGroup to add the data to + * @param array $data The data to add to the group + * @param string $name The name of the group + */ + protected function add_groupdata( + string $grouppath, + array $data, + string $name, + ): void { + if (!array_key_exists($grouppath, $this->groupdata)) { + $this->groupdata[$grouppath] = []; + } + + $this->groupdata[$grouppath][] = [ + 'mapdata' => $data, + 'name' => $name, + ]; + } +} diff --git a/lib/tests/fixtures/router/route_implementing_request_handler_interface.php b/lib/tests/fixtures/router/route_implementing_request_handler_interface.php new file mode 100644 index 00000000000..95fbf4cad85 --- /dev/null +++ b/lib/tests/fixtures/router/route_implementing_request_handler_interface.php @@ -0,0 +1,31 @@ +. + +namespace core\router; + +/** + * A fixture for a route that implements the RequestHandlerInterface. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class route_implementing_request_handler_interface implements \Psr\Http\Server\RequestHandlerInterface { + #[\Override] + public function handle(\Psr\Http\Message\ServerRequestInterface $request): \Psr\Http\Message\ResponseInterface { + return new \GuzzleHttp\Psr7\Response(); + } +} diff --git a/lib/tests/fixtures/router/route_on_class.php b/lib/tests/fixtures/router/route_on_class.php new file mode 100644 index 00000000000..af9d6013d51 --- /dev/null +++ b/lib/tests/fixtures/router/route_on_class.php @@ -0,0 +1,53 @@ +. + +namespace core\fixtures; + +use core\router\route; +use GuzzleHttp\Psr7\Response; + +/** + * Fixture for tests of the router and route classes. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +#[route( + path: '/class/path', +)] +class route_on_class { + /** + * A method without a route. + * + * @return Response + */ + public function method_without_route(): Response { + return new Response(200, [], 'test'); + } + + /** + * A method with a route. + * + * @return Response + */ + #[route( + path: '/method/path', + )] + public function method_with_route(): Response { + return new Response(200, [], 'test2'); + } +} diff --git a/lib/tests/fixtures/router/route_on_method_only.php b/lib/tests/fixtures/router/route_on_method_only.php new file mode 100644 index 00000000000..b70bb202640 --- /dev/null +++ b/lib/tests/fixtures/router/route_on_method_only.php @@ -0,0 +1,50 @@ +. + +namespace core\fixtures; + +use core\router\route; +use GuzzleHttp\Psr7\Response; + +/** + * Fixture for tests of the router and route classes. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class route_on_method_only { + /** + * A method without a route. + * + * @return Response + */ + public function method_without_route(): Response { + return new Response(200, [], 'test'); + } + + /** + * A method with a route. + * + * @return Response + */ + #[route( + path: '/method/path', + )] + public function method_with_route(): Response { + return new Response(200, [], 'test2'); + } +} diff --git a/lib/tests/router/abstract_route_loader_test.php b/lib/tests/router/abstract_route_loader_test.php new file mode 100644 index 00000000000..1cd78df0c98 --- /dev/null +++ b/lib/tests/router/abstract_route_loader_test.php @@ -0,0 +1,141 @@ +. + +namespace core\router; + +/** + * Tests for the abstract route loader. + * + * Note: This is an abstract class used as an optional helper for any other route loader. + * All methods on it are protected and testing them requires a concrete implementation. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\abstract_route_loader + */ +final class abstract_route_loader_test extends \advanced_testcase { + /** + * Ensure that the abstract loader does not implement the interface. That would defeat the point. + */ + public function test_abstract_route_loader_does_not_implement_interface(): void { + $reflection = new \ReflectionClass(abstract_route_loader::class); + $this->assertFalse($reflection->implementsInterface(route_loader_interface::class)); + } + + /** + * Test that we can fetch routes in a namespace. + */ + public function test_get_all_routes_in_namespace(): void { + // phpcs:ignore + $loader = new class() extends abstract_route_loader { + // phpcs:ignore + public function get_routes(): array { + return $this->get_all_routes_in_namespace( + 'route\api', + function (string $component): string { + return '/path/to/' . $component; + }, + ); + } + }; + + $routes = $loader->get_routes(); + $this->assertGreaterThan(1, count($routes)); + foreach ($routes as $route) { + $this->assertArrayHasKey('methods', $route); + $this->assertArrayHasKey('pattern', $route); + $this->assertArrayHasKey('callable', $route); + $this->assertStringStartsWith('/path/to/', $route['pattern']); + } + } + + /** + * Test tha the normalise_component_path method works as expected. + * + * @dataProvider normalise_component_path_provider + * @param string $input + * @param string $expected + */ + public function test_normalise_component_path( + string $input, + string $expected, + ): void { + // phpcs:ignore + $loader = new class() extends abstract_route_loader { + // phpcs:ignore + public function method(...$args): string { + return $this->normalise_component_path(...$args); + } + }; + + $this->assertEquals( + $expected, + $loader->method($input), + ); + } + + /** + * Data provider for test_normalise_component_path. + * + * @return array + */ + public static function normalise_component_path_provider(): array { + return [ + ['core', 'core'], + ['core_user', 'user'], + ['mod_forum', 'mod_forum'], + ['', ''], + ]; + } + + /** + * Tests for the set_route_name_for_callable method. + */ + public function test_set_route_name_for_callable(): void { + // phpcs:ignore + $loader = new class() extends abstract_route_loader { + // phpcs:ignore + public function call(...$args): ?string { + return $this->set_route_name_for_callable(...$args); + } + }; + + $route = $this->createMock(\Slim\Routing\Route::class); + $route->expects($this->once()) + ->method('setName') + ->with('routename'); + + $name = $loader->call($route, 'routename'); + $this->assertEquals('routename', $name); + + $route = $this->createMock(\Slim\Routing\Route::class); + $route->expects($this->once()) + ->method('setName') + ->with('class::method'); + + $name = $loader->call($route, ['class', 'method']); + $this->assertEquals('class::method', $name); + + $route = $this->createMock(\Slim\Routing\Route::class); + $route->expects($this->never()) + ->method('setName'); + + $name = $loader->call($route, fn () => ''); + $this->assertEquals(null, $name); + } +} diff --git a/lib/tests/router/apidocs_test.php b/lib/tests/router/apidocs_test.php new file mode 100644 index 00000000000..b36324feeee --- /dev/null +++ b/lib/tests/router/apidocs_test.php @@ -0,0 +1,40 @@ +. + +namespace core\router; + +use GuzzleHttp\Psr7\Response; +use Psr\Http\Message\ResponseInterface; + +/** + * Tests for user preference API handler. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\apidocs + */ +final class apidocs_test extends \advanced_testcase { + public function test_openapi_docs(): void { + $apidocs = new apidocs(); + + $result = $apidocs->openapi_docs(new Response()); + $this->assertInstanceOf(ResponseInterface::class, $result); + + $this->assertTrue($result->hasHeader('Content-Type')); + $this->assertEquals(['application/json'], $result->getHeader('Content-Type')); + } +} diff --git a/lib/tests/router/callable_resolver_test.php b/lib/tests/router/callable_resolver_test.php new file mode 100644 index 00000000000..137e6cec477 --- /dev/null +++ b/lib/tests/router/callable_resolver_test.php @@ -0,0 +1,80 @@ +. + +namespace core\router; + +use core\di; +use core\router\middleware\cors_middleware; +use Invoker\Exception\NotCallableException; + +/** + * Tests for callable resolver. + * + * @package core + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\callable_resolver + */ +final class callable_resolver_test extends \advanced_testcase { + public function test_can_resolve_slim_notation(): void { + $resolver = di::get(callable_resolver::class); + + $result = $resolver->resolve('core\router\apidocs:openapi_docs'); + $this->assertEquals([new apidocs(), 'openapi_docs'], $result); + } + public function test_can_resolve_array(): void { + $resolver = di::get(callable_resolver::class); + + $result = $resolver->resolve([\core\router\apidocs::class, 'openapi_docs']); + $this->assertEquals([new apidocs(), 'openapi_docs'], $result); + } + + public function test_can_resolve_di_notation(): void { + $resolver = di::get(callable_resolver::class); + + $result = $resolver->resolve('core\router\apidocs::openapi_docs'); + $this->assertEquals([new apidocs(), 'openapi_docs'], $result); + } + + public function test_resolve_middleware(): void { + $resolver = di::get(callable_resolver::class); + + $result = $resolver->resolveMiddleware(\core\router\middleware\cors_middleware::class); + $this->assertEquals([new cors_middleware(), 'process'], $result); + } + + public function test_resolve_middleware_not_middleware(): void { + $resolver = di::get(callable_resolver::class); + + $this->expectException(NotCallableException::class); + $resolver->resolveMiddleware('core\router\apidocs'); + } + + public function test_resolve_route(): void { + self::load_fixture('core', 'router/route_implementing_request_handler_interface.php'); + $resolver = di::get(callable_resolver::class); + + $result = $resolver->resolveRoute(\core\router\route_implementing_request_handler_interface::class); + $this->assertEquals([new route_implementing_request_handler_interface(), 'handle'], $result); + } + + public function test_resolve_route_not_route(): void { + $resolver = di::get(callable_resolver::class); + + $this->expectException(NotCallableException::class); + $resolver->resolveRoute(\core\router\apidocs::class); + } +} diff --git a/lib/tests/router/controller_invoker_test.php b/lib/tests/router/controller_invoker_test.php new file mode 100644 index 00000000000..330a92fb93c --- /dev/null +++ b/lib/tests/router/controller_invoker_test.php @@ -0,0 +1,138 @@ +. + +namespace core\router; + +use core\tests\route_testcase; +use GuzzleHttp\Psr7\Response; +use Psr\Http\Message\ServerRequestInterface; + +/** + * Tests for the controller invoker, and related bridge. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\controller_invoker + * @covers \core\router\bridge + * @covers \core\router\response_handler + */ +final class controller_invoker_test extends route_testcase { + /** + * Configure an instance of Slim and fetch the Invoker. + * + * @return \Slim\Interfaces\InvocationStrategyInterface + */ + protected function get_invocation_strategy(): \Slim\Interfaces\InvocationStrategyInterface { + $container = \core\di::get_container(); + bridge::create($container); + $app = $container->get(\Slim\App::class); + return $app->getRouteCollector()->getDefaultInvocationStrategy(); + } + + /** + * Test that setup of the invoker using the router\bridge sets the correct invoker strategy. + * @covers \core\router\bridge + * @covers \core\router\controller_invoker + */ + public function test_setup_of_invoker(): void { + $strategy = $this->get_invocation_strategy(); + $this->assertInstanceOf(controller_invoker::class, $strategy); + } + + public function test_invocation_with_arguments(): void { + $strategy = $this->get_invocation_strategy(); + $testcase = $this; + + // Providing a callable with no args will mean that none are provided. + $callable = function () use ($testcase): Response { + $testcase->assertCount(0, func_get_args()); + return new Response(); + }; + + $request = $this->create_request('GET', '/example'); + $response = new Response(); + $strategy($callable, $request, $response, []); + + // Requesting one Response will get us the Response. + $originalresponse = new Response(); + $callable = function (Response $response) use ($testcase, $originalresponse): Response { + $testcase->assertNotNull($response); + $testcase->assertEquals($originalresponse, $response); + // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue + $testcase->assertCount(1, func_get_args()); + return $response; + }; + + $request = $this->create_request('GET', '/example'); + + $strategy($callable, $request, $originalresponse, []); + + // Requesting the Request will get us the Request. + $serverrequest = $this->create_request('GET', '/example'); + $callable = function (ServerRequestInterface $request) use ($testcase, $serverrequest): Response { + $this->assertEquals($serverrequest, $request); + // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue + $testcase->assertCount(1, func_get_args()); + + return new Response(); + }; + + $strategy($callable, $serverrequest, $response, []); + + // Attributes on the request will be passed through if requested. + $serverrequest = $this->create_request('GET', '/example') + ->withAttribute('example', 'This is an examplar attribute!'); + $callable = function ( + string $example, + ) use ($testcase): Response { + $testcase->assertCount(1, func_get_args()); + $testcase->assertEquals('This is an examplar attribute!', $example); + return new Response(); + }; + + $strategy($callable, $serverrequest, $response, []); + + // Route arguments to request will be passed through if requested. + $serverrequest = $this->create_request('GET', '/example'); + $callable = function ( + string $example, + ) use ($testcase): Response { + $testcase->assertCount(1, func_get_args()); + $testcase->assertEquals('This is an examplar attribute!', $example); + return new Response(); + }; + + $strategy($callable, $serverrequest, $response, [ + 'example' => 'This is an examplar attribute!', + ]); + + // Attributes will be overridden by Route arguments. + $serverrequest = $this->create_request('GET', '/example') + ->withAttribute('example', 'This is an examplar attribute!'); + $callable = function ( + string $example, + ) use ($testcase): Response { + $testcase->assertCount(1, func_get_args()); + $testcase->assertEquals('This is a different examplar attribute!', $example); + return new Response(); + }; + + $strategy($callable, $serverrequest, $response, [ + 'example' => 'This is a different examplar attribute!', + ]); + } +} diff --git a/lib/tests/router/middleware/cors_middleware_test.php b/lib/tests/router/middleware/cors_middleware_test.php new file mode 100644 index 00000000000..eced3647c97 --- /dev/null +++ b/lib/tests/router/middleware/cors_middleware_test.php @@ -0,0 +1,84 @@ +. + +namespace core\router\middleware; + +use core\di; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; +use Psr\Http\Message\ResponseInterface; + +/** + * Tests for the CORS middleware. + * + * @package core + * @category test + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\middleware\cors_middleware + */ +final class cors_middleware_test extends route_testcase { + /** + * Standard CORS headers are added. + */ + public function test_cors_headers(): void { + $app = $this->get_simple_app(); + $app->add(di::get(cors_middleware::class)); + $app->addRoutingMiddleware(); + + $app->map(['GET'], '/test', function ($request, $response) { + return $response; + }); + + // Handle the request. + $request = new ServerRequest('GET', '/test'); + $returns = $app->handle($request); + $this->assertInstanceOf(ResponseInterface::class, $returns); + + // Assert the relevant CORS headers. + $this->assertEquals('*', $returns->getHeaderLine('Access-Control-Allow-Origin')); + $this->assertEquals('GET', $returns->getHeaderLine('Access-Control-Allow-Methods')); + + // Check the allowed headers. + $allowedheaders = $returns->getHeaderLine('Access-Control-Allow-Headers'); + $this->assertStringContainsString('Content-Type', $allowedheaders); + $this->assertStringContainsString('api_key', $allowedheaders); + $this->assertStringContainsString('Authorization', $allowedheaders); + } + + /** + * CORS methods are added for multiple routes matching the same path. + */ + public function test_cors_multiple_methods_headers(): void { + $app = $this->get_simple_app(); + $app->add(di::get(cors_middleware::class)); + $app->addRoutingMiddleware(); + + $app->map(['GET'], '/test', fn ($request, $response) => $response); + $app->map(['POST'], '/test', fn ($request, $response) => $response); + $app->map(['PUT', 'PATCH'], '/test', fn ($request, $response) => $response); + $app->map(['DELETE'], '/test', fn ($request, $response) => $response); + + // Handle the request. + $request = new ServerRequest('GET', '/test'); + $returns = $app->handle($request); + $this->assertInstanceOf(ResponseInterface::class, $returns); + + // Assert the relevant CORS headers. + $this->assertEquals('*', $returns->getHeaderLine('Access-Control-Allow-Origin')); + $this->assertEquals('GET,POST,PUT,PATCH,DELETE', $returns->getHeaderLine('Access-Control-Allow-Methods')); + } +} diff --git a/lib/tests/router/middleware/error_handling_middleware_test.php b/lib/tests/router/middleware/error_handling_middleware_test.php new file mode 100644 index 00000000000..ff9f298e886 --- /dev/null +++ b/lib/tests/router/middleware/error_handling_middleware_test.php @@ -0,0 +1,80 @@ +. + +namespace core\router\middleware; + +use core\di; +use core\router\response_handler; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; +use Psr\Http\Message\ResponseInterface; + +/** + * Tests for the CORS middleware. + * + * @package core + * @category test + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\middleware\error_handling_middleware + */ +final class error_handling_middleware_test extends route_testcase { + /** + * When no errors, the error handle is not called. + */ + public function test_no_errors(): void { + $responsehandler = $this->getMockBuilder(response_handler::class) + ->disableOriginalConstructor() + ->getMock(); + $responsehandler->expects($this->never())->method('get_response_from_exception'); + + di::set(response_handler::class, $responsehandler); + + $app = $this->get_simple_app(); + $app->add(di::get(error_handling_middleware::class)); + + $app->map(['GET'], '/test', fn ($request, $response) => $response); + + // Handle the request. + $request = new ServerRequest('GET', '/test'); + $returns = $app->handle($request); + $this->assertInstanceOf(ResponseInterface::class, $returns); + } + + /** + * When no errors, the error handle is not called. + */ + public function test_error_handling(): void { + $responsehandler = $this->getMockBuilder(response_handler::class) + ->disableOriginalConstructor() + ->getMock(); + $responsehandler->expects($this->once())->method('get_response_from_exception'); + + di::set(response_handler::class, $responsehandler); + + // Configure the app with one middleware that throws an exception. + $app = $this->get_simple_app(); + $app->add(fn ($request, $handler) => throw new \Exception('Test')); + $app->add(di::get(error_handling_middleware::class)); + + $app->map(['GET'], '/test', fn ($request, $response) => $response); + + // Handle the request. + $request = new ServerRequest('GET', '/test'); + $returns = $app->handle($request); + $this->assertInstanceOf(ResponseInterface::class, $returns); + } +} diff --git a/lib/tests/router/middleware/moodle_bootstrap_middleware_test.php b/lib/tests/router/middleware/moodle_bootstrap_middleware_test.php new file mode 100644 index 00000000000..078a5a295ba --- /dev/null +++ b/lib/tests/router/middleware/moodle_bootstrap_middleware_test.php @@ -0,0 +1,50 @@ +. + +namespace core\router\middleware; + +use core\di; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; + +/** + * Tests for the Moodle Bootstrap middleware. + * + * @package core + * @category test + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\middleware\moodle_bootstrap_middleware + */ +final class moodle_bootstrap_middleware_test extends route_testcase { + public function test_set_page_to_uri(): void { + global $PAGE; + $app = $this->get_simple_app(); + $app->add(di::get(moodle_bootstrap_middleware::class)); + $app->addRoutingMiddleware(); + + $app->map(['GET'], '/example', function ($request, $response) { + return $response; + }); + + // Handle the request. + $request = new ServerRequest('GET', '/example'); + $app->handle($request); + + $expect = new \moodle_url('/example'); + $this->assertEquals($expect->out(), $PAGE->url->out()); + } +} diff --git a/lib/tests/router/middleware/moodle_route_attribute_middleware_test.php b/lib/tests/router/middleware/moodle_route_attribute_middleware_test.php new file mode 100644 index 00000000000..3a37fc05402 --- /dev/null +++ b/lib/tests/router/middleware/moodle_route_attribute_middleware_test.php @@ -0,0 +1,103 @@ +. + +namespace core\router\middleware; + +use core\di; +use core\router\route_loader_interface; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; + +/** + * Tests for the Moodle route attribute middleware. + * + * @package core + * @category test + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\middleware\moodle_route_attribute_middleware + */ +final class moodle_route_attribute_middleware_test extends route_testcase { + /** + * Test the Moodle route will be set on a request which has a Moodle route attached. + */ + public function test_has_route(): void { + self::load_fixture('core', '/router/route_on_method_only.php'); + self::load_fixture('core', '/router/route_on_class.php'); + + $this->add_route_to_route_loader( + \core\fixtures\route_on_method_only::class, + 'method_with_route', + grouppath: '', + ); + + $testcase = $this; + + $app = $this->get_simple_app(); + + $app->add(function ($request, $handler) use ($testcase) { + $route = $request->getAttribute(\core\router\route::class); + $testcase->assertInstanceOf(\core\router\route::class, $route); + + return $handler->handle($request); + }); + + $app->add(di::get(moodle_route_attribute_middleware::class)); + $app->addRoutingMiddleware(); + $app->add(function ($request, $handler) use ($testcase) { + $testcase->assertNull($request->getAttribute(\core\router\route::class)); + + return $handler->handle($request); + }); + + di::get(route_loader_interface::class)->configure_routes($app); + + $request = new ServerRequest('GET', '/method/path'); + $app->handle($request); + } + + /** + * Test that no error occurs when no Moodle route is found. + */ + public function test_has_no_route(): void { + self::load_fixture('core', '/router/route_on_method_only.php'); + self::load_fixture('core', '/router/route_on_class.php'); + + $testcase = $this; + + $app = $this->get_simple_app(); + $app->map(['GET'], '/method/path', fn ($request, $response) => $response); + + $app->add(function ($request, $handler) use ($testcase) { + $testcase->assertNull($request->getAttribute(\core\router\route::class)); + + return $handler->handle($request); + }); + + $app->add(di::get(moodle_route_attribute_middleware::class)); + $app->addRoutingMiddleware(); + $app->add(function ($request, $handler) use ($testcase) { + $testcase->assertNull($request->getAttribute(\core\router\route::class)); + + return $handler->handle($request); + }); + + di::get(route_loader_interface::class)->configure_routes($app); + + $request = new ServerRequest('GET', '/method/path'); + $app->handle($request); + } +} diff --git a/lib/tests/router/middleware/uri_normalisation_middleware_test.php b/lib/tests/router/middleware/uri_normalisation_middleware_test.php new file mode 100644 index 00000000000..c6fa1d806ea --- /dev/null +++ b/lib/tests/router/middleware/uri_normalisation_middleware_test.php @@ -0,0 +1,86 @@ +. + +namespace core\router\middleware; +use GuzzleHttp\Psr7\ServerRequest; +use Psr\Http\Server\RequestHandlerInterface; + +/** + * Tests for uri_normalisation_middleware. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\middleware\uri_normalisation_middleware + */ +final class uri_normalisation_middleware_test extends \advanced_testcase { + /** + * Test the normalisation of URIs. + * + * @dataProvider data_provider + * @param string $input The input URI. + * @param string $expected The expected output URI. + */ + public function test_normalisation( + string $input, + string $expected, + ): void { + $request = new ServerRequest('GET', $input); + $handler = new class () implements \Psr\Http\Server\RequestHandlerInterface { + #[\Override] + public function handle(\Psr\Http\Message\ServerRequestInterface $request): \Psr\Http\Message\ResponseInterface { + return new \GuzzleHttp\Psr7\Response(); + } + }; + + $handler = $this->getMockBuilder(RequestHandlerInterface::class)->getMock(); + $handler->expects($this->once()) + ->method('handle') + ->with( + $this->callback(function ($request) use ($expected) { + return $request->getUri()->getPath() === $expected; + }), + ); + + $middleware = \core\di::get(uri_normalisation_middleware::class); + $middleware->process($request, $handler); + } + + /** + * Data provider for test_normalisation. + */ + public static function data_provider(): array { + return [ + 'Empty URI' => [ + '', + '/', + ], + 'Duplicate slashes' => [ + '/test//path', + '/test/path', + ], + 'Trailing slash' => [ + '/test/path/', + '/test/path', + ], + 'Multiple duplicate slashes' => [ + '/test///path//with//more//than//one', + '/test/path/with/more/than/one', + ], + ]; + } +} diff --git a/lib/tests/router/middleware/validation_middleware_test.php b/lib/tests/router/middleware/validation_middleware_test.php new file mode 100644 index 00000000000..07cdf9ef805 --- /dev/null +++ b/lib/tests/router/middleware/validation_middleware_test.php @@ -0,0 +1,140 @@ +. + +namespace core\router\middleware; + +use core\di; +use core\router\request_validator; +use core\router\response_validator; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\ServerRequest; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Server\RequestHandlerInterface; + +/** + * Tests for the validation middleware. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\middleware\validation_middleware + */ +final class validation_middleware_test extends \advanced_testcase { + /** + * If a request fails request validation, the next middleware will not be called. + */ + public function test_process_fails_request_validation(): void { + $request = new ServerRequest('GET', '/test'); + + // Mock the request validator to throw an exception. + $requestvalidator = $this->getMockBuilder(request_validator::class)->getMock(); + $requestvalidator->expects($this->once()) + ->method('validate_request') + ->with($request) + ->willThrowException(new \Exception('Invalid request')); + + // If the request fails validation, it will not be passed to next Middleware. + $handler = $this->getMockBuilder(RequestHandlerInterface::class)->getMock(); + $handler->expects($this->never())->method('handle'); + + // It will never get a response. + $responsevalidator = $this->getMockBuilder(response_validator::class)->getMock(); + $responsevalidator->expects($this->never())->method('validate_response'); + + di::set('core\router\request_validator', $requestvalidator); + di::set('core\router\response_validator', $responsevalidator); + + // Execute the middleware. + $middleware = di::get(validation_middleware::class); + $returns = $middleware->process($request, $handler); + $this->assertInstanceOf(ResponseInterface::class, $returns); + } + + /** + * If a request passes request validation, but fails response validation. + */ + public function test_process_passes_request_validation_fails_response_validation(): void { + $request = new ServerRequest('GET', '/test'); + $response = new Response(); + + // Mock the request validator to throw an exception. + $requestvalidator = $this->getMockBuilder(request_validator::class)->getMock(); + $requestvalidator->expects($this->once()) + ->method('validate_request') + ->with($request) + ->willReturnArgument(0); + + // If the request fails validation, it will not be passed to next Middleware. + $handler = $this->getMockBuilder(RequestHandlerInterface::class)->getMock(); + $handler + ->method('handle') + ->willReturn($response); + + // It will never get a response. + $responsevalidator = $this->getMockBuilder(response_validator::class)->getMock(); + $responsevalidator + ->expects($this->once()) + ->method('validate_response') + ->with($request, $response) + ->willThrowException(new \Exception('Invalid response')); + + di::set('core\router\request_validator', $requestvalidator); + di::set('core\router\response_validator', $responsevalidator); + + // Execute the middleware. + $middleware = di::get(validation_middleware::class); + $returns = $middleware->process($request, $handler); + $this->assertInstanceOf(ResponseInterface::class, $returns); + $this->assertNotEquals($response, $returns); + } + + /** + * If a request passes request validation, the response middleware will be called. + */ + public function test_process_passes_request_validation(): void { + $request = new ServerRequest('GET', '/test'); + $response = new Response(); + + // Mock the request validator to throw an exception. + $requestvalidator = $this->getMockBuilder(request_validator::class)->getMock(); + $requestvalidator->expects($this->once()) + ->method('validate_request') + ->with($request) + ->willReturnArgument(0); + + // If the request fails validation, it will not be passed to next Middleware. + $handler = $this->getMockBuilder(RequestHandlerInterface::class)->getMock(); + $handler->expects($this->once()) + ->method('handle') + ->with($request) + ->willReturn($response); + + // It will never get a response. + $responsevalidator = $this->getMockBuilder(response_validator::class)->getMock(); + $responsevalidator + ->expects($this->once()) + ->method('validate_response') + ->with($request, $response); + + di::set('core\router\request_validator', $requestvalidator); + di::set('core\router\response_validator', $responsevalidator); + + // Execute the middleware. + $middleware = di::get(validation_middleware::class); + $this->assertEquals($response, $middleware->process($request, $handler)); + } +} diff --git a/lib/tests/router/parameters/header_language_test.php b/lib/tests/router/parameters/header_language_test.php new file mode 100644 index 00000000000..a9dfaf653e5 --- /dev/null +++ b/lib/tests/router/parameters/header_language_test.php @@ -0,0 +1,113 @@ +. + +namespace core\router\parameters; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; +use Psr\Http\Message\ServerRequestInterface; + +/** + * Tests for the language header. + * + * @package core + * @category test + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\parameters\header_language + */ +final class header_language_test extends route_testcase { + /** + * Test that the parameter is valid when the component is not specified. + */ + public function test_component_not_specified(): void { + $param = new header_language(); + + $request = new ServerRequest('GET', '/example'); + + $this->assertInstanceOf( + ServerRequestInterface::class, + $param->validate($request), + ); + } + + /** + * Test that the parameter name is respected + */ + public function test_name(): void { + $param = new header_language(name: 'not_the_default_name'); + $this->assertEquals('not_the_default_name', $param->get_name()); + } + + /** + * Test valid components. + * + * @param string $component + * @dataProvider valid_values + */ + public function test_valid_value(string $component): void { + $param = new header_language(); + + /** @var ServerRequestInterface $request */ // phpcs:ignore moodle.Commenting.InlineComment.DocBlock + $request = (new ServerRequest('GET', '/example')) + ->withAddedHeader('Language', $component); + + $this->assertInstanceOf( + ServerRequestInterface::class, + $param->validate($request), + ); + } + + /** + * Test invalid components. + * + * @param string $component + * @dataProvider invalid_values + */ + public function test_invalid_value(string $component): void { + $param = new header_language(); + + /** @var ServerRequestInterface */ // phpcs:ignore moodle.Commenting.InlineComment.DocBlock + $request = (new ServerRequest('GET', '/example')) + ->withAddedHeader('Language', $component); + + $this->expectException(\core\exception\invalid_parameter_exception::class); + $param->validate($request); + } + + /** + * Data provider containing seemingly-valid components. + * + * @return array + */ + public static function valid_values(): array { + return [ + [''], + ['en'], + ]; + } + + /** + * Data provider containing invalid components. + * + * @return array + */ + public static function invalid_values(): array { + return [ + ['de'], + ['Something wrong!!!'], + ]; + } +} diff --git a/lib/tests/router/parameters/path_component_test.php b/lib/tests/router/parameters/path_component_test.php new file mode 100644 index 00000000000..58e550216e2 --- /dev/null +++ b/lib/tests/router/parameters/path_component_test.php @@ -0,0 +1,136 @@ +. + +namespace core\router\parameters; + +use core\tests\route_testcase; +use invalid_parameter_exception; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\ServerRequest; +use Psr\Http\Message\ServerRequestInterface; +use Slim\Routing\RouteContext; + +/** + * Tests for the Component path parameter. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\parameters\path_component + * @covers \core\router\schema\parameters\path_parameter + */ +final class path_component_test extends route_testcase { + /** + * Test that the parameter is valid when the component is not specified. + */ + public function test_component_not_specified(): void { + $param = new path_component(); + + $app = $this->get_simple_app(); + $app->get('/example', fn () => new Response()); + + $request = $this->route_request($app, new ServerRequest('GET', '/example')); + + $context = RouteContext::fromRequest($request); + $route = $context->getRoute(); + + $this->assertInstanceOf( + ServerRequestInterface::class, + $param->validate($request, $route), + ); + } + + /** + * Test that the parameter name is respected + */ + public function test_name(): void { + $param = new path_component(name: 'not_the_default_name'); + $this->assertEquals('not_the_default_name', $param->get_name()); + } + + /** + * Test valid components. + * + * @param string $component + * @dataProvider valid_components + */ + public function test_valid_value(string $component): void { + $param = new path_component(); + + $app = $this->get_simple_app(); + $app->get('/example/{component}', fn () => new Response()); + + $request = $this->route_request($app, new ServerRequest('GET', "/example/{$component}")); + + $context = RouteContext::fromRequest($request); + $route = $context->getRoute(); + + $this->assertInstanceOf( + ServerRequestInterface::class, + $param->validate($request, $route), + ); + } + + /** + * Test invalid components. + * + * @param string $component + * @dataProvider invalid_components + */ + public function test_invalid_value(string $component): void { + $this->resetAfterTest(); + $param = new path_component(); + + $app = $this->get_simple_app(); + $app->get('/example/{component}', fn () => new Response()); + + $request = $this->route_request($app, new ServerRequest('GET', "/example/{$component}")); + + $context = RouteContext::fromRequest($request); + $route = $context->getRoute(); + + $this->expectException(invalid_parameter_exception::class); + $param->validate($request, $route); + } + + /** + * Data provider containing seemingly-valid components. + * + * @return array + */ + public static function valid_components(): array { + return [ + ['core'], + ['core_message'], + ['mod_forum'], + ['assignsubmission_file'], + // Note: This is handled with a regex, not an actual lookup. + ['blueberry_jam'], + ]; + } + + /** + * Data provider containing invalid components. + * + * @return array + */ + public static function invalid_components(): array { + return [ + ['4things_todo'], + ['EASY_AS'], + ]; + } +} diff --git a/lib/tests/router/parameters/path_course_test.php b/lib/tests/router/parameters/path_course_test.php new file mode 100644 index 00000000000..51bd4a23d31 --- /dev/null +++ b/lib/tests/router/parameters/path_course_test.php @@ -0,0 +1,170 @@ +. + +namespace core\router\parameters; + +use core\exception\not_found_exception; +use core\router\schema\referenced_object; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; +use stdClass; + +/** + * Tests for the Course Path paraemter. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\parameter + * @covers \core\router\schema\parameters\path_parameter + * @covers \core\router\parameters\path_course + */ +final class path_course_test extends route_testcase { + public function test_course_id(): void { + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $coursecontext = \core\context\course::instance($course->id); + + $param = new path_course(); + $request = new ServerRequest('GET', '/course/view'); + $newrequest = $param->add_attributes_for_parameter_value($request, $course->id); + + $this->assertInstanceOf(stdClass::class, $newrequest->getAttribute('course')); + $this->assertInstanceOf(\core\context\course::class, $newrequest->getAttribute('coursecontext')); + + $this->assertEquals($course->id, $newrequest->getAttribute('course')->id); + $this->assertEquals($coursecontext->id, $newrequest->getAttribute('coursecontext')->id); + } + + public function test_course_idnumber(): void { + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course((object) [ + 'idnumber' => '000117-physics-101-1', + ]); + $coursecontext = \core\context\course::instance($course->id); + + $param = new path_course(); + $request = new ServerRequest('GET', '/course/view'); + $newrequest = $param->add_attributes_for_parameter_value($request, "idnumber:{$course->idnumber}"); + + $this->assertInstanceOf(stdClass::class, $newrequest->getAttribute('course')); + $this->assertInstanceOf(\core\context\course::class, $newrequest->getAttribute('coursecontext')); + + $this->assertEquals($course->id, $newrequest->getAttribute('course')->id); + $this->assertEquals($coursecontext->id, $newrequest->getAttribute('coursecontext')->id); + } + + public function test_course_name(): void { + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $coursecontext = \core\context\course::instance($course->id); + + $param = new path_course(); + $request = new ServerRequest('GET', '/course/view'); + $newrequest = $param->add_attributes_for_parameter_value($request, "name:{$course->shortname}"); + + $this->assertInstanceOf(stdClass::class, $newrequest->getAttribute('course')); + $this->assertInstanceOf(\core\context\course::class, $newrequest->getAttribute('coursecontext')); + + $this->assertEquals($course->id, $newrequest->getAttribute('course')->id); + $this->assertEquals($coursecontext->id, $newrequest->getAttribute('coursecontext')->id); + } + + public function test_validation(): void { + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $coursecontext = \core\context\course::instance($course->id); + + $request = $this->create_route( + '/course/view/{course}', + "/course/view/name:{$course->shortname}", + ); + $route = $this->get_slim_route_from_request($request); + + $param = new path_course(); + $newrequest = $param->validate($request, $route); + + $this->assertInstanceOf(stdClass::class, $newrequest->getAttribute('course')); + $this->assertInstanceOf(\core\context\course::class, $newrequest->getAttribute('coursecontext')); + + $this->assertEquals($course->id, $newrequest->getAttribute('course')->id); + $this->assertEquals($coursecontext->id, $newrequest->getAttribute('coursecontext')->id); + } + + /** + * Tests for when a course was not found. + * + * @dataProvider invalid_course_provider + * @param string $searchkey + */ + public function test_course_not_found(string $searchkey): void { + $param = new path_course(); + $request = new ServerRequest('GET', '/course/view'); + + $this->expectException(not_found_exception::class); + $param->add_attributes_for_parameter_value($request, $searchkey); + } + + /** + * Data provider for test_course_not_found. + */ + public static function invalid_course_provider(): array { + return [ + 'id' => ['999999'], + 'idnumber' => ['idnumber:999999'], + 'name' => ['name:999999'], + 'random string' => ['ksdjflajsdfkjaf:jkladjg9pomadbs902po3'], + ]; + } + + public function test_schema(): void { + $param = new path_course(); + $api = new \core\router\schema\specification(); + $api->add_component($param); + $result = $param->get_openapi_schema($api); + + // Should be a reference. + $this->assertInstanceOf(referenced_object::class, $param); + + $schema = $this->get_api_component_schema($api, $param); + $this->assertIsObject($schema); + $this->assertIsObject($schema->schema); + $this->assertObjectHasProperty('pattern', $schema->schema); + + // We do provide some examples here. Make sure they're valid per the regexp. + $this->assertIsArray($schema->examples); + foreach ($schema->examples as $example) { + $this->assertMatchesRegularExpression("/{$schema->schema->pattern}/", $example->value); + } + + // Some deliberately invalid ones. + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'id'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'id:1'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'id;1'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'idnumber'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'idnumber:'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'idnumber;12344'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'name'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'name:'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'name;12345'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'shortname'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'shortname:12345'); + } +} diff --git a/lib/tests/router/parameters/path_themename_test.php b/lib/tests/router/parameters/path_themename_test.php new file mode 100644 index 00000000000..fb155f0fc3b --- /dev/null +++ b/lib/tests/router/parameters/path_themename_test.php @@ -0,0 +1,136 @@ +. + +namespace core\router\parameters; + +use core\tests\route_testcase; +use invalid_parameter_exception; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\ServerRequest; +use Psr\Http\Message\ServerRequestInterface; +use Slim\Routing\RouteContext; + +/** + * Tests for the Theme name path parameter. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\parameters\path_themename + * @covers \core\router\schema\parameters\path_parameter + */ +final class path_themename_test extends route_testcase { + /** + * Test that the parameter is valid when the themename is not specified. + */ + public function test_themename_not_specified(): void { + $param = new path_themename(); + + $app = $this->get_simple_app(); + $app->get('/example', fn () => new Response()); + + $request = $this->route_request($app, new ServerRequest('GET', '/example')); + + $context = RouteContext::fromRequest($request); + $route = $context->getRoute(); + + $this->assertInstanceOf( + ServerRequestInterface::class, + $param->validate($request, $route), + ); + } + + /** + * Test that the parameter name is respected + */ + public function test_name(): void { + $param = new path_themename(name: 'not_the_default_name'); + $this->assertEquals('not_the_default_name', $param->get_name()); + } + + /** + * Test valid themenames. + * + * @param string $themename + * @dataProvider valid_themenames + */ + public function test_valid_value(string $themename): void { + $param = new path_themename(); + + $app = $this->get_simple_app(); + $app->get('/example/{themename}', fn () => new Response()); + + $request = $this->route_request($app, new ServerRequest('GET', "/example/{$themename}")); + + $context = RouteContext::fromRequest($request); + $route = $context->getRoute(); + + $this->assertInstanceOf( + ServerRequestInterface::class, + $param->validate($request, $route), + ); + } + + /** + * Test invalid themenames. + * + * @param string $themename + * @dataProvider invalid_themenames + */ + public function test_invalid_value(string $themename): void { + $this->resetAfterTest(); + $param = new path_themename(); + + $app = $this->get_simple_app(); + $app->get('/example/{themename}', fn () => new Response()); + + $request = $this->route_request($app, new ServerRequest('GET', "/example/{$themename}")); + $context = RouteContext::fromRequest($request); + $route = $context->getRoute(); + + $this->expectException(invalid_parameter_exception::class); + $param->validate($request, $route); + } + + /** + * Data provider containing seemingly-valid themenames. + * + * @return array + */ + public static function valid_themenames(): array { + return [ + // Note: This is handled with a regex, not an actual lookup. + ['boost'], + ['classic'], + ['blueberry_jam'], + ['abc-def'], + ['1theme'], + ['UPPERCASE'], + + ]; + } + + /** + * Data provider containing invalid themenames. + * + * @return array + */ + public static function invalid_themenames(): array { + return [ + ['r|r'], + ]; + } +} diff --git a/lib/tests/router/parameters/path_user_test.php b/lib/tests/router/parameters/path_user_test.php new file mode 100644 index 00000000000..6045d415989 --- /dev/null +++ b/lib/tests/router/parameters/path_user_test.php @@ -0,0 +1,194 @@ +. + +namespace core\router\parameters; + +use core\exception\not_found_exception; +use core\router\schema\referenced_object; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; +use stdClass; + +/** + * Tests for the User Path parameter. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\parameters\path_user + */ +final class path_user_test extends route_testcase { + public function test_current_user(): void { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + $context = \core\context\user::instance($user->id); + + $param = new path_user(); + $newrequest = $param->add_attributes_for_parameter_value( + new ServerRequest('GET', '/user'), + 'current', + ); + + $this->assertInstanceOf(stdClass::class, $newrequest->getAttribute('user')); + $this->assertInstanceOf(\core\context\user::class, $newrequest->getAttribute('usercontext')); + + $this->assertEquals($user->id, $newrequest->getAttribute('user')->id); + $this->assertEquals($context->id, $newrequest->getAttribute('usercontext')->id); + } + + public function test_user_id(): void { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $context = \core\context\user::instance($user->id); + + $param = new path_user(); + $newrequest = $param->add_attributes_for_parameter_value( + new ServerRequest('GET', '/user'), + $user->id, + ); + + $this->assertInstanceOf(stdClass::class, $newrequest->getAttribute('user')); + $this->assertInstanceOf(\core\context\user::class, $newrequest->getAttribute('usercontext')); + + $this->assertEquals($user->id, $newrequest->getAttribute('user')->id); + $this->assertEquals($context->id, $newrequest->getAttribute('usercontext')->id); + } + + public function test_user_idnumber(): void { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user((object) [ + 'idnumber' => '000117-user', + ]); + $context = \core\context\user::instance($user->id); + + $param = new path_user(); + $newrequest = $param->add_attributes_for_parameter_value( + new ServerRequest('GET', '/user'), + "idnumber:{$user->idnumber}", + ); + + $this->assertInstanceOf(stdClass::class, $newrequest->getAttribute('user')); + $this->assertInstanceOf(\core\context\user::class, $newrequest->getAttribute('usercontext')); + + $this->assertEquals($user->id, $newrequest->getAttribute('user')->id); + $this->assertEquals($context->id, $newrequest->getAttribute('usercontext')->id); + } + + public function test_username(): void { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $context = \core\context\user::instance($user->id); + + $param = new path_user(); + $newrequest = $param->add_attributes_for_parameter_value( + new ServerRequest('GET', '/user'), + "username:{$user->username}", + ); + + $this->assertInstanceOf(stdClass::class, $newrequest->getAttribute('user')); + $this->assertInstanceOf(\core\context\user::class, $newrequest->getAttribute('usercontext')); + + $this->assertEquals($user->id, $newrequest->getAttribute('user')->id); + $this->assertEquals($context->id, $newrequest->getAttribute('usercontext')->id); + } + + public function test_validation(): void { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $context = \core\context\user::instance($user->id); + + $request = $this->create_route( + '/user/{user}', + "/user/username:{$user->username}", + ); + $route = $this->get_slim_route_from_request($request); + + $param = new path_user(); + $newrequest = $param->validate($request, $route); + + $this->assertInstanceOf(stdClass::class, $newrequest->getAttribute('user')); + $this->assertInstanceOf(\core\context\user::class, $newrequest->getAttribute('usercontext')); + + $this->assertEquals($user->id, $newrequest->getAttribute('user')->id); + $this->assertEquals($context->id, $newrequest->getAttribute('usercontext')->id); + } + + /** + * Tests for when a course was not found. + * + * @dataProvider invalid_course_provider + * @param string $searchkey + */ + public function test_course_not_found(string $searchkey): void { + $param = new path_user(); + $request = new ServerRequest('GET', '/user'); + + $this->expectException(not_found_exception::class); + $param->add_attributes_for_parameter_value($request, $searchkey); + } + + /** + * Data provider for test_course_not_found. + */ + public static function invalid_course_provider(): array { + return [ + 'id' => ['999999'], + 'idnumber' => ['idnumber:999999'], + 'name' => ['username:999999'], + 'random string' => ['ksdjflajsdfkjaf:jkladjg9pomadbs902po3'], + ]; + } + + public function test_schema(): void { + $param = new path_user(); + $api = new \core\router\schema\specification(); + $api->add_component($param); + $result = $param->get_openapi_schema($api); + + // Should be a reference. + $this->assertInstanceOf(referenced_object::class, $param); + + $schema = $this->get_api_component_schema($api, $param); + $this->assertIsObject($schema); + $this->assertIsObject($schema->schema); + $this->assertObjectHasProperty('pattern', $schema->schema); + + // We do provide some examples here. Make sure they're valid per the regexp. + $this->assertIsArray($schema->examples); + foreach ($schema->examples as $example) { + $this->assertMatchesRegularExpression("/{$schema->schema->pattern}/", $example->value); + } + + // Some deliberately invalid ones. + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'id'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'id:1'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'id;1'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'idnumber'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'idnumber:'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'idnumber;12344'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'name'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'name:'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'name;12345'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'shortname'); + $this->assertDoesNotMatchRegularExpression("/{$schema->schema->pattern}/", 'shortname:12345'); + } +} diff --git a/lib/tests/router/request_validator_test.php b/lib/tests/router/request_validator_test.php new file mode 100644 index 00000000000..49a8498b3d3 --- /dev/null +++ b/lib/tests/router/request_validator_test.php @@ -0,0 +1,391 @@ +. + +namespace core\router; + +use core\param; +use core\router\schema\parameters\path_parameter; +use core\router\schema\parameters\query_parameter; +use core\router\schema\request_body; +use core\router\schema\response\content\payload_response_type; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; +use Psr\Http\Message\ServerRequestInterface; +use Slim\Exception\HttpNotFoundException; + +/** + * Tests for the request validator. + * + * @package core + * @category test + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\request_validator + */ +final class request_validator_test extends route_testcase { + /** + * Request validation on a route which does not have a matching Moodle route attribute. + */ + public function test_validate_request_not_moodle_route(): void { + $request = new ServerRequest('GET', '/example'); + $validator = \core\di::get(request_validator::class); + + $this->assertInstanceOf( + ServerRequestInterface::class, + $validator->validate_request($request), + ); + } + + /** + * A basic test of request validation. + */ + public function test_validate_request(): void { + // The route being tested. + $route = new route( + path: '/example/{required}', + pathtypes: [ + new path_parameter( + name: 'required', + type: param::INT, + ), + ], + ); + $request = $this->get_request_for_routed_route($route, '/example/123'); + $validator = \core\di::get(request_validator::class); + + $this->assertInstanceOf( + ServerRequestInterface::class, + $validator->validate_request($request), + ); + } + + /** + * A basic test of request validation. + */ + public function test_validate_request_missing_pathtype(): void { + // A route with a parameter defined in the path, but no pathtype for it. + $route = new route( + path: '/example/{required}', + ); + + $request = $this->get_request_for_routed_route($route, '/example/123'); + + $validator = \core\di::get(request_validator::class); + $this->assertInstanceOf( + ServerRequestInterface::class, + $validator->validate_request($request), + ); + } + + /** + * When a defined pathtype is missing from the path. + */ + public function test_validate_request_missing_path_component(): void { + // A route with a parameter defined in the path, but no pathtype for it. + $route = new route( + path: '/example/123', + pathtypes: [ + new path_parameter( + name: 'required', + type: param::INT, + ), + ], + ); + + $request = $this->get_request_for_routed_route($route, '/example/123'); + + $validator = \core\di::get(request_validator::class); + $this->expectException(\coding_exception::class); + $this->expectExceptionMessageMatches('/Route.*has 0 arguments.* 1 pathtypes./'); + $result = $validator->validate_request($request); + + $this->assertInstanceOf( + ServerRequestInterface::class, + $result, + ); + } + + /** + * When a pathtype fails to validate, it will result in an HttpNotFoundException. + */ + public function test_validate_request_invalid_path_component(): void { + $type = param::INT; + + $route = new route( + path: '/example/{required}', + pathtypes: [ + new path_parameter( + name: 'required', + type: $type, + ), + ], + ); + + $request = $this->get_request_for_routed_route($route, '/example/abc'); + + $validator = \core\di::get(request_validator::class); + $this->expectException(HttpNotFoundException::class); + $validator->validate_request($request); + } + + /** + * When a pathtype fails to validate, it will result in an HttpNotFoundException. + */ + public function test_validate_request_invalid_path_component_native(): void { + $type = param::ALPHA; + + $route = new route( + path: '/example/{required}', + pathtypes: [ + new path_parameter( + name: 'required', + type: $type, + ), + ], + ); + + // A value which does not meet the param validation. + $request = $this->get_request_for_routed_route($route, '/example/123'); + + $validator = \core\di::get(request_validator::class); + $this->expectException(HttpNotFoundException::class); + $validator->validate_request($request); + } + + /** + * Query parameter validation. + */ + public function test_validate_request_query_parameter_valid(): void { + $type = param::INT; + $value = 123; + + $route = new route( + path: '/example', + queryparams: [ + new query_parameter( + name: 'required', + type: $type, + ), + ], + ); + + $request = $this->get_request_for_routed_route($route, "/example?required={$value}"); + $this->assertEquals($value, $request->getQueryParams()['required']); + + // Validate the request. + $validator = \core\di::get(request_validator::class); + $validatedrequest = $validator->validate_request($request); + $this->assertInstanceOf(ServerRequestInterface::class, $validatedrequest); + $this->assertEquals($value, $validatedrequest->getQueryParams()['required']); + } + + /** + * Query parameter validation failure. + */ + public function test_validate_request_query_parameter_invalid(): void { + $type = param::INT; + $value = 'abc'; + + $route = new route( + path: '/example', + queryparams: [ + new query_parameter( + name: 'required', + type: $type, + ), + ], + ); + + $request = $this->get_request_for_routed_route($route, "/example?required={$value}"); + $this->assertEquals($value, $request->getQueryParams()['required']); + + // Validate the request. + $validator = \core\di::get(request_validator::class); + $this->expectException(\invalid_parameter_exception::class); + $validator->validate_request($request); + } + + /** + * Validate a request body which is expected. + */ + public function test_validate_request_body_valid(): void { + $route = new route( + path: '/example', + requestbody: new request_body( + content: new payload_response_type( + schema: new \core\router\schema\objects\schema_object( + content: [ + 'preferences' => new \core\router\schema\objects\array_of_strings( + keyparamtype: param::TEXT, + valueparamtype: param::INT, + ), + ], + ), + ), + ), + ); + + $request = $this->get_request_for_routed_route($route, "/example"); + $request = $request->withParsedBody([ + 'preferences' => [ + 'key' => 42, + ], + ]); + + // Validate the request. + $validator = \core\di::get(request_validator::class); + $result = $validator->validate_request($request); + } + + /** + * Validate a request body which is expected. + */ + public function test_validate_request_body_invalid(): void { + $route = new route( + path: '/example', + requestbody: new request_body( + content: new payload_response_type( + schema: new \core\router\schema\objects\schema_object( + content: [ + 'preferences' => new \core\router\schema\objects\array_of_strings( + keyparamtype: param::TEXT, + valueparamtype: param::INT, + ), + ], + ), + ), + ), + ); + + $request = $this->get_request_for_routed_route($route, "/example"); + $request = $request->withParsedBody([ + 'preferences' => [ + 'key' => 'value', + ], + ]); + + // Validate the request. + $validator = \core\di::get(request_validator::class); + $this->expectException(\invalid_parameter_exception::class); + $validator->validate_request($request); + } + + /** + * Validate a request body which is optional. + */ + public function test_validate_request_body_missing_optional(): void { + $route = new route( + path: '/example', + requestbody: new request_body( + content: new payload_response_type( + schema: new \core\router\schema\objects\schema_object( + content: [ + 'preferences' => new \core\router\schema\objects\array_of_strings( + keyparamtype: param::TEXT, + valueparamtype: param::INT, + ), + ], + required: false, + ), + ), + ), + ); + + $request = $this->get_request_for_routed_route($route, "/example"); + + // Validate the request. + $validator = \core\di::get(request_validator::class); + $result = $validator->validate_request($request); + $this->assertInstanceOf(ServerRequestInterface::class, $result); + $this->assertInstanceOf( + ServerRequestInterface::class, + $result, + ); + } + + /** + * Validate a request body which is expected. + */ + public function test_validate_request_body_missing_required(): void { + $route = new route( + path: '/example', + requestbody: new request_body( + content: new payload_response_type( + schema: new \core\router\schema\objects\schema_object( + content: [ + 'preferences' => new \core\router\schema\objects\array_of_strings( + keyparamtype: param::TEXT, + valueparamtype: param::INT, + ), + ], + ), + ), + required: true, + ), + ); + + $request = $this->get_request_for_routed_route($route, "/example"); + + // Validate the request. + $validator = \core\di::get(request_validator::class); + $this->expectException(\invalid_parameter_exception::class); + $validator->validate_request($request); + } + + /** + * Validate a request header is appropriately handled. + */ + public function test_validate_request_header_valid(): void { + $route = new route( + path: '/example', + headerparams: [ + new \core\router\schema\parameters\header_object( + name: 'Accept', + description: 'The media type of the response', + type: param::TEXT, + ), + new \core\router\schema\parameters\header_object( + name: 'X-Multiple', + description: 'A header with multiple values', + type: param::TEXT, + multiple: true, + ), + ], + ); + + $request = $this->get_request_for_routed_route($route, "/example") + // A known header. + ->withHeader('Accept', 'application/json') + // An unknown header is kept. + ->withHeader('X-Example', 'example') + // A known header with multiple values. + ->withAddedHeader('X-Multiple', 'value1') + ->withAddedHeader('X-Multiple', 'value2') + // An unknown header with multiple values. + ->withAddedHeader('X-Unknown', 'value1') + ->withAddedHeader('X-Unknown', 'value2'); + + // Validate the request. + $validator = \core\di::get(request_validator::class); + $result = $validator->validate_request($request); + $this->assertInstanceOf(ServerRequestInterface::class, $result); + + $this->assertEquals('application/json', $result->getHeaderLine('Accept')); + $this->assertEquals('example', $result->getHeaderLine('X-Example')); + $this->assertEquals(['value1', 'value2'], $result->getHeader('X-Multiple')); + $this->assertEquals(['value1', 'value2'], $result->getHeader('X-Unknown')); + } +} diff --git a/lib/tests/router/response/access_denied_response_test.php b/lib/tests/router/response/access_denied_response_test.php new file mode 100644 index 00000000000..958a0d96035 --- /dev/null +++ b/lib/tests/router/response/access_denied_response_test.php @@ -0,0 +1,70 @@ +. + +namespace core\router\response; + +use core\exception\access_denied_exception; +use core\router\schema\response\payload_response; +use core\router\schema\specification; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; + +/** + * Tests for the access denied response. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\response\exception_response + * @covers \core\router\response\access_denied_response + */ +final class access_denied_response_test extends route_testcase { + public function test_basics(): void { + $this->assertIsInt(access_denied_response::get_exception_status_code()); + $this->assertEquals(403, access_denied_response::get_exception_status_code()); + } + + public function test_get_response(): void { + $exception = new access_denied_exception('the thing', 'theidentifier'); + $request = new ServerRequest('GET', '/example'); + + $payload = access_denied_response::get_response($request, $exception); + $this->assertInstanceOf(payload_response::class, $payload); + + $response = $payload->get_response($this->get_router()->get_response_factory()); + $this->assertInstanceOf(\Psr\Http\Message\ResponseInterface::class, $response); + $this->assertEquals(403, $response->getStatusCode()); + $content = (string) $response->getBody(); + + $this->assertStringContainsString( + 'the thing', + $content, + ); + $this->assertStringContainsString( + 'theidentifier', + $content, + ); + } + + public function test_openapi_description(): void { + $response = new access_denied_response(); + $openapi = $response->get_openapi_description(new specification()); + + // The OpenAPI description should be present. + // Note: We do not need to test the value of it. Doing so just reduces maintainability. + $this->assertIsString($openapi->description); + } +} diff --git a/lib/tests/router/response/empty_response_test.php b/lib/tests/router/response/empty_response_test.php new file mode 100644 index 00000000000..28738f52709 --- /dev/null +++ b/lib/tests/router/response/empty_response_test.php @@ -0,0 +1,48 @@ +. + +namespace core\router\response; + +use core\exception\access_denied_exception; +use core\router\schema\response\payload_response; +use core\router\schema\specification; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; + +/** + * Tests for the access denied response. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\response\empty_response + */ +final class empty_response_test extends route_testcase { + public function test_basics(): void { + $response = new empty_response(); + $this->assertIsInt($response->get_status_code()); + $this->assertEquals(204, $response->get_status_code()); + } + + public function test_openapi_description(): void { + $response = new empty_response(); + $openapi = $response->get_openapi_description(new specification()); + + // The OpenAPI description should be present. + // Note: We do not need to test the value of it. Doing so just reduces maintainability. + $this->assertIsString($openapi->description); + } +} diff --git a/lib/tests/router/response/exception_response_test.php b/lib/tests/router/response/exception_response_test.php new file mode 100644 index 00000000000..17abbab27cd --- /dev/null +++ b/lib/tests/router/response/exception_response_test.php @@ -0,0 +1,44 @@ +. + +namespace core\router\response; + +use core\tests\route_testcase; + +/** + * Tests for the access denied response. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\response\exception_response + */ +final class exception_response_test extends route_testcase { + public function test_basics(): void { + $instance = new class extends exception_response { // phpcs:ignore + #[\Override] + protected static function get_response_description(): string { + return 'Access was denied to the resource.'; + } + }; + + $rc = new \ReflectionClass($instance); + $rcm = new \ReflectionMethod($instance, 'get_exception_status_code'); + + $this->assertIsInt($rcm->invoke(null)); + $this->assertEquals(500, $rcm->invoke(null)); + } +} diff --git a/lib/tests/router/response/invalid_parameter_response_test.php b/lib/tests/router/response/invalid_parameter_response_test.php new file mode 100644 index 00000000000..848d0d483be --- /dev/null +++ b/lib/tests/router/response/invalid_parameter_response_test.php @@ -0,0 +1,65 @@ +. + +namespace core\router\response; + +use core\router\schema\response\payload_response; +use core\router\schema\specification; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; +use invalid_parameter_exception; + +/** + * Tests for the path parameter. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\response\exception_response + * @covers \core\router\response\invalid_parameter_response + */ +final class invalid_parameter_response_test extends route_testcase { + public function test_basics(): void { + $this->assertIsInt(invalid_parameter_response::get_exception_status_code()); + $this->assertEquals(400, invalid_parameter_response::get_exception_status_code()); + } + + public function test_get_response(): void { + $exception = new invalid_parameter_exception('Someone made a booboo'); + $request = new ServerRequest('GET', '/example'); + + $payload = invalid_parameter_response::get_response($request, $exception); + $this->assertInstanceOf(payload_response::class, $payload); + + $response = $payload->get_response($this->get_router()->get_response_factory()); + $this->assertInstanceOf(\Psr\Http\Message\ResponseInterface::class, $response); + $this->assertEquals(400, $response->getStatusCode()); + + $this->assertStringContainsString( + 'Someone made a booboo', + (string) $response->getBody(), + ); + } + + public function test_openapi_description(): void { + $response = new invalid_parameter_response(); + $openapi = $response->get_openapi_description(new specification()); + + // The OpenAPI description should be present. + // Note: We do not need to test the value of it. Doing so just reduces maintainability. + $this->assertIsString($openapi->description); + } +} diff --git a/lib/tests/router/response/not_found_response_test.php b/lib/tests/router/response/not_found_response_test.php new file mode 100644 index 00000000000..5726e41fd3f --- /dev/null +++ b/lib/tests/router/response/not_found_response_test.php @@ -0,0 +1,70 @@ +. + +namespace core\router\response; + +use core\exception\not_found_exception; +use core\router\schema\response\payload_response; +use core\router\schema\specification; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; + +/** + * Tests for the path parameter. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\response\exception_response + * @covers \core\router\response\not_found_response + */ +final class not_found_response_test extends route_testcase { + public function test_basics(): void { + $this->assertIsInt(not_found_response::get_exception_status_code()); + $this->assertEquals(404, not_found_response::get_exception_status_code()); + } + + public function test_get_response(): void { + $exception = new not_found_exception('the thing', 'theidentifier'); + $request = new ServerRequest('GET', '/example'); + + $payload = not_found_response::get_response($request, $exception); + $this->assertInstanceOf(payload_response::class, $payload); + + $response = $payload->get_response($this->get_router()->get_response_factory()); + $this->assertInstanceOf(\Psr\Http\Message\ResponseInterface::class, $response); + $this->assertEquals(404, $response->getStatusCode()); + $content = (string) $response->getBody(); + + $this->assertStringContainsString( + 'the thing', + $content, + ); + $this->assertStringContainsString( + 'theidentifier', + $content, + ); + } + + public function test_openapi_description(): void { + $response = new not_found_response(); + $openapi = $response->get_openapi_description(new specification()); + + // The OpenAPI description should be present. + // Note: We do not need to test the value of it. Doing so just reduces maintainability. + $this->assertIsString($openapi->description); + } +} diff --git a/lib/tests/router/response_handler_test.php b/lib/tests/router/response_handler_test.php new file mode 100644 index 00000000000..1e300428302 --- /dev/null +++ b/lib/tests/router/response_handler_test.php @@ -0,0 +1,195 @@ +. + +namespace core\router; + +use core\di; +use core\exception\access_denied_exception; +use core\exception\response_aware_exception; +use core\router\schema\response\payload_response; +use core\router\schema\response\view_response; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\ServerRequest; + +/** + * Tests for \core\router\response_handler. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\response_handler + */ +final class response_handler_test extends \advanced_testcase { + public function test_standardise_response_from_response(): void { + $response = new Response(); + + $handler = di::get(response_handler::class); + + $result = $handler->standardise_response($response); + $this->assertEquals($response, $result); + } + + public function test_standardise_response_from_payload_response(): void { + $request = new ServerRequest('GET', 'http://example.com'); + $payload = new payload_response(['key' => 'value'], $request); + + $handler = di::get(response_handler::class); + + $result = $handler->standardise_response($payload); + $this->assertInstanceOf(Response::class, $result); + + // The body should be json and contain the same data. + $value = json_decode($result->getBody()); + $this->assertSame( + ['key' => 'value'], + (array) $value, + ); + + // The content type should be application/json. + $this->assertStringContainsString('application/json', $result->getHeaderLine('Content-Type')); + + // The status code should be 200. + $this->assertEquals(200, $result->getStatusCode()); + } + + public function test_standardise_response_from_payload_response_and_response(): void { + $request = new ServerRequest('GET', 'http://example.com'); + $response = new Response(); + + // Add some test headers. + $response = $response->withAddedHeader('Content-Type', 'text/plain') + ->withAddedHeader('X-Example', 'example-value'); + + $payload = new payload_response(['key' => 'value'], $request, $response); + + $handler = di::get(response_handler::class); + + $result = $handler->standardise_response($payload); + $this->assertInstanceOf(Response::class, $result); + + // The body should be json and contain the same data. + $value = json_decode($result->getBody()); + $this->assertSame( + ['key' => 'value'], + (array) $value, + ); + + // The content type should be application/json and the text/plain header should have been replaced. + $this->assertStringContainsString('application/json', $result->getHeaderLine('Content-Type')); + + // The status code should be 200. + $this->assertEquals(200, $result->getStatusCode()); + + // The X-Example header should be present. + $this->assertEquals('example-value', $result->getHeaderLine('X-Example')); + } + + public function test_standardise_response_from_view_response(): void { + global $OUTPUT; + + $request = new ServerRequest('GET', 'http://example.com'); + + // Add some test headers. + $initialresponse = new Response(); + $initialresponse = $initialresponse->withAddedHeader('Content-Type', 'application/json') + ->withAddedHeader('X-Example', 'example-value'); + + $response = new view_response( + template: 'core/welcome', + parameters: [ + 'welcomemessage' => 'Hello, everybody!', + ], + request: $request, + response: $initialresponse, + ); + + $handler = di::get(response_handler::class); + + $result = $handler->standardise_response($response); + $this->assertInstanceOf(Response::class, $result); + + // The content type should be application/json and the text/plain header should have been replaced. + $this->assertStringContainsString('text/html', $result->getHeaderLine('Content-Type')); + + // The status code should be 200. + $this->assertEquals(200, $result->getStatusCode()); + + // The X-Example header should be present. + $this->assertEquals('example-value', $result->getHeaderLine('X-Example')); + + $body = (string) $result->getBody(); + $this->assertStringContainsString('Hello, everybody!', $body); + $this->assertEquals( + $OUTPUT->render_from_template('core/welcome', ['welcomemessage' => 'Hello, everybody!']), + $body, + ); + } + + /** + * Test that the response handler can get a response from an exception. + */ + public function test_get_response_from_exception(): void { + $request = new ServerRequest('GET', 'http://example.com'); + $exception = new \Exception('Test exception'); + + $handler = di::get(response_handler::class); + + $result = $handler->get_response_from_exception($request, $exception); + $this->assertInstanceOf(Response::class, $result); + + // The body should be json and contain the exception message. + $value = json_decode($result->getBody(), true); + $this->assertArrayHasKey('message', (array) $value); + $this->assertArrayHasKey('stacktrace', (array) $value); + + $this->assertEquals( + 'Test exception', + $value['message'], + ); + + // The content type should be application/json. + $this->assertStringContainsString('application/json', $result->getHeaderLine('Content-Type')); + + // The status code should be 500. + $this->assertEquals(500, $result->getStatusCode()); + } + + /** + * Test that the response handler can get a response from an exception. + */ + public function test_get_response_from_response_aware_exception(): void { + $request = new ServerRequest('GET', 'http://example.com'); + + $exception = new access_denied_exception('Test exception'); + $handler = di::get(response_handler::class); + + $result = $handler->get_response_from_exception($request, $exception); + $this->assertInstanceOf(Response::class, $result); + + // The body should be json and contain the exception message. + $value = json_decode($result->getBody(), true); + $this->assertArrayHasKey('message', (array) $value); + $this->assertArrayHasKey('stacktrace', (array) $value); + + // The content type should be application/json. + $this->assertStringContainsString('application/json', $result->getHeaderLine('Content-Type')); + + // The status code should be 500. + $this->assertEquals(403, $result->getStatusCode()); + } +} diff --git a/lib/tests/router/response_validator_test.php b/lib/tests/router/response_validator_test.php new file mode 100644 index 00000000000..0570fb4552f --- /dev/null +++ b/lib/tests/router/response_validator_test.php @@ -0,0 +1,80 @@ +. + +namespace core\router; + +use core\router\schema\response\response; +use GuzzleHttp\Psr7\ServerRequest; +use Psr\Http\Message\ResponseInterface; + +/** + * Tests for the response_validator. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\response_validator + */ +final class response_validator_test extends \advanced_testcase { + public function test_validate_response_without_moodle_route(): void { + $validator = new response_validator(); + + $request = new ServerRequest('GET', 'http://example.com'); + $response = new \GuzzleHttp\Psr7\Response(); + $this->assertNull($validator->validate_response($request, $response)); + } + + public function test_validate_response_with_route_no_responses(): void { + $validator = new response_validator(); + + $route = new route(path: '/test'); + $request = (new ServerRequest('GET', 'http://example.com/test')) + ->withAttribute(route::class, $route); + $response = new \GuzzleHttp\Psr7\Response(); + $this->assertNull($validator->validate_response($request, $response)); + } + + public function test_validate_response_with_route_and_response(): void { + $validator = new response_validator(); + + $routeresponse = new class extends response { + // phpcs:ignore + public function __construct() { + parent::__construct( + statuscode: 200, + description: 'Test response', + ); + } + + // phpcs:ignore + public function validate( + ResponseInterface $response, + ): void { + throw new \Exception('Test exception'); + } + }; + + $route = new route(path: '/test', responses: [200 => $routeresponse]); + $request = (new ServerRequest('GET', 'http://example.com/test')) + ->withAttribute(route::class, $route); + $response = new \GuzzleHttp\Psr7\Response(); + + $this->expectException(\Exception::class); + $this->expectExceptionMessage('Test exception'); + $validator->validate_response($request, $response); + } +} diff --git a/lib/tests/router/route_controller_test.php b/lib/tests/router/route_controller_test.php new file mode 100644 index 00000000000..5654ba47935 --- /dev/null +++ b/lib/tests/router/route_controller_test.php @@ -0,0 +1,151 @@ +. + +namespace core\router; + +use core\router; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\ServerRequest; +use GuzzleHttp\Psr7\Uri; +use Psr\Http\Message\ResponseInterface; +use Slim\Exception\HttpNotFoundException; + +/** + * Tests for the route_controller trait. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\route_controller + */ +final class route_controller_test extends route_testcase { + /** + * Test that the redirect method works as expected. + * + * @covers ::redirect + */ + public function test_redirect(): void { + $helper = new class (\core\di::get_container()) { + use route_controller; + + // phpcs:ignore moodle.Commenting.MissingDocblock.MissingTestcaseMethodDescription + public function test( + ResponseInterface $response, + $url, + ) { + return $this->redirect($response, $url); + } + }; + + $response = $helper->test(new Response(), '/test'); + $this->assertEquals(302, $response->getStatusCode()); + $this->assertEquals('/test', $response->getHeaderLine('Location')); + } + + public function test_page_not_found(): void { + $request = new ServerRequest('GET', '/test'); + $response = new Response(); + + $helper = new class (\core\di::get_container()) { + use route_controller; + }; + + $rc = new \ReflectionClass($helper); + $rcm = $rc->getMethod('page_not_found'); + + $this->expectException(HttpNotFoundException::class); + $rcm->invokeArgs($helper, [$request, $response]); + } + + /** + * Test that get_param works as expected. + * + * @covers \core\router\route_controller::get_param + */ + public function test_get_param(): void { + $request = (new \GuzzleHttp\Psr7\ServerRequest('GET', '/test')) + ->withQueryParams(['test' => 'value']); + + $helper = new class (\core\di::get_container()) { + use route_controller; + }; + + $rc = new \ReflectionClass($helper); + $rcm = $rc->getMethod('get_param'); + + // Test a value that exists. + $result = $rcm->invokeArgs($helper, [$request, 'test', null]); + $this->assertEquals('value', $result); + + $result = $rcm->invokeArgs($helper, [$request, 'test', 'Unused default']); + $this->assertEquals('value', $result); + + // Test a value that does not existexists. + $result = $rcm->invokeArgs($helper, [$request, 'fake', null]); + $this->assertEquals(null, $result); + $this->assertdebuggingcalledcount(1); + + $result = $rcm->invokeArgs($helper, [$request, 'fake', 'Used default']); + $this->assertEquals('Used default', $result); + $this->assertdebuggingcalledcount(1); + } + + /** + * Test that it is possible to redirect to a callable. + * + * @covers \core\router\route_controller::redirect_to_callable + */ + public function test_redirect_to_callable(): void { + self::load_fixture('core', '/router/route_on_class.php'); + + $rc = new \ReflectionClass(\core\fixtures\route_on_class::class); + $rcm = $rc->getMethod('method_with_route'); + $route = $rcm->getAttributes(route::class)[0]->newInstance(); + $router = $this->get_router(); + $app = $router->get_app(); + \core\di::get_container()->set(router::class, $router); + + $app + ->get( + $route->get_path(), + ['core\fixtures\route_on_class', 'method_with_route'], + ) + ->setName('core\fixtures\route_on_class::method_with_route'); + + $helper = new class (\core\di::get_container()) { + use route_controller; + }; + $rc = new \ReflectionClass($helper); + $rcm = $rc->getMethod('redirect_to_callable'); + + $response = $rcm->invokeArgs( + $helper, + [ + new ServerRequest('GET', '/test'), + new Response(), + 'core\fixtures\route_on_class::method_with_route', + ], + ); + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals(302, $response->getStatusCode()); + $this->assertTrue($response->hasHeader('Location')); + + $uri = new Uri($response->getHeader('Location')[0]); + + $this->assertEmpty($uri->getQuery()); + } +} diff --git a/lib/tests/router/route_loader_test.php b/lib/tests/router/route_loader_test.php new file mode 100644 index 00000000000..ad9a565c282 --- /dev/null +++ b/lib/tests/router/route_loader_test.php @@ -0,0 +1,91 @@ +. + +namespace core\router; + +use core\tests\route_testcase; +use Slim\Routing\RoutingResults; + +/** + * Tests for the standard route loader. + * + * @package core + * @category test + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\route_loader + */ +final class route_loader_test extends route_testcase { + /** + * Ensure that the abstract loader implements the interface. + */ + public function test_class_implements(): void { + $reflection = new \ReflectionClass(route_loader::class); + $this->assertTrue($reflection->implementsInterface(route_loader_interface::class)); + } + + /** + * Test that we are able to fetch all routes. + */ + public function test_configure_api_routes(): void { + $loader = new route_loader(); + + $app = $this->get_simple_app(); + $routegroups = $loader->configure_routes($app); + + $this->assertIsArray($routegroups); + $this->assertGreaterThanOrEqual(1, count($routegroups)); + foreach ($routegroups as $group) { + // Each of the returned groups shoudl be a RouteGroupInterface. + if (is_array($group)) { + foreach ($group as $thisgroup) { + $this->assertInstanceOf(\Slim\Interfaces\RouteInterface::class, $thisgroup); + } + } else { + $this->assertInstanceOf(\Slim\Interfaces\RouteGroupInterface::class, $group); + } + } + + // Note: It is not possible to test the actual routes that are added to + // the group as they are added to the App which we cannot inspect. + // We can, however, test that a known route is resolved. + + $collector = $app->getRouteCollector(); + $allroutes = $collector->getRoutes(); + foreach ($allroutes as $route) { + $thisroutegroups = $route->getGroups(); + $this->assertGreaterThanOrEqual(1, $thisroutegroups); + foreach ($thisroutegroups as $thisroutegroup) { + $this->assertContains($thisroutegroup, $routegroups); + } + } + + // Resolve the OpenAPI route. + $path = route_loader_interface::ROUTE_GROUP_API . '/openapi.json'; + $result = $app->getRouteResolver()->computeRoutingResults($path, 'GET'); + $this->assertNotNull($result); + $this->assertInstanceOf(\Slim\Routing\RoutingResults::class, $result); + + // The result should be found. + $this->assertEquals(RoutingResults::FOUND, $result->getRouteStatus()); + + // It should have an identifier which resolves to a Route. + $identifier = $result->getRouteIdentifier(); + $this->assertIsString($identifier); + $route = $app->getRouteResolver()->resolveRoute($identifier); + $this->assertInstanceOf(\Slim\Interfaces\RouteInterface::class, $route); + } +} diff --git a/lib/tests/router/route_test.php b/lib/tests/router/route_test.php new file mode 100644 index 00000000000..072d0f35deb --- /dev/null +++ b/lib/tests/router/route_test.php @@ -0,0 +1,561 @@ +. + +namespace core\router; + +use core\param; +use core\router\response\empty_response; +use core\router\route; +use core\router\schema\parameters\header_object; +use core\router\schema\parameters\path_parameter; +use core\router\schema\parameters\query_parameter; +use core\router\schema\request_body; +use core\router\schema\response\response; +use core\tests\route_testcase; + +/** + * Tests for user preference API handler. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\route + */ +final class route_test extends route_testcase { + /** + * Test that the Attribute is configured correctly. + */ + public function test_attributes(): void { + $route = new \ReflectionClass(route::class); + $this->assertNotEmpty($route->getAttributes()); + + $this->assertNotEmpty($route->getAttributes(\Attribute::class)); + $attributes = $route->getAttributes(\Attribute::class); + $this->assertCount(1, $attributes); + $attribute = reset($attributes); + $flags = $attribute->getArguments()[0]; + + // This can only be set on class, and method. + $this->assertEquals(\Attribute::TARGET_CLASS, $flags & \Attribute::TARGET_CLASS); + $this->assertEquals(\Attribute::TARGET_METHOD, $flags & \Attribute::TARGET_METHOD); + + // Only one per method or class allowed. + $this->assertEquals(0, \Attribute::IS_REPEATABLE & $flags); + + // Yes, this is a poor test, but if someone wants to extend this attribute in future, + // they need to write appropriate tests for it. + $this->assertEquals(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD, $flags); + } + + /** + * Test that path setting and getting works as expected. + */ + public function test_get_path(): void { + $route = new route( + path: '/example', + ); + + $this->assertEquals('/example', $route->get_path()); + + // And with a parent. + $child = new route( + path: '/child/path', + ); + $child->set_parent($route); + $this->assertEquals('/example/child/path', $child->get_path()); + + // But the parent is not changed in any way. + $this->assertEquals('/example', $route->get_path()); + } + + + /** + * Test the default method. + */ + public function test_get_methods(): void { + // No method specified. + $route = new route(); + $this->assertNull($route->get_methods()); + + // With a method. + $route = new route( + method: 'POST', + ); + $this->assertEquals(['POST'], $route->get_methods()); + + // An array of methods. + $route = new route( + method: ['POST', 'PUT'], + ); + $this->assertEquals(['POST', 'PUT'], $route->get_methods()); + + // A route which inherits its method from its parent. + $child = new route(); + $child->set_parent($route); + $this->assertEquals(['POST', 'PUT'], $child->get_methods()); + + // A child route will merge its own methods with its parents. + $child = new route( + method: 'GET', + ); + $child->set_parent($route); + $this->assertEquals(['GET', 'POST', 'PUT'], $child->get_methods()); + + // A child route which shares some will not duplicate. + $child = new route( + method: ['GET', 'PUT'], + ); + $child->set_parent($route); + $this->assertEquals(['GET', 'POST', 'PUT'], $child->get_methods()); + } + + /** + * Ensure that pathtypes and queryparams accept query parameters correctly. + */ + public function test_params_are_params(): void { + $route = new route( + pathtypes: [ + new path_parameter( + name: 'example', + type: param::RAW, + ), + new path_parameter( + name: 'another', + type: param::INT, + ), + ], + queryparams: [ + new query_parameter( + name: 'example', + type: param::RAW, + ), + new query_parameter( + name: 'another', + type: param::INT, + ), + ], + ); + $this->assertInstanceOf(route::class, $route); + } + + /** + * Ensure that pathtypes and queryparams do not accept the wrong type of parameter. + * + * @dataProvider invalid_constructor_param_types + * @param array $args + */ + public function test_params_not_params(array $args): void { + $this->expectException(\coding_exception::class); + new route(...$args); + } + + /** + * Data provider for test_params_not_params. + * + * @return array + */ + public static function invalid_constructor_param_types(): array { + return [ + 'not a param at all in queryparams' => [ + 'args' => [ + 'queryparams' => [ + new query_parameter( + name: 'another', + type: param::INT, + ), + new \stdClass(), + ], + ], + ], + 'not a param at all in pathtypes' => [ + 'args' => [ + 'pathtypes' => [ + new path_parameter( + name: 'another', + type: param::INT, + ), + new \stdClass(), + ], + ], + ], + 'not a param at all in headerparams' => [ + 'args' => [ + 'headerparams' => [ + new header_object( + name: 'another', + type: param::INT, + ), + new \stdClass(), + ], + ], + ], + 'path_parameter in queryparams' => [ + 'args' => [ + 'queryparams' => [ + new path_parameter( + name: 'example', + type: param::RAW, + ), + new query_parameter( + name: 'another', + type: param::INT, + ), + ], + ], + ], + 'query_parameter in pathtype' => [ + 'args' => [ + 'pathtypes' => [ + new path_parameter( + name: 'example', + type: param::RAW, + ), + new query_parameter( + name: 'another', + type: param::INT, + ), + ], + ], + ], + 'query_parameter in header' => [ + 'args' => [ + 'headerparams' => [ + new path_parameter( + name: 'example', + type: param::RAW, + ), + new query_parameter( + name: 'another', + type: param::INT, + ), + ], + ], + ], + ]; + } + + public function test_get_path_parameters(): void { + // No parameters at all. + $route = new route(); + $this->assertEmpty($route->get_path_parameters()); + + $child = new route(); + $child->set_parent($route); + $this->assertEmpty($child->get_path_parameters()); + + // A route with a single parameter. + $route = new route( + pathtypes: [ + new path_parameter( + name: 'example', + type: param::SAFEPATH, + ), + ], + ); + $params = $route->get_path_parameters(); + $this->assertCount(1, $params); + $this->assertArrayHasKey('example', $params); + $this->assertInstanceOf(path_parameter::class, $params['example']); + $this->assertEquals(param::SAFEPATH, $params['example']->get_type()); + + // A route with a multiple parameters. + $route = new route( + pathtypes: [ + new path_parameter( + name: 'example', + type: param::SAFEPATH, + ), + new path_parameter( + name: 'another', + type: param::INT, + ), + ], + ); + $params = $route->get_path_parameters(); + $this->assertCount(2, $params); + $this->assertArrayHasKey('example', $params); + $this->assertArrayHasKey('another', $params); + $this->assertInstanceOf(path_parameter::class, $params['example']); + $this->assertEquals(param::SAFEPATH, $params['example']->get_type()); + $this->assertInstanceOf(path_parameter::class, $params['another']); + $this->assertEquals(param::INT, $params['another']->get_type()); + + // A child will also inhereit any params from the parent. + $child = new route( + pathtypes: [ + new path_parameter( + name: 'childparam', + type: param::COMPONENT, + ), + ], + ); + $child->set_parent($route); + $params = $child->get_path_parameters(); + $this->assertCount(3, $params); + $this->assertArrayHasKey('example', $params); + $this->assertArrayHasKey('another', $params); + $this->assertArrayHasKey('childparam', $params); + $this->assertInstanceOf(path_parameter::class, $params['example']); + $this->assertEquals(param::SAFEPATH, $params['example']->get_type()); + $this->assertInstanceOf(path_parameter::class, $params['another']); + $this->assertEquals(param::INT, $params['another']->get_type()); + $this->assertInstanceOf(path_parameter::class, $params['childparam']); + $this->assertEquals(param::COMPONENT, $params['childparam']->get_type()); + } + + public function test_get_header_parameters(): void { + // No parameters at all. + $route = new route(); + $this->assertEmpty($route->get_header_parameters()); + + $child = new route(); + $child->set_parent($route); + $this->assertEmpty($child->get_header_parameters()); + + // A route with a single parameter. + $route = new route( + headerparams: [ + new header_object( + name: 'example', + type: param::SAFEPATH, + ), + ], + ); + $params = $route->get_header_parameters(); + $this->assertCount(1, $params); + $this->assertArrayHasKey('example', $params); + $this->assertInstanceOf(header_object::class, $params['example']); + $this->assertEquals(param::SAFEPATH, $params['example']->get_type()); + + // A route with a multiple parameters. + $route = new route( + headerparams: [ + new header_object( + name: 'example', + type: param::SAFEPATH, + ), + new header_object( + name: 'another', + type: param::INT, + ), + ], + ); + $params = $route->get_header_parameters(); + $this->assertCount(2, $params); + $this->assertArrayHasKey('example', $params); + $this->assertArrayHasKey('another', $params); + $this->assertInstanceOf(header_object::class, $params['example']); + $this->assertEquals(param::SAFEPATH, $params['example']->get_type()); + $this->assertInstanceOf(header_object::class, $params['another']); + $this->assertEquals(param::INT, $params['another']->get_type()); + + // A child will also inhereit any params from the parent. + $child = new route( + headerparams: [ + new header_object( + name: 'childparam', + type: param::COMPONENT, + ), + ], + ); + $child->set_parent($route); + $params = $child->get_header_parameters(); + $this->assertCount(3, $params); + $this->assertArrayHasKey('example', $params); + $this->assertArrayHasKey('another', $params); + $this->assertArrayHasKey('childparam', $params); + $this->assertInstanceOf(header_object::class, $params['example']); + $this->assertEquals(param::SAFEPATH, $params['example']->get_type()); + $this->assertInstanceOf(header_object::class, $params['another']); + $this->assertEquals(param::INT, $params['another']->get_type()); + $this->assertInstanceOf(header_object::class, $params['childparam']); + $this->assertEquals(param::COMPONENT, $params['childparam']->get_type()); + } + + public function test_get_query_parameters(): void { + // No parameters at all. + $route = new route(); + $this->assertEmpty($route->get_query_parameters()); + + $child = new route(); + $child->set_parent($route); + $this->assertEmpty($child->get_query_parameters()); + + // A route with a single parameter. + $route = new route( + queryparams: [ + new query_parameter( + name: 'example', + type: param::SAFEPATH, + ), + ], + ); + $params = $route->get_query_parameters(); + $this->assertCount(1, $params); + $this->assertArrayHasKey('example', $params); + $this->assertInstanceOf(query_parameter::class, $params['example']); + $this->assertEquals(param::SAFEPATH, $params['example']->get_type()); + + // A route with a multiple parameters. + $route = new route( + queryparams: [ + new query_parameter( + name: 'example', + type: param::SAFEPATH, + ), + new query_parameter( + name: 'another', + type: param::INT, + ), + ], + ); + $params = $route->get_query_parameters(); + $this->assertCount(2, $params); + $this->assertArrayHasKey('example', $params); + $this->assertArrayHasKey('another', $params); + $this->assertInstanceOf(query_parameter::class, $params['example']); + $this->assertEquals(param::SAFEPATH, $params['example']->get_type()); + $this->assertInstanceOf(query_parameter::class, $params['another']); + $this->assertEquals(param::INT, $params['another']->get_type()); + + // A child will also inhereit any params from the parent. + $child = new route( + queryparams: [ + new query_parameter( + name: 'childparam', + type: param::COMPONENT, + ), + ], + ); + $child->set_parent($route); + $params = $child->get_query_parameters(); + $this->assertCount(3, $params); + $this->assertArrayHasKey('example', $params); + $this->assertArrayHasKey('another', $params); + $this->assertArrayHasKey('childparam', $params); + $this->assertInstanceOf(query_parameter::class, $params['example']); + $this->assertEquals(param::SAFEPATH, $params['example']->get_type()); + $this->assertInstanceOf(query_parameter::class, $params['another']); + $this->assertEquals(param::INT, $params['another']->get_type()); + $this->assertInstanceOf(query_parameter::class, $params['childparam']); + $this->assertEquals(param::COMPONENT, $params['childparam']->get_type()); + } + + /** + * Test that has_request_body works as expected. + */ + public function test_has_request_body(): void { + $route = new route(); + $this->assertFalse($route->has_request_body()); + + $route = new route( + requestbody: new request_body(), + ); + $this->assertTrue($route->has_request_body()); + } + + public function test_get_request_body(): void { + $route = new route(); + $this->assertNull($route->get_request_body()); + + $body = new request_body(); + $route = new route( + requestbody: $body, + ); + $this->assertEquals($body, $route->get_request_body()); + + } + + /** + * Ensure that has_any_validatable_parameter checks the param types. + */ + public function test_has_any_validatable_parameter(): void { + // No validatable params. + $route = new route(); + $this->assertFalse($route->has_any_validatable_parameter()); + + // A pathtype is a validatable param. + $route = new route( + pathtypes: [ + new path_parameter( + name: 'example', + type: param::INT, + ), + ], + ); + $this->assertTrue($route->has_any_validatable_parameter()); + + // A pathtype is a validatable param. + $route = new route( + queryparams: [ + new query_parameter( + name: 'example', + type: param::INT, + ), + ], + ); + $this->assertTrue($route->has_any_validatable_parameter()); + + // A request body is a validatable param. + $route = new route( + requestbody: new request_body(), + ); + $this->assertTrue($route->has_any_validatable_parameter()); + } + + /** + * Test cookie control. + */ + public function test_cookies(): void { + // By default we allow cookie access. + $route = new route(); + $this->assertTrue($route->cookies); + } + + /** + * Test abort_after_config control. + */ + public function test_abort_after_config(): void { + // By default we do not abort after config. + $route = new route(); + $this->assertFalse($route->abortafterconfig); + } + + public function test_get_responses(): void { + $route = new route( + responses: [ + new response( + statuscode: 201, + description: 'Example response', + ), + new empty_response(), + ], + ); + + $this->assertCount(2, $route->get_responses()); + + $this->assertNull($route->get_response_with_status_code(200)); + $this->assertInstanceOf( + empty_response::class, + $route->get_response_with_status_code(204), + ); + $this->assertInstanceOf( + response::class, + $route->get_response_with_status_code(201), + ); + $this->assertEquals('Example response', $route->get_response_with_status_code(201)->description); + } +} diff --git a/lib/tests/router/schema/example_test.php b/lib/tests/router/schema/example_test.php new file mode 100644 index 00000000000..ee74e2b72fb --- /dev/null +++ b/lib/tests/router/schema/example_test.php @@ -0,0 +1,95 @@ +. + +namespace core\router\schema; +use core\tests\route_testcase; + +/** + * Tests for examples. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\example + * @covers \core\router\schema\openapi_base + */ +final class example_test extends route_testcase { + public function test_value(): void { + $example = new example( + name: 'First example', + value: 'This is a value', + ); + $schema = $example->get_openapi_schema(new specification()); + $this->assertEquals( + 'This is a value', + $schema->value, + ); + $this->assertObjectNotHasProperty('externalValue', $schema); + } + + public function test_externalvalue(): void { + $example = new example( + name: 'First example', + externalvalue: 'https://example.com/example/value' + ); + $schema = $example->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('value', $schema); + $this->assertEquals( + 'https://example.com/example/value', + $schema->externalValue, + ); + } + + public function test_value_or_externalvalue(): void { + $this->expectException(\coding_exception::class); + new example( + name: 'First example', + value: 'This is a value', + externalvalue: 'This is an external value', + ); + } + + public function test_basics(): void { + $example = new example( + name: 'First example', + summary: 'This is a summary', + description: 'This is a description', + value: 'This is a value', + ); + + $schema = $example->get_openapi_schema(new specification()); + + $this->assertEquals('First example', $example->get_name()); + $this->assertEquals('This is a summary', $schema->summary); + $this->assertEquals('This is a description', $schema->description); + } + + public function test_referenced_object(): void { + $object = new class ( + name: 'example', + value: 'Some value', + ) extends example implements referenced_object { + }; + + $schema = $object->get_openapi_description(new specification()); + $this->assertObjectNotHasProperty('$ref', $schema); + $this->assertObjectHasProperty('value', $schema); + + $reference = $object->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('value', $reference); + $this->assertObjectHasProperty('$ref', $reference); + } +} diff --git a/lib/tests/router/schema/header_object_test.php b/lib/tests/router/schema/header_object_test.php new file mode 100644 index 00000000000..ccf9780c59b --- /dev/null +++ b/lib/tests/router/schema/header_object_test.php @@ -0,0 +1,85 @@ +. + +namespace core\router\schema; + +use core\param; +use core\tests\route_testcase; + +/** + * Tests for header objects. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\header_object + * @covers \core\router\schema\parameters\header_object + * @covers \core\router\schema\openapi_base + */ +final class header_object_test extends route_testcase { + public function test_in_path(): void { + $param = new header_object( + name: 'example', + type: param::INT, + ); + $this->assertEquals('header', $param->get_in()); + $this->assertEquals('example', $param->get_name()); + + // Fetch the description directly. + $schema = $param->get_openapi_description(new specification()); + $this->assertObjectNotHasProperty('$ref', $schema); + $this->assertObjectHasProperty('schema', $schema); + $this->assertObjectHasProperty('type', $schema->schema); + $this->assertEquals('integer', $schema->schema->type); + $this->assertObjectNotHasProperty('in', $schema); + $this->assertObjectNotHasProperty('name', $schema); + + // Should have the same response a via the get_openapi_schema method. + $schema = $param->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('$ref', $schema); + $this->assertObjectHasProperty('schema', $schema); + $this->assertObjectHasProperty('type', $schema->schema); + $this->assertEquals('integer', $schema->schema->type); + $this->assertObjectNotHasProperty('in', $schema); + $this->assertObjectNotHasProperty('name', $schema); + } + + public function test_referenced_object(): void { + $object = new class extends header_object implements referenced_object { + /** + * Constructor. + */ + public function __construct() { + parent::__construct( + name: 'example', + type: param::INT, + ); + } + }; + + $schema = $object->get_openapi_description(new specification()); + $this->assertObjectNotHasProperty('$ref', $schema); + $this->assertObjectHasProperty('schema', $schema); + $this->assertObjectHasProperty('type', $schema->schema); + $this->assertEquals('integer', $schema->schema->type); + $this->assertObjectNotHasProperty('in', $schema); + $this->assertObjectNotHasProperty('name', $schema); + + $reference = $object->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('schema', $reference); + $this->assertObjectHasProperty('$ref', $reference); + } +} diff --git a/lib/tests/router/schema/objects/array_of_strings_test.php b/lib/tests/router/schema/objects/array_of_strings_test.php new file mode 100644 index 00000000000..8c3dffa66d1 --- /dev/null +++ b/lib/tests/router/schema/objects/array_of_strings_test.php @@ -0,0 +1,108 @@ +. + +namespace core\router\schema\objects; + +use core\param; +use core\router\schema\specification; +use core\tests\route_testcase; + +/** + * Tests for the an array of other objects. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\objects\array_of_strings + * @covers \core\router\schema\objects\type_base + * @covers \core\router\schema\openapi_base + */ +final class array_of_strings_test extends route_testcase { + public function test_referenced_object(): void { + $object = new array_of_strings(); + + $schema = $object->get_openapi_description(new specification()); + $this->assertEquals((object) [ + 'type' => 'object', + 'additionalProperties' => [ + 'type' => 'string', + ], + ], $schema); + + $reference = $object->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('type', $reference); + $this->assertObjectHasProperty('$ref', $reference); + } + + public function test_validation(): void { + $object = new array_of_strings( + keyparamtype: param::ALPHANUM, + valueparamtype: param::INT, + ); + + $data = $object->validate_data([ + 'example' => 123, + 'other' => 321, + ]); + + $this->assertCount(2, $data); + $this->assertEquals([ + 'example' => '123', + 'other' => '321', + ], $data); + } + + /** + * Test tha the validate_data method throws an exception when the data is invalid. + * + * @dataProvider failed_validation_provider + * @param param $keyparamtype + * @param param $valueparamtype + * @param array $data + */ + public function test_validation_failures( + param $keyparamtype, + param $valueparamtype, + array $data, + ): void { + $object = new array_of_strings( + keyparamtype: $keyparamtype, + valueparamtype: $valueparamtype, + ); + + $this->expectException(\invalid_parameter_exception::class); + + $object->validate_data($data); + } + + /** + * Data provider for test_validation_failures. + * + * @return array + */ + public static function failed_validation_provider(): array { + return [ + [ + param::ALPHANUM, + param::INT, + [ + 'example' => 123, + 'other' => 'threetwoone', + ], + ], + ]; + } +} diff --git a/lib/tests/router/schema/objects/array_of_things_test.php b/lib/tests/router/schema/objects/array_of_things_test.php new file mode 100644 index 00000000000..c17be7e8463 --- /dev/null +++ b/lib/tests/router/schema/objects/array_of_things_test.php @@ -0,0 +1,201 @@ +. + +namespace core\router\schema\objects; + +use core\param; +use core\router\schema\referenced_object; +use core\router\schema\specification; +use core\tests\route_testcase; + +/** + * Tests for the an array of other objects. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\objects\array_of_things + * @covers \core\router\schema\objects\type_base + * @covers \core\router\schema\openapi_base + */ +final class array_of_things_test extends route_testcase { + public function test_referenced_object(): void { + $object = new class ( // phpcs:ignore + thingtype: 'integer', + content: [ + 'example' => new schema_object(content: []), + ], + ) extends array_of_things implements referenced_object { + }; + + $schema = $object->get_openapi_description(new specification()); + $this->assertObjectNotHasProperty('$ref', $schema); + $this->assertObjectHasProperty('type', $schema); + $this->assertEquals('object', $schema->type); + $this->assertObjectNotHasProperty('properties', $schema); + $this->assertObjectHasProperty('additionalProperties', $schema); + $this->assertArrayHasKey('type', $schema->additionalProperties); + $this->assertEquals('integer', $schema->additionalProperties['type']); + + $reference = $object->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('type', $reference); + $this->assertObjectHasProperty('$ref', $reference); + } + + public function test_referenced_object_typebase(): void { + $object = new class ( // phpcs:ignore + thingtype: new scalar_type(param::INT), + ) extends array_of_things implements referenced_object { + }; + + $schema = $object->get_openapi_description(new specification()); + $this->assertArrayHasKey('$ref', $schema->additionalProperties); + + $reference = $object->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('type', $reference); + $this->assertObjectHasProperty('$ref', $reference); + } + + public function test_basics(): void { + $object = new array_of_things( + thingtype: 'integer', + ); + + $schema = $object->get_openapi_description(new specification()); + $this->assertEquals((object) [ + 'type' => 'object', + 'additionalProperties' => [ + 'type' => 'integer', + ], + ], $schema); + } + + + /** + * Test tha the validate_data method successfully validates content. + * + * @dataProvider successful_validation_provider + * @param param|string|type_base|null $valueparamtype + * @param mixed $data + */ + public function test_validation_success( + param|string|type_base|null $valueparamtype, + mixed $data, + ): void { + $object = new array_of_things( + $valueparamtype, + ); + + $result = $object->validate_data($data); + $this->assertEquals($data, $result); + } + + /** + * Data provider for test_validation_success. + * + * @return array + */ + public static function successful_validation_provider(): array { + return [ + [ + param::INT, + [ + 'example' => 123, + 'other' => 321, + 'more' => 5634543456543456, + ], + ], + [ + 'int', + [ + 'example' => 123, + 'other' => 321, + 'more' => 5634543456543456, + ], + ], + [ + new scalar_type(param::INT), + [ + 'example' => 123, + 'other' => 321, + 'more' => 5634543456543456, + ], + ], + [ + null, + [ + 'example' => 123, + 'other' => 321, + 'more' => 'This is a string', + ], + ], + ]; + } + + /** + * Test tha the validate_data method throws an exception when the data is invalid. + * + * @dataProvider failed_validation_provider + * @param param|string|type_base|null $valueparamtype + * @param mixed $data + */ + public function test_validation_failures( + param|string|type_base|null $valueparamtype, + mixed $data, + ): void { + $object = new array_of_things( + $valueparamtype, + ); + + $this->expectException(\invalid_parameter_exception::class); + $object->validate_data($data); + } + + /** + * Data provider for test_validation_failures. + * + * @return array + */ + public static function failed_validation_provider(): array { + return [ + [ + 'int', + [ + 'example' => 123, + 'other' => 'threetwoone', + ], + ], + [ + new scalar_type(param::INT), + [ + 'example' => 123, + 'other' => 'threetwoone', + ], + ], + [ + param::INT, + [ + 'example' => 123, + 'other' => 'threetwoone', + ], + ], + [ + param::INT, + 'string', + ], + ]; + } +} diff --git a/lib/tests/router/schema/objects/scalar_type_test.php b/lib/tests/router/schema/objects/scalar_type_test.php new file mode 100644 index 00000000000..6105907688d --- /dev/null +++ b/lib/tests/router/schema/objects/scalar_type_test.php @@ -0,0 +1,80 @@ +. + +namespace core\router\schema\objects; + +use core\param; +use core\router\schema\referenced_object; +use core\router\schema\specification; +use core\tests\route_testcase; +use invalid_parameter_exception; + +/** + * Tests for a scalar type. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\objects\scalar_type + */ +final class scalar_type_test extends route_testcase { + public function test_referenced_object(): void { + $object = new class ( + type: param::ALPHANUM, + ) extends scalar_type implements referenced_object { + }; + + $schema = $object->get_openapi_description(new specification()); + $this->assertObjectNotHasProperty('$ref', $schema); + $this->assertObjectHasProperty('type', $schema); + + $reference = $object->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('type', $reference); + $this->assertObjectHasProperty('$ref', $reference); + } + + public function test_validation(): void { + $object = new scalar_type( + type: param::ALPHA, + ); + + // This should return whatever was input. + $data = $object->validate_data('alpha'); + $this->assertEquals('alpha', $data); + + // By default parameters are optional. + $this->assertNull($object->validate_data(null)); + } + + public function test_validation_nullable(): void { + $object = new scalar_type( + type: param::ALPHA, + required: false, + ); + + $this->assertNull($object->validate_data(null)); + } + + public function test_validation_not_nullable(): void { + $object = new scalar_type( + type: param::ALPHA, + required: true, + ); + + $this->expectException(invalid_parameter_exception::class); + $object->validate_data(null); + } +} diff --git a/lib/tests/router/schema/objects/schema_object_test.php b/lib/tests/router/schema/objects/schema_object_test.php new file mode 100644 index 00000000000..8d9b7c2f5cc --- /dev/null +++ b/lib/tests/router/schema/objects/schema_object_test.php @@ -0,0 +1,103 @@ +. + +namespace core\router\schema\objects; + +use core\router\schema\referenced_object; +use core\router\schema\specification; +use core\tests\route_testcase; + +/** + * Tests for the schema_object. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\objects\schema_object + * @covers \core\router\schema\openapi_base + */ +final class schema_object_test extends route_testcase { + public function test_referenced_object(): void { + $object = new class ( + content: [ + 'example' => new schema_object(content: []), + ], + ) extends schema_object implements referenced_object { + }; + + $schema = $object->get_openapi_description(new specification()); + $this->assertObjectNotHasProperty('$ref', $schema); + $this->assertObjectHasProperty('type', $schema); + $this->assertObjectHasProperty('properties', $schema); + $this->assertObjectHasProperty('example', $schema->properties); + + $reference = $object->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('type', $reference); + $this->assertObjectHasProperty('$ref', $reference); + } + + public function test_basics(): void { + $object = new schema_object( + content: [ + 'example' => new schema_object(content: []), + ], + ); + + $this->assertFalse($object->has('missing')); + $this->assertTrue($object->has('example')); + + $this->assertInstanceOf(schema_object::class, $object->get('example')); + } + + public function test_invalid_content(): void { + $this->expectException(\coding_exception::class); + + new schema_object(content: ['invalid']); + } + + public function test_validation(): void { + $object = new schema_object( + content: [ + 'example' => new schema_object(content: [ + 'somekey' => new schema_object(content: []), + ]), + ], + ); + + // Nothing in, nothing out. + $this->assertEmpty($object->validate_data([])); + + // Surplus data is ignored. + $this->assertEmpty($object->validate_data(['surplus' => []])); + + // Valid data is included. + $data = $object->validate_data(['example' => []]); + $this->assertArrayHasKey('example', $data); + $this->assertIsArray($data['example']); + + // Valid data is included and surplus data is removed. + $data = $object->validate_data(['example' => [], 'surplus' => []]); + $this->assertArrayHasKey('example', $data); + $this->assertIsArray($data['example']); + $this->assertArrayNotHasKey('surplus', $data); + + // Applied to nested values. + $data = $object->validate_data(['example' => ['somekey' => []]]); + $this->assertArrayHasKey('example', $data); + $this->assertIsArray($data['example']); + $this->assertArrayHasKey('somekey', $data['example']); + } +} diff --git a/lib/tests/router/schema/objects/stacktrace_test.php b/lib/tests/router/schema/objects/stacktrace_test.php new file mode 100644 index 00000000000..3ddcb13e912 --- /dev/null +++ b/lib/tests/router/schema/objects/stacktrace_test.php @@ -0,0 +1,62 @@ +. + +namespace core\router\schema\objects; + +use core\router\schema\specification; +use core\tests\route_testcase; + +/** + * Tests for a stacktrace. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\objects\stacktrace + * @covers \core\router\schema\objects\type_base + */ +final class stacktrace_test extends route_testcase { + public function test_referenced_object(): void { + $object = new stacktrace(); + + $schema = $object->get_openapi_description(new specification()); + $this->assertObjectNotHasProperty('$ref', $schema); + $this->assertObjectHasProperty('type', $schema); + $this->assertObjectHasProperty('items', $schema); + $this->assertObjectHasProperty('examples', $schema); + $this->assertEquals('array', $schema->type); + + $reference = $object->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('type', $reference); + $this->assertObjectHasProperty('$ref', $reference); + } + + public function test_validation(): void { + $object = new stacktrace(); + + // This should return whatever was input. + $data = $object->validate_data([ + 'example' => 123, + 'other' => 321, + ]); + + $this->assertCount(2, $data); + $this->assertEquals([ + 'example' => 123, + 'other' => 321, + ], $data); + } +} diff --git a/lib/tests/router/schema/parameter_test.php b/lib/tests/router/schema/parameter_test.php new file mode 100644 index 00000000000..d488d91c7b8 --- /dev/null +++ b/lib/tests/router/schema/parameter_test.php @@ -0,0 +1,191 @@ +. + +namespace core\router\schema; + +use core\param; +use core\router\route; +use core\router\schema\objects\array_of_strings; +use core\router\schema\objects\schema_object; +use core\router\schema\specification; +use core\tests\route_testcase; + +/** + * Tests for parameters. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\parameter + * @covers \core\router\schema\openapi_base + */ +final class parameter_test extends route_testcase { + public function test_basics(): void { + $param = new parameter( + name: 'examplename', + in: 'examplein', + ); + $this->assertEquals('examplename', $param->get_name()); + $this->assertEquals('examplein', $param->get_in()); + + // The default behaviour is for a parameter to not be required if it was not explicitly specified. + $this->assertFalse($param->is_required(new route())); + } + + /** + * Test for required default. + * + * @dataProvider required_default_provider + * @param array $params + */ + public function test_required_default(array $params): void { + $this->expectException(\coding_exception::class); + new parameter(...$params); + } + + /** + * Data provider for required default tests. + * + * @return array + */ + public static function required_default_provider(): array { + return [ + [ + [ + 'name' => 'example', + 'in' => parameter::IN_PATH, + 'required' => true, + 'default' => 0, + ], + [ + 'name' => 'example', + 'in' => parameter::IN_PATH, + 'required' => true, + 'default' => false, + ], + [ + 'name' => 'example', + 'in' => parameter::IN_PATH, + 'required' => true, + 'default' => "", + ], + ], + ]; + } + + public function test_get_type(): void { + $param = new parameter( + name: 'examplename', + in: 'examplein', + type: param::ALPHANUMEXT, + ); + $this->assertEquals(param::ALPHANUMEXT, $param->get_type()); + } + + /** + * Test for is_required. + * + * @dataProvider is_required_provider + * @param null|bool $required + * @param bool $expected + */ + public function test_is_required(?bool $required, bool $expected): void { + $param = new parameter( + name: 'example', + in: parameter::IN_HEADER, + required: $required, + ); + + $this->assertSame($expected, $param->is_required(new route())); + } + + /** + * Data provider for is_required tests. + * + * @return array + */ + public static function is_required_provider(): array { + return [ + [true, true], + [false, false], + [null, false], + ]; + } + + public function test_example(): void { + $example = new example('examplevalue'); + $param = new parameter( + name: 'example', + in: 'header', + type: param::INT, + example: $example, + ); + $description = $param->get_openapi_description(new specification()); + $this->assertArrayHasKey('examplevalue', $description->examples); + } + + public function test_examples(): void { + $example = new example('examplevalue'); + $param = new parameter( + name: 'example', + in: 'header', + type: param::INT, + examples: [$example], + ); + $description = $param->get_openapi_description(new specification()); + $this->assertArrayHasKey('examplevalue', $description->examples); + } + + public function test_example_and_examples(): void { + $example = new example('examplevalue'); + $this->expectException(\coding_exception::class); + new parameter( + name: 'example', + in: 'header', + type: param::INT, + example: $example, + examples: [$example], + ); + } + + public function test_schema(): void { + $schema = new schema_object( + content: [ + new array_of_strings(), + ], + ); + $param = new parameter( + name: 'example', + in: 'header', + type: param::INT, + schema: $schema, + ); + $description = $param->get_openapi_description(new specification()); + $this->assertNotNull($description->schema); + $this->assertEquals('object', $description->schema->type); + } + + public function test_schema_includes_clientside_pattern(): void { + $param = new parameter( + name: 'example', + in: 'header', + type: param::ALPHANUM, + ); + $description = $param->get_openapi_description(new specification()); + $this->assertNotNull($description->schema); + $this->assertEquals('string', $description->schema->type); + } +} diff --git a/lib/tests/router/schema/parameters/header_object_test.php b/lib/tests/router/schema/parameters/header_object_test.php new file mode 100644 index 00000000000..627219f5b5d --- /dev/null +++ b/lib/tests/router/schema/parameters/header_object_test.php @@ -0,0 +1,171 @@ +. + +namespace core\router\schema\parameters; + +use core\param; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; +use invalid_parameter_exception; +use Psr\Http\Message\ServerRequestInterface; +use ValueError; + +/** + * Tests for header objects. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\parameters\header_object + */ +final class header_object_test extends route_testcase { + public function test_validate(): void { + $param = new header_object( + name: 'example', + type: param::TEXT, + ); + + /** @var ServerRequestInterface $request */ // phpcs:ignore moodle.Commenting.InlineComment.DocBlock + $request = (new ServerRequest('GET', 'http://example.com')) + // A known header. + ->withHeader('Accept', 'application/json') + // An unknown header is kept. + ->withHeader('X-Example', 'example') + // A known header with multiple values. + ->withAddedHeader('X-Multiple', 'value1') + ->withAddedHeader('X-Multiple', 'value2') + // An unknown header with multiple values. + ->withAddedHeader('X-Unknown', 'value1') + ->withAddedHeader('X-Unknown', 'value2'); + + $result = $param->validate($request); + $this->assertInstanceOf(ServerRequestInterface::class, $result); + + $this->assertEquals('application/json', $result->getHeaderLine('Accept')); + $this->assertEquals('example', $result->getHeaderLine('X-Example')); + $this->assertEquals(['value1', 'value2'], $result->getHeader('X-Multiple')); + $this->assertEquals(['value1', 'value2'], $result->getHeader('X-Unknown')); + } + + public function test_required_missing(): void { + $request = new ServerRequest('GET', 'http://example.com'); + $param = new header_object( + name: 'example', + type: param::TEXT, + required: true, + ); + + $this->expectException(invalid_parameter_exception::class); + $param->validate($request); + } + + public function test_optional_default(): void { + $request = new ServerRequest('GET', 'http://example.com'); + $param = new header_object( + name: 'example', + type: param::TEXT, + required: false, + default: 'default', + ); + + $request = $param->validate($request); + $this->assertEquals('default', $request->getHeaderLine('example')); + } + + public function test_optional_without_default(): void { + $request = new ServerRequest('GET', 'http://example.com'); + $param = new header_object( + name: 'example', + type: param::TEXT, + required: false, + ); + + $request = $param->validate($request); + $this->assertEquals(null, $request->getHeaderLine('example')); + } + + public function test_multiple_allowed(): void { + $request = new ServerRequest('GET', 'http://example.com'); + $request = $request + ->withAddedHeader('example', 'value1') + ->withAddedHeader('example', 'value2'); + $param = new header_object( + name: 'example', + type: param::TEXT, + multiple: true, + ); + + $request = $param->validate($request); + $this->assertEquals(['value1', 'value2'], $request->getHeader('example')); + } + + public function test_multiple_not_allowed(): void { + $request = new ServerRequest('GET', 'http://example.com'); + $request = $request + ->withAddedHeader('example', 'value1') + ->withAddedHeader('example', 'value2'); + $param = new header_object( + name: 'example', + type: param::TEXT, + multiple: false, + ); + + $this->expectException(invalid_parameter_exception::class); + $param->validate($request); + } + + public function test_boolean_param(): void { + $request = (new ServerRequest('GET', 'http://example.com')) + ->withHeader('example', 'true'); + $param = new header_object( + name: 'example', + type: param::BOOL, + ); + + $request = $param->validate($request); + $this->assertEquals([true], $request->getHeader('example')); + } + + public function test_multiple_boolean_param(): void { + $request = (new ServerRequest('GET', 'http://example.com')) + ->withAddedHeader('example', 'true') + ->withAddedHeader('example', 'false') + ->withAddedHeader('example', 'true'); + $param = new header_object( + name: 'example', + type: param::BOOL, + multiple: true, + ); + + $request = $param->validate($request); + $this->assertEquals([true, false, true], $request->getHeader('example')); + } + + public function test_multiple_boolean_param_invalid(): void { + $request = (new ServerRequest('GET', 'http://example.com')) + ->withAddedHeader('example', 'true') + ->withAddedHeader('example', '0') + ->withAddedHeader('example', 'true'); + $param = new header_object( + name: 'example', + type: param::BOOL, + multiple: true, + ); + + $this->expectException(ValueError::class); + $param->validate($request); + } +} diff --git a/lib/tests/router/schema/parameters/path_parameter_test.php b/lib/tests/router/schema/parameters/path_parameter_test.php new file mode 100644 index 00000000000..b78b65eb01a --- /dev/null +++ b/lib/tests/router/schema/parameters/path_parameter_test.php @@ -0,0 +1,165 @@ +. + +namespace core\router\schema\parameters; + +use core\param; +use core\router\route; +use core\router\schema\referenced_object; +use core\router\schema\specification; +use core\tests\route_testcase; +use invalid_parameter_exception; +use Psr\Http\Message\ServerRequestInterface; +use Slim\Routing\RouteContext; + +/** + * Tests for the path parameter. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\parameter + * @covers \core\router\schema\parameters\path_parameter + * @covers \core\router\schema\openapi_base + */ +final class path_parameter_test extends route_testcase { + public function test_in_path(): void { + $param = new path_parameter(name: 'example'); + $this->assertEquals('path', $param->get_in()); + $this->assertEquals('example', $param->get_name()); + } + + /** + * Test the is_required method. + * + * @dataProvider is_required_provider + * @param string $path + * @param bool $expected + */ + public function test_is_required(string $path, bool $expected): void { + $route = new route( + path: $path, + ); + $param = new path_parameter(name: 'value'); + $this->assertEquals($expected, $param->is_required($route)); + } + + /** + * Data provider for the is_required method. + * + * @return array + */ + public static function is_required_provider(): array { + return [ + ['/is/required/{value}', true], + ['/is/optional/[{value}]', false], + ['/is/[optional/[{value}]]', false], + ]; + } + + /** + * Test fo the OPenAPI description in different configurations. + * + * @dataProvider openapi_required_values_provider + * @param string $path If the a value is a required part of the path + * @param bool $required If the value is expected to be required + */ + public function test_get_openapi_description_required_values( + string $path, + bool $required, + ): void { + $param = new path_parameter( + name: 'value', + type: param::INT, + ); + + $api = new specification(); + $result = $param->get_openapi_description($api, $path); + if ($required) { + $this->assertNotNull($result); + $this->assertTrue($result->required); + $this->assertEquals('value', $result->name); + } else { + $this->assertNull($result); + } + } + + /** + * Data provider for OpenAPI Required values. + * + * @return array + */ + public static function openapi_required_values_provider(): array { + return [ + ['/is/required/{value}/with/children', true], + ['/is/required/{value}', true], + ['/is/optional', false], + ]; + } + + /** + * Ensure that a validation failure results in an invalid_parameter_exception. + */ + public function test_validation_failure(): void { + $param = new path_parameter( + name: 'example', + type: param::INT, + ); + $value = "example"; + + $request = $this->create_route( + '/example/{example}', + "/example/{$value}", + ); + $route = $request->getAttribute(RouteContext::ROUTE); + + $this->expectException(invalid_parameter_exception::class); + $param->validate($request, $route); + } + + public function test_validation_success(): void { + $param = new path_parameter( + name: 'example', + type: param::INT, + ); + $value = 12345; + + $request = $this->create_route( + '/example/{example}', + "/example/{$value}", + ); + $route = $request->getAttribute(RouteContext::ROUTE); + + $validatedresult = $param->validate($request, $route); + $this->assertInstanceOf(ServerRequestInterface::class, $validatedresult); + } + + public function test_referenced_object(): void { + $object = new class ( + name: 'example', + type: param::INT, + ) extends path_parameter implements referenced_object { + }; + + $schema = $object->get_openapi_description(new specification()); + $this->assertObjectNotHasProperty('$ref', $schema); + $this->assertObjectHasProperty('schema', $schema); + + $reference = $object->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('schema', $reference); + $this->assertObjectHasProperty('$ref', $reference); + } +} diff --git a/lib/tests/router/schema/parameters/query_parameter_test.php b/lib/tests/router/schema/parameters/query_parameter_test.php new file mode 100644 index 00000000000..dbd66d5d052 --- /dev/null +++ b/lib/tests/router/schema/parameters/query_parameter_test.php @@ -0,0 +1,316 @@ +. + +namespace core\router\schema\parameters; + +use core\param; +use core\router\route; +use core\router\schema\referenced_object; +use core\router\schema\specification; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; + +/** + * Tests for the query parameters. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\parameter + * @covers \core\router\schema\parameters\query_parameter + * @covers \core\router\schema\openapi_base + */ +final class query_parameter_test extends route_testcase { + public function test_in_path(): void { + $param = new query_parameter(name: 'example'); + $this->assertEquals('query', $param->get_in()); + $this->assertEquals('example', $param->get_name()); + } + + /** + * Test the is_required method. + * + * @dataProvider is_required_provider + * @param bool|null $required + * @param bool $expected + */ + public function test_is_required(?bool $required, bool $expected): void { + $param = new query_parameter( + name: 'value', + required: $required, + ); + $this->assertEquals($expected, $param->is_required(new route())); + } + + /** + * Data provider for the is_required method. + * + * @return array + */ + public static function is_required_provider(): array { + return [ + [true, true], + [false, false], + [null, false], + ]; + } + + public function test_referenced_object(): void { + $object = new class ( + name: 'example', + type: param::INT, + ) extends query_parameter implements referenced_object { + }; + + $schema = $object->get_openapi_description(new specification()); + $this->assertObjectNotHasProperty('$ref', $schema); + $this->assertObjectHasProperty('schema', $schema); + + $reference = $object->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('schema', $reference); + $this->assertObjectHasProperty('$ref', $reference); + } + + /** + * Test for the allowReserved property. + * + * @dataProvider allow_reserved_provider + * @param bool|null $allowreserved + * @param bool $expected + */ + public function test_allow_reserved( + ?bool $allowreserved, + bool $expected, + ): void { + $param = new query_parameter( + name: 'example', + type: param::INT, + allowreserved: $allowreserved, + ); + + $schema = $param->get_openapi_description(new specification()); + if ($expected) { + $this->assertObjectHasProperty('allowReserved', $schema); + $this->assertTrue($schema->allowReserved); + } else { + $this->assertObjectNotHasProperty('allowReserved', $schema); + } + } + + /** + * Data provider ofr testing whether reserved characters are allowed. + * + * @return array + */ + public static function allow_reserved_provider(): array { + return [ + [true, true], + [false, false], + [null, false], + ]; + } + + /** + * Tests of the param validation. + * + * @dataProvider validation_provider + * @param array $properties + * @param array $params + * @param array $expected + */ + public function test_validation( + array $properties, + array $params, + array $expected, + ): void { + $param = new query_parameter(...$properties); + + $request = new ServerRequest('GET', '/example'); + $request = $request->withQueryParams(array_merge( + $request->getQueryParams(), + $params, + )); + + $newrequest = $param->validate($request, $request->getQueryParams()); + $this->assertEquals($expected, $newrequest->getQueryParams()); + } + + /** + * Validation data provider. + * + * @return array + */ + public static function validation_provider(): array { + return [ + 'Basic required param' => [ + [ + 'name' => 'example', + 'type' => param::INT, + 'required' => true, + ], + [ + 'example' => 12345, + 'otherfield' => 'abcde', + ], + [ + 'example' => 12345, + 'otherfield' => 'abcde', + ], + ], + 'Basic optional param' => [ + [ + 'name' => 'example', + 'type' => param::INT, + ], + [ + 'example' => 12345, + 'otherfield' => 'abcde', + ], + [ + 'example' => 12345, + 'otherfield' => 'abcde', + ], + ], + 'Basic optional param not provided' => [ + [ + 'name' => 'example', + 'type' => param::INT, + ], + [ + 'otherfield' => 'abcde', + ], + [ + 'otherfield' => 'abcde', + 'example' => null, + ], + ], + 'Basic optional param not provided with defaults' => [ + [ + 'name' => 'example', + 'type' => param::INT, + 'default' => 999, + ], + [ + 'otherfield' => 'abcde', + ], + [ + 'otherfield' => 'abcde', + 'example' => 999, + ], + ], + 'Special handling for a bool (true)' => [ + [ + 'name' => 'example', + 'type' => param::BOOL, + ], + [ + 'otherfield' => 'abcde', + 'example' => 'true', + ], + [ + 'otherfield' => 'abcde', + 'example' => true, + ], + ], + 'Special handling for a bool (false)' => [ + [ + 'name' => 'example', + 'type' => param::BOOL, + ], + [ + 'otherfield' => 'abcde', + 'example' => 'false', + ], + [ + 'otherfield' => 'abcde', + 'example' => false, + ], + ], + 'Special handling for a bool - not specified' => [ + [ + 'name' => 'example', + 'type' => param::BOOL, + ], + [ + 'otherfield' => 'abcde', + ], + [ + 'otherfield' => 'abcde', + 'example' => null, + ], + ], + 'Special handling for a bool - default true' => [ + [ + 'name' => 'example', + 'type' => param::BOOL, + 'default' => true, + ], + [ + 'otherfield' => 'abcde', + ], + [ + 'otherfield' => 'abcde', + 'example' => true, + ], + ], + 'Special handling for a bool - default false' => [ + [ + 'name' => 'example', + 'type' => param::BOOL, + 'default' => false, + ], + [ + 'otherfield' => 'abcde', + ], + [ + 'otherfield' => 'abcde', + 'example' => false, + ], + ], + ]; + } + + public function test_validation_boolean_failure(): void { + $param = new query_parameter( + name: 'example', + type: param::BOOL, + ); + + $request = new ServerRequest('GET', '/example'); + $request = $request->withQueryParams(array_merge( + $request->getQueryParams(), + [ + 'example' => 'notaboolean', + ], + )); + + $this->expectException(\ValueError::class); + $param->validate($request, $request->getQueryParams()); + } + + public function test_validation_required_not_set(): void { + $param = new query_parameter( + name: 'example', + type: param::BOOL, + required: true, + ); + + $request = new ServerRequest('GET', '/example'); + + $this->expectException(\coding_exception::class); + $param->validate($request, $request->getQueryParams()); + } +} diff --git a/lib/tests/router/schema/request_body_test.php b/lib/tests/router/schema/request_body_test.php new file mode 100644 index 00000000000..32dd14933d9 --- /dev/null +++ b/lib/tests/router/schema/request_body_test.php @@ -0,0 +1,136 @@ +. + +namespace core\router\schema; + +use core\router\schema\objects\schema_object; +use core\router\schema\response\content\json_media_type; +use core\router\schema\response\content\payload_response_type; +use core\router\schema\specification; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; + +/** + * Tests for the request_body object. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\request_body + * @covers \core\router\schema\openapi_base + */ +final class request_body_test extends route_testcase { + public function test_basics(): void { + $object = new request_body(); + + $schema = $object->get_openapi_description(new specification()); + $this->assertEquals((object) [ + 'description' => '', + 'required' => false, + 'content' => [], + ], $schema); + $this->assertFalse($schema->required); + $this->assertFalse($object->is_required()); + } + + public function test_content_wrong_type(): void { + $this->expectException(\coding_exception::class); + new request_body( + content: [new schema_object(content: [])], + ); + } + + public function test_content_array(): void { + $object = new request_body( + content: [ + new json_media_type(content: [], required: true), + ], + ); + + $schema = $object->get_openapi_schema(new specification()); + $this->assertObjectHasProperty('content', $schema); + $this->assertObjectHasProperty('application/json', (object) $schema->content); + + $request = new ServerRequest('GET', 'http://example.com', [ + 'Content-Type' => json_media_type::get_encoding(), + ]); + $body = $object->get_body_for_request($request); + $this->assertInstanceOf(json_media_type::class, $body); + $this->assertTrue($body->is_required()); + } + + public function test_content_not_matching(): void { + $object = new request_body( + content: [ + new json_media_type(content: []), + ], + ); + $this->expectException(\invalid_parameter_exception::class); + $object->get_body_for_request(new ServerRequest('GET', 'http://example.com')); + } + + public function test_content_payload_type(): void { + $content = new payload_response_type(content: [], required: true); + $object = new request_body( + content: $content, + ); + + $schema = $object->get_openapi_schema(new specification()); + $this->assertObjectHasProperty('content', $schema); + + foreach ($content->get_supported_content_types() as $contenttypeclass) { + $encoding = $contenttypeclass::get_encoding(); + $this->assertObjectHasProperty($encoding, $schema->content); + $this->assertObjectNotHasProperty('$ref', $schema->content->{$encoding}); + + $request = new ServerRequest('GET', 'http://example.com', [ + 'Content-Type' => $encoding, + ]); + $body = $object->get_body_for_request($request); + $this->assertInstanceOf($contenttypeclass, $body); + } + } + + /** + * Test object referencing. + * + * @covers \core\router\schema\openapi_base + */ + public function test_referenced_object(): void { + $object = new class extends request_body implements referenced_object { + }; + + // Note: The status code is not in the OpenAPI schema, but in the parent. + $schema = $object->get_openapi_description(new specification()); + $this->assertObjectNotHasProperty('$ref', $schema); + $this->assertObjectHasProperty('description', $schema); + + $reference = $object->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('description', $reference); + $this->assertObjectHasProperty('$ref', $reference); + } + + public function test_reference_content(): void { + $object = new request_body( + content: [], + ); + $object = new class extends request_body implements referenced_object { + }; + + $schema = $object->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('content', $schema); + } +} diff --git a/lib/tests/router/schema/response/content/json_media_type_test.php b/lib/tests/router/schema/response/content/json_media_type_test.php new file mode 100644 index 00000000000..07874b55350 --- /dev/null +++ b/lib/tests/router/schema/response/content/json_media_type_test.php @@ -0,0 +1,157 @@ +. + +namespace core\router\schema\response\content; + +use core\router\schema\example; +use core\router\schema\objects\schema_object; +use core\router\schema\referenced_object; +use core\router\schema\specification; + +/** + * Tests for the abstract media type response content container. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\response\content\json_media_type + * @covers \core\router\schema\response\content\media_type + */ +final class json_media_type_test extends \advanced_testcase { + public function test_basics(): void { + $this->assertEquals( + 'application/json', + json_media_type::get_encoding(), + ); + + $object = new json_media_type(); + $this->assertEquals( + 'application/json', + $object->get_mimetype(), + ); + } + + /** + * Tests for the is_required method. + * + * @dataProvider is_required_provider + * @param bool|null $required + * @param bool $expected + */ + public function test_is_required(?bool $required, bool $expected): void { + // Note: This related to the _body_ being required. + $object = new json_media_type( + required: $required, + ); + + $schema = $object->get_openapi_schema(new specification()); + if ($expected) { + $this->assertTrue($object->is_required()); + $this->assertTrue($schema->required); + } else { + $this->assertFalse($object->is_required()); + $this->assertObjectNotHasProperty('required', $schema); + } + } + + /** + * Data provider for the is_required method. + * + * @return array + */ + public static function is_required_provider(): array { + return [ + [true, true], + [false, false], + ]; + } + + public function test_example_or_examples(): void { + $this->expectException(\coding_exception::class); + + new json_media_type( + example: new example(name: 'example'), + examples: [ + new example(name: 'example2'), + ], + ); + } + + public function test_single_example(): void { + $example = new example(name: 'examplename'); + $object = new json_media_type( + example: $example, + ); + + $spec = new specification(); + $schema = $object->get_openapi_schema($spec); + + // There is no schema specified here, so none in the OpenAPI object. + $this->assertObjectNotHasProperty('schema', $schema); + + // The 'example' attribute is going to be deprecated in a future version of the spec. + // We normalise a single example into the examples array instead which is the preferred way. + $this->assertObjectNotHasProperty('example', $schema); + $this->assertObjectHasProperty('examples', $schema); + + // The example will be listed by name. + $this->assertArrayHasKey('examplename', $schema->examples); + $this->assertEquals( + $example->get_openapi_schema($spec), + $schema->examples['examplename'], + ); + } + + public function test_schema(): void { + $schemaobject = new schema_object( + content: [ + 'example' => new schema_object(content: []), + ], + ); + + $object = new json_media_type( + schema: $schemaobject, + ); + + $this->assertSame($schemaobject, $object->get_schema()); + + $spec = new specification(); + $schema = $object->get_openapi_schema($spec); + $this->assertObjectHasProperty('schema', $schema); + $this->assertEquals( + $schemaobject->get_openapi_description($spec), + $schema->schema, + ); + } + + public function test_referenced_schema(): void { + $schemaobject = new class ( + content: [], + ) extends schema_object implements referenced_object { + }; + + $object = new json_media_type( + schema: $schemaobject, + ); + + $this->assertSame($schemaobject, $object->get_schema()); + + $spec = new specification(); + $schema = $object->get_openapi_schema($spec); + $this->assertObjectNotHasProperty('schema', $schema->schema); + $this->assertObjectHasProperty('$ref', $schema->schema); + } +} diff --git a/lib/tests/router/schema/response/content/payload_response_type_test.php b/lib/tests/router/schema/response/content/payload_response_type_test.php new file mode 100644 index 00000000000..d7335e63321 --- /dev/null +++ b/lib/tests/router/schema/response/content/payload_response_type_test.php @@ -0,0 +1,111 @@ +. + +namespace core\router\schema\response\content; + +use core\router\schema\example; +use core\router\schema\specification; +use GuzzleHttp\Psr7\ServerRequest; + +/** + * Tests for the abstract media type response content container. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\response\content\payload_response_type + */ +final class payload_response_type_test extends \advanced_testcase { + /** + * Test supported types. + */ + public function test_supported_types(): void { + $object = new payload_response_type(); + $this->assertIsArray($object->get_supported_content_types()); + $this->assertContains(json_media_type::class, $object->get_supported_content_types()); + } + + public function test_get_media_type_instance(): void { + $example = new example(name: 'This is my example'); + $object = new payload_response_type(example: $example); + + // Request by mimetype. + $jsoninstance = $object->get_media_type_instance( + mimetype: json_media_type::get_encoding(), + ); + $this->assertInstanceOf(json_media_type::class, $jsoninstance); + + $spec = new specification(); + $this->assertEquals( + $example->get_openapi_description($spec), + $jsoninstance->get_openapi_description($spec)->examples['This is my example'], + ); + + // Request by classname. + $jsoninstance = $object->get_media_type_instance( + classname: json_media_type::class, + ); + $this->assertInstanceOf(json_media_type::class, $jsoninstance); + + $spec = new specification(); + $this->assertEquals( + $example->get_openapi_description($spec), + $jsoninstance->get_openapi_description($spec)->examples['This is my example'], + ); + } + + public function test_description(): void { + $object = new payload_response_type( + description: 'This is a nice description about the content', + ); + + $schema = $object->get_openapi_schema(new specification()); + foreach ($object->get_supported_content_types() as $contenttypeclass) { + $encoding = $contenttypeclass::get_encoding(); + $this->assertObjectHasProperty($encoding, $schema); + $this->assertObjectNotHasProperty('$ref', $schema->{$encoding}); + } + + $this->assertObjectNotHasProperty('$ref', $schema); + } + + public function test_no_media_type_instances(): void { + // Really this should never happen, but it's nice to know that things work if there's a probelm in future. + $object = new class () extends payload_response_type { // phpcs:ignore + #[\Override] + public function get_supported_content_types(): array { + return []; + } + }; + + $this->assertNull($object->get_media_type_instance()); + } + + public function test_required(): void { + // Note: The payload required property is a Moodle check, and not represented in OpenAPI. + $object = new payload_response_type( + required: true, + ); + + $this->assertTrue($object->is_required()); + + $object = new payload_response_type( + required: false, + ); + + $this->assertFalse($object->is_required()); + } +} diff --git a/lib/tests/router/schema/response/payload_response_test.php b/lib/tests/router/schema/response/payload_response_test.php new file mode 100644 index 00000000000..e121a4d50e5 --- /dev/null +++ b/lib/tests/router/schema/response/payload_response_test.php @@ -0,0 +1,81 @@ +. + +namespace core\router\schema\response; + +use core\router\response_handler; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\ServerRequest; + +/** + * Tests for the payload response. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\response\payload_response + * @covers \core\router\schema\response\abstract_response + */ +final class payload_response_test extends route_testcase { + public function test_get_payload(): void { + $request = new ServerRequest('GET', 'http://example.com/example/endpoint'); + + $payload = [ + 'example' => 'data', + 'goes' => 'here', + 'count' => 123, + ]; + + $response = new payload_response($payload, $request); + $this->assertEquals($payload, $response->payload); + $this->assertEquals($request, $response->get_request()); + } + + public function test_basics(): void { + $request = new ServerRequest('GET', 'http://example.com/example/endpoint'); + $response = new Response(); + + $payloaddata = [ + 'example' => 'data', + 'goes' => 'here', + 'count' => 123, + ]; + + $payload = new payload_response($payloaddata, $request, $response); + $this->assertSame($request, $payload->request); + $this->assertSame($response, $payload->response); + } + + public function test_response_standardisation(): void { + $request = new ServerRequest('GET', 'http://example.com/example/endpoint'); + $response = new Response(); + + $payloaddata = [ + 'example' => 'data', + 'goes' => 'here', + 'count' => 123, + ]; + + $payload = new payload_response($payloaddata, $request, $response); + + $handler = new response_handler(\core\di::get_container()); + + // Note: The standardisation itself is tested elsewhere. + $response = $handler->standardise_response($payload); + $this->assertInstanceOf(Response::class, $response); + } +} diff --git a/lib/tests/router/schema/response/response_test.php b/lib/tests/router/schema/response/response_test.php new file mode 100644 index 00000000000..a65490b343f --- /dev/null +++ b/lib/tests/router/schema/response/response_test.php @@ -0,0 +1,152 @@ +. + +namespace core\router\schema\response; + +use core\param; +use core\router\schema\header_object; +use core\router\schema\objects\schema_object; +use core\router\schema\referenced_object; +use core\router\schema\response\content\json_media_type; +use core\router\schema\response\content\payload_response_type; +use core\router\schema\specification; +use core\tests\route_testcase; + +/** + * Tests for the response schema definition. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\response\response + */ +final class response_test extends route_testcase { + public function test_defaults(): void { + $response = new response(); + + // The default status code is 200. + $this->assertSame(200, $response->get_status_code()); + } + + public function test_basics(): void { + $response = new response( + statuscode: 450, + description: 'This is a nice description about the response', + headers: [ + new header_object( + name: 'X-Header', + type: param::ALPHA, + description: 'This is a nice description about the header', + required: true, + ), + ], + content: new payload_response_type( + description: 'This is a nice description about the content', + schema: new schema_object( + content: [ + 'example' => new schema_object(content: []), + ], + ), + ), + ); + + // The default status code is 200. + $this->assertSame(450, $response->get_status_code()); + $schema = $response->get_openapi_schema(new specification()); + + $this->assertObjectHasProperty('description', $schema); + $this->assertEquals('This is a nice description about the response', $schema->description); + + $this->assertObjectHasProperty('headers', $schema); + $this->assertArrayHasKey('X-Header', $schema->headers); + $this->assertObjectHasProperty('description', $schema->headers['X-Header']); + $this->assertEquals('This is a nice description about the header', $schema->headers['X-Header']->description); + $this->assertObjectHasProperty('schema', $schema->headers['X-Header']); + + $this->assertObjectHasProperty('content', $schema); + } + + public function test_default_200_description(): void { + $response = new response( + statuscode: 200, + ); + + // The default status code is 200. + $this->assertSame(200, $response->get_status_code()); + $schema = $response->get_openapi_schema(new specification()); + $this->assertObjectHasProperty('description', $schema); + $this->assertIsString($schema->description); + $this->assertEquals('OK', $schema->description); + } + + public function test_array_content(): void { + $response = new response( + statuscode: 450, + content: [ + new json_media_type( + schema: new schema_object( + content: [ + 'example' => new schema_object(content: []), + ], + ), + ), + ], + ); + + // The default status code is 200. + $this->assertSame(450, $response->get_status_code()); + $schema = $response->get_openapi_schema(new specification()); + $this->assertObjectHasProperty('description', $schema); + $this->assertIsString($schema->description); + + $this->assertObjectHasProperty('content', $schema); + $this->assertArrayHasKey('application/json', $schema->content); + } + + public function test_invalid_content(): void { + $this->expectException(\coding_exception::class); + + $response = new response( + content: [ + new schema_object( + content: [], + ), + ], + ); + $response->get_openapi_schema(new specification()); + } + + /** + * Tests for object references. + * + * @covers \core\router\schema\openapi_base + */ + public function test_referenced_object(): void { + $object = new class ( + statuscode: 499, + ) extends response implements referenced_object { + }; + + // Note: The status code is not in the OpenAPI schema, but in the parent. + $schema = $object->get_openapi_description(new specification()); + $this->assertObjectNotHasProperty('$ref', $schema); + $this->assertObjectHasProperty('description', $schema); + + $reference = $object->get_openapi_schema(new specification()); + $this->assertObjectNotHasProperty('description', $reference); + $this->assertObjectHasProperty('$ref', $reference); + } +} diff --git a/lib/tests/router/schema/response/view_response_test.php b/lib/tests/router/schema/response/view_response_test.php new file mode 100644 index 00000000000..cd15dfd6c6c --- /dev/null +++ b/lib/tests/router/schema/response/view_response_test.php @@ -0,0 +1,63 @@ +. + +namespace core\router\schema\response; + +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; + +/** + * Tests for the view_response response type. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\response\view_response + */ +final class view_response_test extends route_testcase { + public function test_defaults(): void { + $response = new response(); + + // The default status code is 200. + $this->assertSame(200, $response->get_status_code()); + } + + public function test_get_response(): void { + global $OUTPUT; + + $request = new ServerRequest('GET', 'http://example.com'); + + $response = new view_response( + template: 'core/welcome', + parameters: [ + 'welcomemessage' => 'Hello, everybody!', + ], + request: $request, + ); + + $this->assertEquals('core/welcome', $response->get_template_name()); + $this->assertEquals( + ['welcomemessage' => 'Hello, everybody!'], + $response->get_parameters(), + ); + + $this->assertEquals($request, $response->get_request()); + $this->assertEquals( + $OUTPUT->render_from_template('core/welcome', ['welcomemessage' => 'Hello, everybody!']), + $response->get_response($this->get_router()->get_response_factory())->getBody(), + ); + } +} diff --git a/lib/tests/router/schema/specification_test.php b/lib/tests/router/schema/specification_test.php new file mode 100644 index 00000000000..0830fb03820 --- /dev/null +++ b/lib/tests/router/schema/specification_test.php @@ -0,0 +1,353 @@ +. + +namespace core\router\schema; + +use core\param; +use core\router\route; +use core\router\schema\parameters\path_parameter; +use core\router\schema\response\content\payload_response_type; +use core\router\schema\response\response; +use core\router\schema\specification; +use core\tests\route_testcase; + +/** + * Tests for the specification. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\schema\specification + */ +final class specification_test extends route_testcase { + public function test_basics(): void { + global $CFG; + + $spec = new specification(); + $schema = $spec->get_schema(); + + $this->assertIsObject($schema); + + // We comply with OpenAPI 3.1.0. + $this->assertObjectHasProperty('openapi', $schema); + $this->assertEquals('3.1.0', $schema->openapi); + + // INfo should include our license. + $this->assertObjectHasProperty('info', $schema); + $this->assertObjectHasProperty('license', $schema->info); + $this->assertStringContainsString('GNU GPL v3 or later', $schema->info->license->name); + $this->assertObjectHasProperty('url', $schema->info->license); + + // The server list should contain the currenet URI during finalisation. + $this->assertObjectHasProperty('servers', $schema); + $this->assertIsArray($schema->servers); + $this->assertCount(1, $schema->servers); + $server = $schema->servers[0]; + $this->assertStringStartsWith($CFG->wwwroot, $server->url); + + $this->assertObjectHasProperty('paths', $schema); + $this->assertObjectHasProperty('components', $schema); + $this->assertObjectHasProperty('security', $schema); + $this->assertObjectHasProperty('externalDocs', $schema); + + // Calculated parameters should only be set once. + $schema = $spec->get_schema(); + $this->assertCount(1, $schema->servers); + + $this->assertJson(json_encode($spec)); + } + + /** + * Test the add_path method. + * + * @dataProvider add_path_provider + * @param string $component + * @param string $path + * @param string $expectedpath + */ + public function test_add_path( + string $component, + string $path, + string $expectedpath, + ): void { + $spec = new specification(); + + $spec->add_path( + $component, + new route( + path: $path, + ), + ); + + $schema = $spec->get_schema(); + $this->assertObjectHasProperty($expectedpath, $schema->paths); + } + + /** + * Data provider for add_path. + * + * @return array + */ + public static function add_path_provider(): array { + return [ + 'Core' => [ + 'core', + '/example/path', + '/core/example/path', + ], + 'Core Subsystem' => [ + 'core_access', + '/example/path', + '/access/example/path', + ], + 'An activity' => [ + 'mod_assign', + '/example/path', + '/mod_assign/example/path', + ], + ]; + } + + public function test_add_path_with_option(): void { + $spec = new specification(); + + $spec->add_path( + 'core', + new route( + path: '/example/path/with[/{option}]', + pathtypes: [ + new path_parameter(name: 'option', type: param::INT), + ], + ), + ); + + $schema = $spec->get_schema(); + $this->assertObjectHasProperty('/core/example/path/with', $schema->paths); + $this->assertObjectHasProperty('/core/example/path/with/{option}', $schema->paths); + } + + public function test_add_path_with_options(): void { + $spec = new specification(); + + $spec->add_path( + 'core', + new route( + path: '/example/path/with[/{optional}][/{extras}]', + pathtypes: [ + new path_parameter(name: 'optional', type: param::INT), + new path_parameter(name: 'extras', type: param::INT), + ], + ), + ); + + $schema = $spec->get_schema(); + $this->assertObjectHasProperty('/core/example/path/with', $schema->paths); + $this->assertObjectHasProperty('/core/example/path/with/{optional}', $schema->paths); + $this->assertObjectHasProperty('/core/example/path/with/{optional}/{extras}', $schema->paths); + } + + public function test_add_parameter(): void { + $spec = new specification(); + + /** @var path_parameter&\PHPUnit\Framework\MockObject\MockObject $child */ + $child = $this->getMockBuilder(path_parameter::class) + ->onlyMethods([]) + ->setConstructorArgs([ + 'name' => 'example', + 'type' => param::INT, + ]) + ->getMock(); + + $this->assertFalse($spec->is_reference_defined($child->get_reference(true))); + + $spec->add_component($child); + + $schema = $spec->get_schema(); + $this->assertObjectHasProperty($child->get_reference(false), $schema->components->parameters); + $this->assertTrue($spec->is_reference_defined($child->get_reference(true))); + } + + public function test_add_header(): void { + $spec = new specification(); + + /** @var header_object&\PHPUnit\Framework\MockObject\MockObject $child */ + $child = $this->getMockBuilder(header_object::class) + ->onlyMethods([]) + ->setConstructorArgs([ + 'name' => 'example', + 'type' => param::INT, + ]) + ->getMock(); + + $this->assertFalse($spec->is_reference_defined($child->get_reference(true))); + + $spec->add_component($child); + + $schema = $spec->get_schema(); + $this->assertObjectHasProperty($child->get_reference(false), $schema->components->headers); + $this->assertTrue($spec->is_reference_defined($child->get_reference(true))); + } + + public function test_add_response(): void { + $spec = new specification(); + + /** @var response&\PHPUnit\Framework\MockObject\MockObject $child */ + $child = $this->getMockBuilder(response::class) + ->onlyMethods([]) + ->setConstructorArgs([ + ]) + ->getMock(); + + $this->assertFalse($spec->is_reference_defined($child->get_reference(true))); + + $spec->add_component($child); + + $schema = $spec->get_schema(); + $this->assertObjectHasProperty($child->get_reference(false), $schema->components->responses); + $this->assertTrue($spec->is_reference_defined($child->get_reference(true))); + } + + public function test_add_example(): void { + $spec = new specification(); + + /** @var example&\PHPUnit\Framework\MockObject\MockObject $child */ + $child = $this->getMockBuilder(example::class) + ->onlyMethods([]) + ->setConstructorArgs([ + 'name' => 'An excellent example', + ]) + ->getMock(); + $this->assertFalse($spec->is_reference_defined($child->get_reference(true))); + + $spec->add_component($child); + + $schema = $spec->get_schema(); + $this->assertObjectHasProperty($child->get_reference(false), $schema->components->examples); + $this->assertTrue($spec->is_reference_defined($child->get_reference(true))); + } + + public function test_add_request_body(): void { + $spec = new specification(); + + $child = new request_body( + description: 'example', + required: true, + ); + + $this->assertFalse($spec->is_reference_defined($child->get_reference(true))); + + $spec->add_component($child); + + $schema = $spec->get_schema(); + $this->assertObjectHasProperty($child->get_reference(false), $schema->components->requestBodies); + + $this->assertTrue($spec->is_reference_defined($child->get_reference(true))); + + $requestschema = $spec->get_openapi_schema_for_route( + route: new route( + path: '/example/path', + requestbody: $child, + ), + component: '', + path: '/example/path', + ); + + $this->assertObjectHasProperty('requestBody', $requestschema->get); + $this->assertEquals('example', $requestschema->get->requestBody->description); + $this->assertTrue($requestschema->get->requestBody->required); + } + + public function test_route_security_in_schema(): void { + $spec = new specification(); + + $route = new route( + path: '/example/path', + security: ['example'], + ); + + $spec->add_path( + 'core', + $route, + ); + + $requestschema = $spec->get_openapi_schema_for_route( + route: $route, + component: '', + path: '/example/path', + ); + + $this->assertObjectHasProperty('security', $requestschema->get); + $this->assertEquals(['example'], $requestschema->get->security); + } + + public function test_is_reference_defined(): void { + $spec = new specification(); + $this->assertFalse($spec->is_reference_defined('example')); + $this->assertFalse($spec->is_reference_defined('#/components/fake/component')); + } + + public function test_deprecated_route(): void { + $spec = new specification(); + $route = new route( + path: '/example/path', + deprecated: true, + ); + + $spec->add_path( + 'core', + $route, + ); + + $requestschema = $spec->get_openapi_schema_for_route( + route: $route, + component: '', + path: '/example/path', + ); + + $this->assertObjectHasProperty('deprecated', $requestschema->get); + $this->assertTrue($requestschema->get->deprecated); + } + + public function test_response(): void { + $spec = new specification(); + $route = new route( + path: '/example/path', + responses: [ + new response( + status: 200, + description: 'example', + content: new payload_response_type(), + ), + ], + ); + + $spec->add_path( + 'core', + $route, + ); + + $requestschema = $spec->get_openapi_schema_for_route( + route: $route, + component: '', + path: '/example/path', + ); + + $this->assertObjectHasProperty('responses', $requestschema->get); + $this->assertArrayHasKey('200', $requestschema->get->responses); + $this->assertObjectHasProperty('content', $requestschema->get->responses['200']); + $this->assertObjectHasProperty('application/json', $requestschema->get->responses['200']->content); + } +} diff --git a/lib/tests/router/util_test.php b/lib/tests/router/util_test.php new file mode 100644 index 00000000000..99f3c8c7b15 --- /dev/null +++ b/lib/tests/router/util_test.php @@ -0,0 +1,153 @@ +. + +namespace core\router; + +use core\router\middleware\moodle_route_attribute_middleware; +use core\tests\route_testcase; +use GuzzleHttp\Psr7\ServerRequest; +use Slim\Middleware\RoutingMiddleware; + +/** + * Tests for the route utility class. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router\util + */ +final class util_test extends route_testcase { + /** + * Ensure that no error is thrown when getting a route instance for a callable. + */ + public function test_get_route_instance_for_method_not_array_callable(): void { + $this->assertNull(util::get_route_instance_for_method(fn () => null)); + } + + /** + * Test getting the path for a callable. + */ + public function test_get_path_for_callable(): void { + self::load_fixture('core', 'router/route_on_class.php'); + + $this->add_route_to_route_loader( + \core\fixtures\route_on_class::class, + 'method_with_route', + grouppath: '/example', + ); + + $url = util::get_path_for_callable( + [\core\fixtures\route_on_class::class, 'method_with_route'], + [], + [], + ); + + $parsedurl = parse_url($url); + $this->assertEquals( + (new \moodle_url('/example/class/path/method/path'))->get_path(), + $parsedurl['path'], + ); + } + + public function test_get_route_instance_for_method(): void { + self::load_fixture('core', 'router/route_on_method_only.php'); + self::load_fixture('core', 'router/route_on_class.php'); + + // The class has no route attribute. + + // Test a method that has no route attribute. + $this->assertNull(util::get_route_instance_for_method('core\fixtures\route_on_method_only::method_without_route')); + $this->assertNull(util::get_route_instance_for_method(['core\fixtures\route_on_method_only', 'method_without_route'])); + + // Test a method that has a route attribute. + $this->assert_route_callable_data( + 'core\fixtures\route_on_method_only::method_with_route', + '/method/path', + 'core\fixtures\route_on_method_only::method_with_route', + ); + $this->assert_route_callable_data( + ['core\fixtures\route_on_method_only', 'method_with_route'], + '/method/path', + 'core\fixtures\route_on_method_only::method_with_route', + ); + + // The class has a route attribute. + + // Test a method that has no route attribute. + $this->assertNull(util::get_route_instance_for_method('core\fixtures\route_on_class::method_without_route')); + $this->assertNull(util::get_route_instance_for_method(['core\fixtures\route_on_class', 'method_without_route'])); + + // Test a method that has a route attribute - it is merged with parent. + $this->assert_route_callable_data( + 'core\fixtures\route_on_class::method_with_route', + '/class/path/method/path', + 'core\fixtures\route_on_class::method_with_route', + ); + $this->assert_route_callable_data( + ['core\fixtures\route_on_class', 'method_with_route'], + '/class/path/method/path', + 'core\fixtures\route_on_class::method_with_route', + ); + } + + /** + * Assertion helper to asser that a callable is a route and has the expected path and name. + * + * @param callable $callable The callable to check. + * @param string $path The expected path. + * @param string $routename The expected route name. + */ + protected function assert_route_callable_data( + $callable, + string $path, + string $routename, + ): void { + $route = util::get_route_instance_for_method($callable); + $this->assertInstanceOf(route::class, $route); + $this->assertEquals($path, $route->get_path()); + $this->assertIsString(util::get_route_name_for_callable($callable)); + $this->assertEquals($routename, util::get_route_name_for_callable($callable)); + } + + /** + * Test getting the route name for an anonymous callable. + */ + public function test_get_route_for_callable_not_array_callable(): void { + $this->expectException(\coding_exception::class); + $this->assertNull(util::get_route_name_for_callable(fn () => null)); + } + + public function test_get_route_instance_for_request(): void { + self::load_fixture('core', 'router/route_on_method_only.php'); + + $app = $this->get_simple_app(); + $app->add(moodle_route_attribute_middleware::class); + $app->addRoutingMiddleware(); + $app->get('/method/path', [\core\fixtures\route_on_method_only::class, 'method_with_route']); + $app->handle(new ServerRequest('GET', '/method/path')); + + $request = $this->route_request($app, new ServerRequest('GET', '/method/path')); + + $route = util::get_route_instance_for_request($request); + + $this->assertInstanceOf(route::class, $route); + $this->assertEquals('/method/path', $route->get_path()); + + $secondroute = util::get_route_instance_for_request($request); + $this->assertInstanceOf(route::class, $secondroute); + $this->assertEquals('/method/path', $secondroute->get_path()); + } +} diff --git a/lib/tests/router_test.php b/lib/tests/router_test.php new file mode 100644 index 00000000000..f10f19feb60 --- /dev/null +++ b/lib/tests/router_test.php @@ -0,0 +1,109 @@ +. + +namespace core; + +use core\tests\route_testcase; +use Slim\App; + +/** + * Tests for the router class. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\router + * @covers \core\router\response_handler + */ +final class router_test extends route_testcase { + public function test_get_app(): void { + $router = $this->get_router('/example'); + $app = $router->get_app(); + $this->assertInstanceOf(App::class, $app); + + $this->assertEquals(di::get_container(), $app->getContainer()); + } + + public function test_request_normalisation(): void { + $router = $this->get_router(''); + + // Create handlers for the routes. + // Note: These must all be created before any are accessed as the data is cached after first use. + $app = $router->get_app(); + $app->get('/test/path', fn ($response) => $response->withStatus(299)); + $app->get('/', fn ($response) => $response->withStatus(275)); + $app->get('/test/otherpath', fn ($response) => $response->withStatus(250)); + + // Duplicate slashes. + $request = $this->create_request('GET', '/test//path', ''); + $response = $router->handle_request($request); + $this->assertEquals(299, $response->getStatusCode()); + + // An empty route. + $request = $this->create_request('GET', '', ''); + $response = $router->handle_request($request); + $this->assertEquals(275, $response->getStatusCode()); + + // A route with a trailing slash. + $request = $this->create_request('GET', '/test/otherpath/', ''); + $response = $router->handle_request($request); + $this->assertEquals(250, $response->getStatusCode()); + + // A route with a trailing double slash. + $request = $this->create_request('GET', '/test/otherpath/////', ''); + $response = $router->handle_request($request); + $this->assertEquals(250, $response->getStatusCode()); + } + + /** + * Test an API route. + */ + public function test_preferences_no_login(): void { + $this->add_class_routes_to_route_loader(\core_user\route\api\preferences::class); + $response = $this->process_api_request('GET', '/current/preferences'); + + $this->assert_valid_response($response); + $payload = $this->decode_response($response); + + $this->assertEmpty((array) $payload); + } + + public function test_basepath_supplied(): void { + $router = $this->get_router( + basepath: '/example', + ); + $this->assertEquals('/example', $router->basepath); + } + + public function test_basepath_guessed(): void { + global $CFG; + + $wwwroot = new \moodle_url($CFG->wwwroot); + $router = di::get(router::class); + + $this->assertEquals($wwwroot->get_path(), $router->basepath); + } + + public function test_basepath_guessed_rphp(): void { + $wwwroot = new \moodle_url('/r.php'); + $_SERVER['SCRIPT_FILENAME'] = 'r.php'; + $_SERVER['REQUEST_URI'] = $wwwroot->get_path(); + + $router = di::get(router::class); + + $this->assertEquals($wwwroot->get_path(), $router->basepath); + } +} diff --git a/lib/weblib.php b/lib/weblib.php index e1a0bd345af..2872cf6b647 100644 --- a/lib/weblib.php +++ b/lib/weblib.php @@ -2081,7 +2081,7 @@ function notice ($message, $link='', $course=null) { * @param string $messagetype The type of notification to show the message in. See constants on \core\output\notification. * @throws moodle_exception */ -function redirect($url, $message='', $delay=null, $messagetype = \core\output\notification::NOTIFY_INFO) { +function redirect($url, $message='', $delay=null, $messagetype = \core\output\notification::NOTIFY_INFO): Never { global $OUTPUT, $PAGE, $CFG; if (CLI_SCRIPT or AJAX_SCRIPT) { diff --git a/r.php b/r.php new file mode 100644 index 00000000000..3292af8f6be --- /dev/null +++ b/r.php @@ -0,0 +1,32 @@ +. + +/** + * Routing support for Moodle. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +// phpcs:disable moodle.Files.MoodleInternal.MoodleInternalGlobalState + +// Load the bootstrap and perform the bare early setup. +// This just sets up the autoloaders, basic configuration, and so on. +define('ABORT_AFTER_CONFIG', true); +require_once('config.php'); + +$router = \core\di::get(\core\router::class); +$router->serve(); diff --git a/user/tests/route/api/preferences_test.php b/user/tests/route/api/preferences_test.php new file mode 100644 index 00000000000..294d1f8a7f5 --- /dev/null +++ b/user/tests/route/api/preferences_test.php @@ -0,0 +1,96 @@ +. + +namespace core_user\route\api; + +use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\Utils; + +/** + * Tests for user preference API handler. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core_user\route\api\preferences + * @covers \core_user\route\responses\user_preferences_response + */ +class preferences_test extends \route_testcase { + /** + * Ensure that preferences returned for a user without login are empty. + */ + public function test_preferences_no_login(): void { + $response = $this->process_request('GET', '/user/preferences'); + + $this->assert_valid_response($response); + $payload = $this->decode_response($response); + + $this->assertEmpty((array) $payload); + } + + /** + * Test that the preferences are returned when logged in. + */ + public function test_preferences_returned(): void { + $this->resetAfterTest(); + + $this->setAdminUser(); + set_user_preference('testpreference', 'testvalue'); + + $response = $this->process_request('GET', '/user/preferences'); + + $this->assert_valid_response($response); + + $payload = $this->decode_response($response); + + $this->assertObjectHasAttribute('testpreference', $payload); + $this->assertEquals('testvalue', $payload->testpreference); + } + + public function test_preference_returned(): void { + $this->resetAfterTest(); + + $this->setAdminUser(); + set_user_preference('testpreference', 'testvalue'); + + $response = $this->process_request('GET', '/user/preferences/testpreference'); + + $this->assert_valid_response($response); + + $payload = $this->decode_response($response); + + $this->assertObjectHasAttribute('testpreference', $payload); + $this->assertEquals('testvalue', $payload->testpreference); + } + + public function test_preferences_set(): void { + $this->resetAfterTest(); + + $request = $this->create_request( + 'POST', + '/user/preferences', + )->withBody( + Utils::streamFor(json_encode([ + 'preferences' => [ + 'testpreference' => 'someothervalue', + ], + ])), + ); + + $app = $this->get_app(); + $response = $app->handle($request); + } +} From 614b3bf9b33fa887fdf5ee77b37ae3d0346da31a Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 31 Oct 2023 10:15:40 +0800 Subject: [PATCH 10/16] MDL-81031 core: Add some new WS --- lib/classes/route/api/templates.php | 157 ++++++++++ lib/tests/route/api/templates_test.php | 112 +++++++ user/classes/route/api/preferences.php | 284 ++++++++++++++++++ .../responses/user_preferences_response.php | 61 ++++ user/tests/route/api/preferences_test.php | 244 +++++++++++++-- 5 files changed, 836 insertions(+), 22 deletions(-) create mode 100644 lib/classes/route/api/templates.php create mode 100644 lib/tests/route/api/templates_test.php create mode 100644 user/classes/route/api/preferences.php create mode 100644 user/classes/route/responses/user_preferences_response.php diff --git a/lib/classes/route/api/templates.php b/lib/classes/route/api/templates.php new file mode 100644 index 00000000000..867c145e026 --- /dev/null +++ b/lib/classes/route/api/templates.php @@ -0,0 +1,157 @@ +. + +namespace core\route\api; + +use core\exception; +use core\param; +use core\router\route; +use core\output\mustache_template_source_loader; +use core\router\schema\response\payload_response; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +/** + * Template Controller. + * + * @package core + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class templates { + use \core\router\route_controller; + + /** + * Fetch a single template for a component in a theme. + * + * @param ResponseInterface $response + * @param string $themename + * @param string $component + * @param null|string $identifier + * @return payload_response + */ + #[route( + path: '/templates/{themename}/{component}/{identifier}', + method: ['GET'], + title: 'Fetch a single template', + description: 'Fetch a single template for a component in a theme', + security: [], + pathtypes: [ + new \core\router\parameters\path_themename(), + new \core\router\parameters\path_component(), + new \core\router\schema\parameters\path_parameter( + name: 'identifier', + type: param::SAFEPATH, + ), + ], + queryparams: [ + new \core\router\schema\parameters\query_parameter( + name: 'includecomments', + type: param::BOOL, + description: 'Include comments in the template', + default: false, + ), + ], + headerparams: [ + new \core\router\parameters\header_language(), + ], + responses: [ + new \core\router\schema\response\response( + statuscode: 200, + description: 'OK', + content: [ + new \core\router\schema\response\content\json_media_type( + schema: new \core\router\schema\objects\schema_object( + content: [ + 'templates' => new \core\router\schema\objects\array_of_strings( + keyparamtype: param::TEXT, + valueparamtype: param::RAW, + ), + 'strings' => new \core\router\schema\objects\array_of_strings( + keyparamtype: param::TEXT, + valueparamtype: param::RAW, + ), + ], + ), + examples: [ + new \core\router\schema\example( + name: 'Single template value', + summary: 'A json response containing the template for a single template', + value: [ + 'templates' => [ + "mod_example/template_identifier" => "
Hello World
", + "mod_example/other_template" => "
Hello World
", + ], + 'strings' => [ + 'core/loading' => 'Loading', + ], + ], + ), + ] + ), + ], + ), + ], + )] + public function get_templates( + ServerRequestInterface $request, + ResponseInterface $response, + mustache_template_source_loader $loader, + string $themename, + string $component, + string $identifier, + ): payload_response { + global $PAGE; + + $PAGE->set_context(\core\context\system::instance()); + + $params = $request->getQueryParams(); + $comments = $params['includecomments']; + + try { + $dependencies = $loader->load_with_dependencies( + templatecomponent: $component, + templatename: $identifier, + themename: $themename, + includecomments: $comments, + lang: $request->getHeaderLine('language'), + ); + } catch (\moodle_exception $e) { + throw new exception\not_found_exception('template', "{$component}/{$identifier}"); + } + + $result = [ + 'templates' => [], + 'strings' => [], + ]; + + foreach ($dependencies['templates'] as $component => $templates) { + foreach ($templates as $template => $value) { + $result['templates']["{$component}/{$template}"] = $value; + } + } + foreach ($dependencies['strings'] as $component => $templates) { + foreach ($templates as $template => $value) { + $result['strings']["{$component}/{$template}"] = $value; + } + } + + return new payload_response( + payload: $result, + request: $request, + ); + } +} diff --git a/lib/tests/route/api/templates_test.php b/lib/tests/route/api/templates_test.php new file mode 100644 index 00000000000..3a189cc83fe --- /dev/null +++ b/lib/tests/route/api/templates_test.php @@ -0,0 +1,112 @@ +. + +namespace core\route\api; + +use core\tests\route_testcase; + +/** + * Tests for Templates API. + * + * @package core + * @category test + * @copyright 2024 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \core\route\api\templates + */ +final class templates_test extends route_testcase { + /** + * Test fetching templates. + * + * Note: This is a risky test because it relies on data in other parts of Moodle. + * + * @dataProvider fetch_templates_provider + * @param string $path + * @param array $requiredtemplates + * @param array $requiredstrings + */ + public function test_fetch_known_templates( + string $path, + array $requiredtemplates, + array $requiredstrings, + ): void { + $this->add_class_routes_to_route_loader(\core\route\api\templates::class); + $response = $this->process_api_request('GET', "/templates/{$path}"); + + $this->assert_valid_response($response); + $payload = $this->decode_response($response, true); + + $this->assert_payload_contains($payload, $requiredtemplates, $requiredstrings); + } + + /** + * Data propvider for template tests. + * + * @return array + */ + public static function fetch_templates_provider(): array { + return [ + 'fetch single template' => [ + 'boost/core/modal', + ['core/modal'], + [], + ], + 'foo' => [ + 'boost/core/notification', + [ + 'core/notification', + 'core/notification_success', + 'core/notification_warning', + 'core/notification_error', + 'core/notification_info', + ], + [ + 'core/dismissnotification', + ], + ], + ]; + } + + /** + * Assertthat the payload contains the required templates and strings. + * + * @param array $payload + * @param array $requiredtemplates + * @param array $requiredstrings + */ + protected function assert_payload_contains( + array $payload, + array $requiredtemplates = [], + array $requiredstrings = [], + ): void { + $this->assertArrayHasKey('templates', $payload); + $this->assertArrayHasKey('strings', $payload); + + foreach ($requiredtemplates as $template) { + $this->assertArrayHasKey($template, $payload['templates']); + } + foreach ($requiredstrings as $string) { + $this->assertArrayHasKey($string, $payload['strings']); + } + } + + public function test_template_missing(): void { + $this->add_class_routes_to_route_loader(\core\route\api\templates::class); + $response = $this->process_api_request('GET', '/templates/boost/core/missing'); + + $this->assert_not_found_response($response); + } +} diff --git a/user/classes/route/api/preferences.php b/user/classes/route/api/preferences.php new file mode 100644 index 00000000000..ff30b37dc90 --- /dev/null +++ b/user/classes/route/api/preferences.php @@ -0,0 +1,284 @@ +. + +namespace core_user\route\api; + +use core\exception\coding_exception; +use core\exception\invalid_parameter_exception; +use core\param; +use core\router\route; +use core\router\schema\objects\scalar_type; +use core\router\schema\response\payload_response; +use core\router\schema\response\content\payload_response_type; +use core\router\schema\response\response_type; +use core\user; +use core_user\route\responses\user_preferences_response; +use stdClass; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; + +/** + * User preference API handler. + * + * @package core_user + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +#[route( + path: '/{user}/preferences', + pathtypes: [ + new \core\router\parameters\path_user(), + ], +)] +class preferences { + /** + * Fetch all user preferences, or a specific user preference. + * + * @param ResponseInterface $response + * @param ServerRequestInterface $request + * @param stdClass $user + * @param null|string $preference + * @return payload_response + */ + #[route( + path: '[/{preference}]', + title: 'Fetch user preferences', + description: 'Fetch one user preference, or all user preferences', + pathtypes: [ + new \core\router\schema\parameters\path_parameter( + name: 'preference', + type: param::RAW, + ), + ], + responses: [ + new user_preferences_response(), + ], + )] + public function get_preferences( + ResponseInterface $response, + ServerRequestInterface $request, + stdClass $user, + ?string $preference, + ): payload_response { + $this->check_user($user); + + $result = get_user_preferences( + name: $preference, + user: $user, + ); + + if (!is_array($result)) { + // Check if we received just one preference. + $result = [$preference => $result]; + } + + return new payload_response($result, $request, $response); + } + + /** + * Set a set of user preferences. + * + * @param ResponseInterface $response + * @param stdClass $user + * @return payload_response + */ + #[route( + method: ['POST'], + title: 'Set or update multiple user preferences', + requestbody: new \core\router\schema\request_body( + content: new payload_response_type( + schema: new \core\router\schema\objects\schema_object( + content: [ + 'preferences' => new \core\router\schema\objects\array_of_strings( + keyparamtype: param::TEXT, + valueparamtype: param::RAW, + ), + ], + ), + ), + ), + responses: [ + new user_preferences_response(), + ], + )] + public function set_preferences( + ResponseInterface $response, + ServerRequestInterface $request, + stdClass $user, + ): payload_response { + $this->check_user($user); + + $values = $request->getParsedBody(); + $preferences = $values['preferences'] ?? []; + + foreach ($preferences as $preference => $value) { + $this->set_single_preference($user, $preference, $value); + } + + $result = array_filter( + get_user_preferences( + user: $user, + ), + fn ($preference) => array_key_exists($preference, $preferences), + ARRAY_FILTER_USE_KEY, + ); + + return new payload_response($result, $request, $response); + } + + /** + * Set a single user preference. + * + * @param ResponseInterface $response + * @param string $themename + * @param string $component + * @param null|string $identifier + * @return response_type + */ + #[route( + path: '/{preference}', + method: ['POST'], + title: 'Set a single user preference', + description: 'Set a single user preference', + pathtypes: [ + new \core\router\schema\parameters\path_parameter( + name: 'preference', + type: param::RAW, + ), + ], + requestbody: new \core\router\schema\request_body( + content: new payload_response_type( + schema: new \core\router\schema\objects\schema_object( + content: [ + 'value' => new scalar_type(param::RAW), + ], + ), + ), + ), + responses: [ + new \core\router\schema\response\response( + statuscode: 200, + description: 'OK', + content: [ + new \core\router\schema\response\content\json_media_type( + schema: new \core\router\schema\objects\array_of_strings( + keyparamtype: param::TEXT, + valueparamtype: param::RAW, + ), + examples: [ + new \core\router\schema\example( + name: 'A single preference value', + summary: 'A json response containing a single preference', + value: [ + "drawers-open-index" => "1", + ], + ), + ] + ), + ], + ), + ], + )] + public function set_preference( + ResponseInterface $response, + ServerRequestInterface $request, + stdClass $user, + ?string $preference, + ): response_type { + $this->check_user($user); + + $values = $request->getParsedBody(); + $value = $values['value'] ?? null; + $this->set_single_preference($user, $preference, $value); + + return $this->get_preferences($response, $request, $user, $preference); + } + + /** + * Set a single user preference. + * + * @param \stdClass $user + * @param string $preference + * @param mixed $value + * @throws \core\exception\access_denied_exception + * @throws \invalid_parameter_exception + */ + protected function set_single_preference( + stdClass $user, + string $preference, + mixed $value, + ): void { + try { + $definition = user::get_preference_definition($preference); + } catch (coding_exception $e) { + throw new invalid_parameter_exception("Invalid preference '$preference'"); + } + + if (!user::can_edit_preference($preference, $user)) { + throw new \core\exception\access_denied_exception('You do not have permission to edit this preference.'); + } + + if (isset($definition['type'])) { + $type = param::from_type($definition['type']); + $value = $this->standardise_value($type, $value); + } + + $cleanvalue = user::clean_preference($value, $preference); + if ($cleanvalue !== $value) { + throw new \invalid_parameter_exception("Invalid value for preference '$preference': '{$value}'"); + } + $value = $cleanvalue; + set_user_preference($preference, $value, $user->id); + } + + /** + * Ensure that the requested user meets the requirements. + * + * @param stdClass $user + * @throws invalid_parameter_exception + */ + protected function check_user(stdClass $user): void { + global $USER; + + if ($user->id !== $USER->id) { + throw new \core\exception\access_denied_exception( + 'You do not have permission to view or edit preferences for other users.', + ); + } + } + + /** + * Standardise value based on type. + * + * Note: We cannot use \core\param here because we only want to cast some types. + * Requests do not have an inherent understanding of anything but strings. We need to be strict on typing of integers and bools. + * + * @param string param $type + * @param mixed $value + * @return mixed + */ + protected function standardise_value(param $type, mixed $value): mixed { + if (is_numeric($value) || is_bool($value)) { + switch ($type) { + case param::INT: + case param::BOOL: + $value = (int) $value; + } + } + + return $value; + } +} diff --git a/user/classes/route/responses/user_preferences_response.php b/user/classes/route/responses/user_preferences_response.php new file mode 100644 index 00000000000..8f5c5f8b72f --- /dev/null +++ b/user/classes/route/responses/user_preferences_response.php @@ -0,0 +1,61 @@ +. + +namespace core_user\route\responses; + +use core\param; +use core\router\schema\response\content\payload_response_type; + +/** + * A standard response for user preferences. + * + * @package core_user + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class user_preferences_response extends \core\router\schema\response\response { + /** + * Constructor for a standard user preference response. + */ + public function __construct() { + parent::__construct( + content: new payload_response_type( + schema: new \core\router\schema\objects\array_of_strings( + keyparamtype: param::TEXT, + valueparamtype: param::RAW, + ), + examples: [ + new \core\router\schema\example( + name: 'A single preference value', + summary: 'A json response containing a single preference', + value: [ + "drawers-open-index" => "1", + ], + ), + new \core\router\schema\example( + name: 'A set of preference values', + summary: 'A json response containing a set of preferences', + value: [ + "drawers-open-index" => "1", + "login_failed_count_since_success" => "1", + "coursesectionspreferences_2" => "{\"contentcollapsed\":[]}", + ], + ), + ] + ), + ); + } +} diff --git a/user/tests/route/api/preferences_test.php b/user/tests/route/api/preferences_test.php index 294d1f8a7f5..a12ec3f5ab4 100644 --- a/user/tests/route/api/preferences_test.php +++ b/user/tests/route/api/preferences_test.php @@ -16,24 +16,25 @@ namespace core_user\route\api; -use GuzzleHttp\Psr7\Response; +use core\tests\route_testcase; use GuzzleHttp\Psr7\Utils; /** * Tests for user preference API handler. * - * @package core - * @copyright 2023 Andrew Lyons + * @package core_user + * @copyright Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @covers \core_user\route\api\preferences * @covers \core_user\route\responses\user_preferences_response */ -class preferences_test extends \route_testcase { +final class preferences_test extends route_testcase { /** * Ensure that preferences returned for a user without login are empty. */ public function test_preferences_no_login(): void { - $response = $this->process_request('GET', '/user/preferences'); + $this->add_class_routes_to_route_loader(preferences::class); + $response = $this->process_api_request('GET', '/current/preferences'); $this->assert_valid_response($response); $payload = $this->decode_response($response); @@ -47,50 +48,249 @@ class preferences_test extends \route_testcase { public function test_preferences_returned(): void { $this->resetAfterTest(); - $this->setAdminUser(); - set_user_preference('testpreference', 'testvalue'); + $this->add_class_routes_to_route_loader(preferences::class); - $response = $this->process_request('GET', '/user/preferences'); + $this->setAdminUser(); + set_user_preference('filemanager_recentviewmode', 1); + + $response = $this->process_api_request('GET', '/current/preferences'); $this->assert_valid_response($response); $payload = $this->decode_response($response); - $this->assertObjectHasAttribute('testpreference', $payload); - $this->assertEquals('testvalue', $payload->testpreference); + $this->assertObjectHasProperty('filemanager_recentviewmode', $payload); + $this->assertEquals(1, $payload->filemanager_recentviewmode); } public function test_preference_returned(): void { $this->resetAfterTest(); - $this->setAdminUser(); - set_user_preference('testpreference', 'testvalue'); + $this->add_class_routes_to_route_loader(preferences::class); - $response = $this->process_request('GET', '/user/preferences/testpreference'); + $this->setAdminUser(); + set_user_preference('filemanager_recentviewmode', 1); + + $response = $this->process_api_request('GET', '/current/preferences/filemanager_recentviewmode'); $this->assert_valid_response($response); $payload = $this->decode_response($response); - $this->assertObjectHasAttribute('testpreference', $payload); - $this->assertEquals('testvalue', $payload->testpreference); + $this->assertObjectHasProperty('filemanager_recentviewmode', $payload); + $this->assertEquals(1, $payload->filemanager_recentviewmode); } public function test_preferences_set(): void { $this->resetAfterTest(); - $request = $this->create_request( + $this->add_class_routes_to_route_loader(preferences::class); + + $this->setAdminUser(); + + $response = $this->process_api_request( 'POST', - '/user/preferences', - )->withBody( - Utils::streamFor(json_encode([ + '/current/preferences', + body: Utils::streamFor(json_encode([ 'preferences' => [ - 'testpreference' => 'someothervalue', + 'filemanager_recentviewmode' => 2, + 'drawer-open-index' => 1, ], ])), ); - $app = $this->get_app(); - $response = $app->handle($request); + $this->assert_valid_response($response); + + // Check that the response contained the updtaed parameter. + $payload = (object) $this->decode_response($response); + $this->assertObjectHasProperty('filemanager_recentviewmode', $payload); + $this->assertObjectHasProperty('drawer-open-index', $payload); + $this->assertEquals(2, $payload->filemanager_recentviewmode); + + // Check that the preference was updated. + $this->assertEquals(2, get_user_preferences('filemanager_recentviewmode')); + $this->assertEquals(1, get_user_preferences('drawer-open-index')); + } + + /** + * Test that an invalid preference is rejected. + */ + public function test_preferences_set_invalid_value(): void { + $this->resetAfterTest(); + + $this->add_class_routes_to_route_loader(preferences::class); + + $this->setAdminUser(); + + $response = $this->process_api_request( + 'POST', + '/current/preferences', + body: Utils::streamFor(json_encode([ + 'preferences' => [ + 'filemanager_recentviewmode' => 4, + ], + ])), + ); + + $this->assert_invalid_parameter_response($response); + $payload = $this->decode_response($response); + $this->assertStringContainsString('filemanager_recentviewmode', $payload->message); + } + + /** + * Test that a preference the user does not have permission to is rejected. + */ + public function test_preferences_set_not_permitted_valid_login(): void { + $this->resetAfterTest(); + + $this->add_class_routes_to_route_loader(preferences::class); + + $this->setAdminUser(); + + $response = $this->process_api_request( + 'POST', + '/current/preferences', + body: Utils::streamFor(json_encode([ + 'preferences' => [ + 'auth_forcepasswordchange' => 4, + ], + ])), + ); + + $this->assert_access_denied_response($response); + } + + public function test_preference_set(): void { + $this->resetAfterTest(); + + $this->add_class_routes_to_route_loader(preferences::class); + + $this->setAdminUser(); + + $response = $this->process_api_request( + 'POST', + '/current/preferences/filemanager_recentviewmode', + body: Utils::streamFor(json_encode([ + 'value' => 2, + ])), + ); + + $this->assert_valid_response($response); + + // Check that the response contained the updtaed parameter. + $payload = $this->decode_response($response); + $this->assertObjectHasProperty('filemanager_recentviewmode', $payload); + $this->assertEquals(2, $payload->filemanager_recentviewmode); + + // Check that the preference was updated. + $this->assertEquals(2, get_user_preferences('filemanager_recentviewmode')); + } + + /** + * Test that an invalid preference is rejected. + */ + public function test_preference_set_invalid_value(): void { + $this->resetAfterTest(); + + $this->add_class_routes_to_route_loader(preferences::class); + + $this->setAdminUser(); + + $response = $this->process_api_request( + 'POST', + '/current/preferences/filemanager_recentviewmode', + body: Utils::streamFor(json_encode([ + 'value' => 4, + ])), + ); + + $this->assert_invalid_parameter_response($response); + $payload = $this->decode_response($response); + $this->assertStringContainsString('filemanager_recentviewmode', $payload->message); + } + + /** + * Test that an invalid preference inentifier is rejected. + */ + public function test_preference_set_invalid_preference(): void { + $this->resetAfterTest(); + + $this->add_class_routes_to_route_loader(preferences::class); + + $this->setAdminUser(); + + $response = $this->process_api_request( + 'POST', + '/current/preferences/what_a_fake', + body: Utils::streamFor(json_encode([ + 'value' => 4, + ])), + ); + + $this->assert_invalid_parameter_response($response); + $payload = $this->decode_response($response); + $this->assertStringContainsString('what_a_fake', $payload->message); + } + + /** + * Test that a preference the user does not have permission to is rejected. + */ + public function test_preference_set_not_permitted_valid_login(): void { + $this->resetAfterTest(); + + $this->add_class_routes_to_route_loader(preferences::class); + + $this->setAdminUser(); + + $response = $this->process_api_request( + 'POST', + '/current/preferences/auth_forcepasswordchange', + body: Utils::streamFor(json_encode([ + 'value' => 4, + ])), + ); + + $this->assert_access_denied_response($response); + } + + /** + * A user cannot get or set preferences for anothe ruser. + */ + public function test_preference_get_other_user(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + $user = $this->getDataGenerator()->create_user(); + + $this->add_class_routes_to_route_loader(preferences::class); + + // Get all preferences. + $response = $this->process_api_request('GET', "/{$user->id}/preferences"); + $this->assert_access_denied_response($response); + + // Get one preference. + $response = $this->process_api_request('GET', "/{$user->id}/preferences/example"); + $this->assert_access_denied_response($response); + + // Set all preferences. + $response = $this->process_api_request( + 'POST', + "/{$user->id}/preferences/filemanager_recentviewmode", + body: Utils::streamFor(json_encode([ + 'value' => 4, + ])), + ); + $this->assert_access_denied_response($response); + + // Get all preferences. + $response = $this->process_api_request( + 'POST', + "/{$user->id}/preferences", + body: Utils::streamFor(json_encode([ + 'preferences' => [ + 'filemanager_recentviewmode' => 2, + ], + ])), + ); + $this->assert_access_denied_response($response); } } From fbca10b8f3200d54abbb42b3acfa467e8bbe56db Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Mon, 30 Oct 2023 20:10:54 +0800 Subject: [PATCH 11/16] MDL-81031 core: Add core/fetch to query new WS layer --- lib/amd/build/fetch.min.js | 10 + lib/amd/build/fetch.min.js.map | 1 + lib/amd/build/utils.min.js | 2 +- lib/amd/build/utils.min.js.map | 2 +- lib/amd/src/fetch.js | 237 ++++++++++++++++++ lib/amd/src/utils.js | 9 + .../page_requirements_manager.php | 3 +- user/amd/build/repository.min.js | 9 +- user/amd/build/repository.min.js.map | 2 +- user/amd/src/repository.js | 94 +++++-- 10 files changed, 350 insertions(+), 19 deletions(-) create mode 100644 lib/amd/build/fetch.min.js create mode 100644 lib/amd/build/fetch.min.js.map create mode 100644 lib/amd/src/fetch.js diff --git a/lib/amd/build/fetch.min.js b/lib/amd/build/fetch.min.js new file mode 100644 index 00000000000..e90b107327f --- /dev/null +++ b/lib/amd/build/fetch.min.js @@ -0,0 +1,10 @@ +define("core/fetch",["exports","core/config","./pending"],(function(_exports,_config,_pending){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * The core/fetch module allows you to make web service requests to the Moodle API. + * + * @module core/fetch + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.request=_exports.performPut=_exports.performPost=_exports.performHead=_exports.performGet=_exports.performDelete=void 0,_config=_interopRequireDefault(_config),_pending=_interopRequireDefault(_pending);const normaliseComponent=component=>component.replace(/^core_/,""),getRequest=(component,endpoint,_ref)=>{let{params:params={},body:body=null,method:method="GET"}=_ref;const url=new URL("".concat(_config.default.apibase,"rest/v2/").concat(component,"/").concat(endpoint)),options={method:method,headers:{Accept:"application/json","Content-Type":"application/json"}};return Object.entries(params).forEach((_ref2=>{let[key,value]=_ref2;url.searchParams.append(key,value)})),body&&(body instanceof FormData?options.body=body:options.body=body instanceof Object?JSON.stringify(body):body),new Request(url,options)},request=async function(component,action){let{params:params={},body:body=null,method:method="GET"}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const pending=new _pending.default("Requesting ".concat(component,"/").concat(action," with ").concat(method)),result=await fetch(getRequest(normaliseComponent(component),action,{params:params,method:method,body:body}));if(pending.resolve(),result.ok)return result.json();throw new Error(result.statusText)};_exports.request=request;_exports.performGet=function(component,action){let{params:params={}}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{params:params,method:"GET"})};_exports.performHead=function(component,action){let{params:params={}}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{params:params,method:"HEAD"})};_exports.performPost=function(component,action){let{body:body}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{body:body,method:"POST"})};_exports.performPut=function(component,action){let{body:body}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{body:body,method:"POST"})};_exports.performDelete=function(component,action){let{params:params={},body:body=null}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return request(component,action,{body:body,params:params,method:"DELETE"})}})); + +//# sourceMappingURL=fetch.min.js.map \ No newline at end of file diff --git a/lib/amd/build/fetch.min.js.map b/lib/amd/build/fetch.min.js.map new file mode 100644 index 00000000000..9c770a19164 --- /dev/null +++ b/lib/amd/build/fetch.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fetch.min.js","sources":["../src/fetch.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * The core/fetch module allows you to make web service requests to the Moodle API.\n *\n * @module core/fetch\n * @copyright 2023 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Cfg from 'core/config';\nimport PendingPromise from './pending';\n\n/**\n * Normalise the component name to remove the core_ prefix.\n *\n * @param {string} component\n * @returns {string}\n */\nconst normaliseComponent = (component) => component.replace(/^core_/, '');\n\n/**\n * Get the Request object for a given API request.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} endpoint The endpoint within the componet to call\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @param {string|Object|FormData} [params.body = null] The HTTP method to use\n * @param {string} [params.method = \"GET\"] The HTTP method to use\n * @returns {Request}\n */\nconst getRequest = (\n component,\n endpoint,\n {\n params = {},\n body = null,\n method = 'GET',\n }\n) => {\n const url = new URL(`${Cfg.apibase}rest/v2/${component}/${endpoint}`);\n const options = {\n method,\n headers: {\n 'Accept': 'application/json',\n 'Content-Type': 'application/json',\n },\n };\n\n Object.entries(params).forEach(([key, value]) => {\n url.searchParams.append(key, value);\n });\n\n if (body) {\n if (body instanceof FormData) {\n options.body = body;\n } else if (body instanceof Object) {\n options.body = JSON.stringify(body);\n } else {\n options.body = body;\n }\n }\n\n return new Request(url, options);\n};\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @param {string|Object|FormData} [params.body = null] The HTTP method to use\n * @param {string} [params.method = \"GET\"] The HTTP method to use\n * @returns {Promise}\n */\nconst request = async(\n component,\n action,\n {\n params = {},\n body = null,\n method = 'GET',\n } = {},\n) => {\n const pending = new PendingPromise(`Requesting ${component}/${action} with ${method}`);\n const result = await fetch(\n getRequest(\n normaliseComponent(component),\n action,\n {params, method, body},\n ),\n );\n\n pending.resolve();\n\n if (result.ok) {\n return result.json();\n }\n\n throw new Error(result.statusText);\n};\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @returns {Promise}\n */\nconst performGet = (\n component,\n action,\n {\n params = {},\n } = {},\n) => request(\n component,\n action,\n {params, method: 'GET'},\n);\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @returns {Promise}\n */\nconst performHead = (\n component,\n action,\n {\n params = {},\n } = {},\n) => request(\n component,\n action,\n {params, method: 'HEAD'},\n);\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {string|Object|FormData} params.body The HTTP method to use\n * @returns {Promise}\n */\nconst performPost = (\n component,\n action,\n {\n body,\n } = {},\n) => request(\n component,\n action,\n {body, method: 'POST'},\n);\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {string|Object|FormData} params.body The HTTP method to use\n * @returns {Promise}\n */\nconst performPut = (\n component,\n action,\n {\n body,\n } = {},\n) => request(\n component,\n action,\n {body, method: 'POST'},\n);\n\n/**\n * Make a request to the Moodle API.\n *\n * @param {string} component The frankenstyle component name\n * @param {string} action The component action to perform\n * @param {object} params\n * @param {object} [params.params = {}] The parameters to pass to the API\n * @param {string|Object|FormData} [params.body = null] The HTTP method to use\n * @returns {Promise}\n */\nconst performDelete = (\n component,\n action,\n {\n params = {},\n body = null,\n } = {},\n) => request(\n component,\n action,\n {\n body,\n params,\n method: 'DELETE',\n },\n);\n\nexport {\n request,\n performGet,\n performHead,\n performPost,\n performPut,\n performDelete,\n};\n"],"names":["normaliseComponent","component","replace","getRequest","endpoint","params","body","method","url","URL","Cfg","apibase","options","headers","Object","entries","forEach","_ref2","key","value","searchParams","append","FormData","JSON","stringify","Request","request","async","action","pending","PendingPromise","result","fetch","resolve","ok","json","Error","statusText"],"mappings":";;;;;;;sRAgCMA,mBAAsBC,WAAcA,UAAUC,QAAQ,SAAU,IAahEC,WAAa,CACfF,UACAG,qBACAC,OACIA,OAAS,GADbC,KAEIA,KAAO,KAFXC,OAGIA,OAAS,kBAGPC,IAAM,IAAIC,cAAOC,gBAAIC,2BAAkBV,sBAAaG,WACpDQ,QAAU,CACZL,OAAAA,OACAM,QAAS,QACK,kCACM,4BAIxBC,OAAOC,QAAQV,QAAQW,SAAQC,YAAEC,IAAKC,aAClCX,IAAIY,aAAaC,OAAOH,IAAKC,UAG7Bb,OACIA,gBAAgBgB,SAChBV,QAAQN,KAAOA,KAEfM,QAAQN,KADDA,gBAAgBQ,OACRS,KAAKC,UAAUlB,MAEfA,MAIhB,IAAImB,QAAQjB,IAAKI,UActBc,QAAUC,eACZ1B,UACA2B,YACAvB,OACIA,OAAS,GADbC,KAEIA,KAAO,KAFXC,OAGIA,OAAS,8DACT,SAEEsB,QAAU,IAAIC,sCAA6B7B,sBAAa2B,wBAAerB,SACvEwB,aAAeC,MACjB7B,WACIH,mBAAmBC,WACnB2B,OACA,CAACvB,OAAAA,OAAQE,OAAAA,OAAQD,KAAAA,WAIzBuB,QAAQI,UAEJF,OAAOG,UACAH,OAAOI,aAGZ,IAAIC,MAAML,OAAOM,0DAYR,SACfpC,UACA2B,YACAvB,OACIA,OAAS,2DACT,UACHqB,QACDzB,UACA2B,OACA,CAACvB,OAAAA,OAAQE,OAAQ,8BAYD,SAChBN,UACA2B,YACAvB,OACIA,OAAS,2DACT,UACHqB,QACDzB,UACA2B,OACA,CAACvB,OAAAA,OAAQE,OAAQ,+BAYD,SAChBN,UACA2B,YACAtB,KACIA,6DACA,UACHoB,QACDzB,UACA2B,OACA,CAACtB,KAAAA,KAAMC,OAAQ,8BAYA,SACfN,UACA2B,YACAtB,KACIA,6DACA,UACHoB,QACDzB,UACA2B,OACA,CAACtB,KAAAA,KAAMC,OAAQ,iCAaG,SAClBN,UACA2B,YACAvB,OACIA,OAAS,GADbC,KAEIA,KAAO,6DACP,UACHoB,QACDzB,UACA2B,OACA,CACItB,KAAAA,KACAD,OAAAA,OACAE,OAAQ"} \ No newline at end of file diff --git a/lib/amd/build/utils.min.js b/lib/amd/build/utils.min.js index df869fc7fea..4f5052e9b6f 100644 --- a/lib/amd/build/utils.min.js +++ b/lib/amd/build/utils.min.js @@ -1,3 +1,3 @@ -define("core/utils",["exports","core/pending"],(function(_exports,_pending){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.throttle=_exports.getNormalisedComponent=_exports.debounce=void 0,_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};_exports.throttle=(func,wait)=>{let onCooldown=!1,runAgain=null;const run=function(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];runAgain=null!==runAgain,onCooldown||(func.apply(this,args),onCooldown=!0,setTimeout((()=>{const recurse=runAgain;onCooldown=!1,runAgain=null,recurse&&run(args)}),wait))};return run};const debounceMap=new Map;_exports.debounce=function(func,wait){let{pending:pending=!1,cancel:cancel=!1}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},timeout=null;const returnedFunction=function(){for(var _len2=arguments.length,args=new Array(_len2),_key2=0;_key2<_len2;_key2++)args[_key2]=arguments[_key2];pending&&!debounceMap.has(returnedFunction)&&debounceMap.set(returnedFunction,new _pending.default("core/utils:debounce")),clearTimeout(timeout),timeout=setTimeout((async()=>{const pendingPromise=debounceMap.get(returnedFunction);debounceMap.delete(returnedFunction),await func.apply(undefined,args),null==pendingPromise||pendingPromise.resolve()}),wait)};return cancel&&(returnedFunction.cancel=()=>{const pendingPromise=debounceMap.get(returnedFunction);null==pendingPromise||pendingPromise.resolve(),clearTimeout(timeout)}),returnedFunction};_exports.getNormalisedComponent=component=>component&&"moodle"!==component&&"core"!==component?component:"core"})); +define("core/utils",["exports","core/pending","jquery"],(function(_exports,_pending,_jquery){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.wrapPromiseInWhenable=_exports.throttle=_exports.getNormalisedComponent=_exports.debounce=void 0,_pending=_interopRequireDefault(_pending),_jquery=_interopRequireDefault(_jquery);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}_exports.throttle=(func,wait)=>{let onCooldown=!1,runAgain=null;const run=function(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];runAgain=null!==runAgain,onCooldown||(func.apply(this,args),onCooldown=!0,setTimeout((()=>{const recurse=runAgain;onCooldown=!1,runAgain=null,recurse&&run(args)}),wait))};return run};const debounceMap=new Map;_exports.debounce=function(func,wait){let{pending:pending=!1,cancel:cancel=!1}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},timeout=null;const returnedFunction=function(){for(var _len2=arguments.length,args=new Array(_len2),_key2=0;_key2<_len2;_key2++)args[_key2]=arguments[_key2];pending&&!debounceMap.has(returnedFunction)&&debounceMap.set(returnedFunction,new _pending.default("core/utils:debounce")),clearTimeout(timeout),timeout=setTimeout((async()=>{const pendingPromise=debounceMap.get(returnedFunction);debounceMap.delete(returnedFunction),await func.apply(undefined,args),null==pendingPromise||pendingPromise.resolve()}),wait)};return cancel&&(returnedFunction.cancel=()=>{const pendingPromise=debounceMap.get(returnedFunction);null==pendingPromise||pendingPromise.resolve(),clearTimeout(timeout)}),returnedFunction};_exports.getNormalisedComponent=component=>component&&"moodle"!==component&&"core"!==component?component:"core";_exports.wrapPromiseInWhenable=promise=>_jquery.default.when(promise)})); //# sourceMappingURL=utils.min.js.map \ No newline at end of file diff --git a/lib/amd/build/utils.min.js.map b/lib/amd/build/utils.min.js.map index 60a9850f2ef..f36fce1ae49 100644 --- a/lib/amd/build/utils.min.js.map +++ b/lib/amd/build/utils.min.js.map @@ -1 +1 @@ -{"version":3,"file":"utils.min.js","sources":["../src/utils.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Utility functions.\n *\n * @module core/utils\n * @copyright 2019 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\n\n /**\n * Create a wrapper function to throttle the execution of the given\n *\n * function to at most once every specified period.\n *\n * If the function is attempted to be executed while it's in cooldown\n * (during the wait period) then it'll immediately execute again as\n * soon as the cooldown is over.\n *\n * @method\n * @param {Function} func The function to throttle\n * @param {Number} wait The number of milliseconds to wait between executions\n * @return {Function}\n */\nexport const throttle = (func, wait) => {\n let onCooldown = false;\n let runAgain = null;\n const run = function(...args) {\n if (runAgain === null) {\n // This is the first time the function has been called.\n runAgain = false;\n } else {\n // This function has been called a second time during the wait period\n // so re-run it once the wait period is over.\n runAgain = true;\n }\n\n if (onCooldown) {\n // Function has already run for this wait period.\n return;\n }\n\n func.apply(this, args);\n onCooldown = true;\n\n setTimeout(() => {\n const recurse = runAgain;\n onCooldown = false;\n runAgain = null;\n\n if (recurse) {\n run(args);\n }\n }, wait);\n };\n\n return run;\n};\n\n/**\n * @property {Map} debounceMap A map of functions to their debounced pending promises.\n */\nconst debounceMap = new Map();\n\n/**\n * Create a wrapper function to debounce the execution of the given\n * function. Each attempt to execute the function will reset the cooldown\n * period.\n *\n * @method\n * @param {Function} func The function to debounce\n * @param {Number} wait The number of milliseconds to wait after the final attempt to execute\n * @param {Object} [options]\n * @param {boolean} [options.pending=false] Whether to wrap the debounced method in a pending promise\n * @param {boolean} [options.cancel=false] Whether to add a cancel method to the debounced function\n * @return {Function}\n */\nexport const debounce = (\n func,\n wait,\n {\n pending = false,\n cancel = false,\n } = {},\n) => {\n let timeout = null;\n\n const returnedFunction = (...args) => {\n if (pending && !debounceMap.has(returnedFunction)) {\n debounceMap.set(returnedFunction, new Pending('core/utils:debounce'));\n }\n clearTimeout(timeout);\n timeout = setTimeout(async () => {\n // Get the current pending promise and immediately empty it.\n // This is important to allow the function to be debounced again as soon as possible.\n // We do not resolve it until later - but that's fine because the promise is appropriately scoped.\n const pendingPromise = debounceMap.get(returnedFunction);\n debounceMap.delete(returnedFunction);\n\n // Allow the debounced function to return a Promise.\n // This ensures that Behat will not continue until the function has finished executing.\n await func.apply(this, args);\n\n // Resolve the pending promise if it exists.\n pendingPromise?.resolve();\n }, wait);\n };\n\n if (cancel) {\n returnedFunction.cancel = () => {\n const pendingPromise = debounceMap.get(returnedFunction);\n pendingPromise?.resolve();\n clearTimeout(timeout);\n };\n }\n\n return returnedFunction;\n};\n\n/**\n * Normalise the provided component such that '', 'moodle', and 'core' are treated consistently.\n *\n * @param {String} component\n * @returns {String}\n */\nexport const getNormalisedComponent = (component) => {\n if (component) {\n if (component !== 'moodle' && component !== 'core') {\n return component;\n }\n }\n\n return 'core';\n};\n"],"names":["func","wait","onCooldown","runAgain","run","args","apply","this","setTimeout","recurse","debounceMap","Map","pending","cancel","timeout","returnedFunction","has","set","Pending","clearTimeout","async","pendingPromise","get","delete","resolve","component"],"mappings":"mSAuCwB,CAACA,KAAMC,YACvBC,YAAa,EACbC,SAAW,WACTC,IAAM,yCAAYC,6CAAAA,2BAGhBF,SAFa,OAAbA,SASAD,aAKJF,KAAKM,MAAMC,KAAMF,MACjBH,YAAa,EAEbM,YAAW,WACDC,QAAUN,SAChBD,YAAa,EACbC,SAAW,KAEPM,SACAL,IAAIC,QAETJ,eAGAG,WAMLM,YAAc,IAAIC,sBAeA,SACpBX,KACAC,UACAW,QACIA,SAAU,EADdC,OAEIA,QAAS,0DACT,GAEAC,QAAU,WAERC,iBAAmB,0CAAIV,kDAAAA,6BACrBO,UAAYF,YAAYM,IAAID,mBAC5BL,YAAYO,IAAIF,iBAAkB,IAAIG,iBAAQ,wBAElDC,aAAaL,SACbA,QAAUN,YAAWY,gBAIXC,eAAiBX,YAAYY,IAAIP,kBACvCL,YAAYa,OAAOR,wBAIbf,KAAKM,gBAAYD,MAGvBgB,MAAAA,gBAAAA,eAAgBG,YACjBvB,cAGHY,SACAE,iBAAiBF,OAAS,WAChBQ,eAAiBX,YAAYY,IAAIP,kBACvCM,MAAAA,gBAAAA,eAAgBG,UAChBL,aAAaL,WAIdC,kDAS4BU,WAC/BA,WACkB,WAAdA,WAAwC,SAAdA,UACnBA,UAIR"} \ No newline at end of file +{"version":3,"file":"utils.min.js","sources":["../src/utils.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Utility functions.\n *\n * @module core/utils\n * @copyright 2019 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\nimport jQuery from 'jquery';\n\n /**\n * Create a wrapper function to throttle the execution of the given\n *\n * function to at most once every specified period.\n *\n * If the function is attempted to be executed while it's in cooldown\n * (during the wait period) then it'll immediately execute again as\n * soon as the cooldown is over.\n *\n * @method\n * @param {Function} func The function to throttle\n * @param {Number} wait The number of milliseconds to wait between executions\n * @return {Function}\n */\nexport const throttle = (func, wait) => {\n let onCooldown = false;\n let runAgain = null;\n const run = function(...args) {\n if (runAgain === null) {\n // This is the first time the function has been called.\n runAgain = false;\n } else {\n // This function has been called a second time during the wait period\n // so re-run it once the wait period is over.\n runAgain = true;\n }\n\n if (onCooldown) {\n // Function has already run for this wait period.\n return;\n }\n\n func.apply(this, args);\n onCooldown = true;\n\n setTimeout(() => {\n const recurse = runAgain;\n onCooldown = false;\n runAgain = null;\n\n if (recurse) {\n run(args);\n }\n }, wait);\n };\n\n return run;\n};\n\n/**\n * @property {Map} debounceMap A map of functions to their debounced pending promises.\n */\nconst debounceMap = new Map();\n\n/**\n * Create a wrapper function to debounce the execution of the given\n * function. Each attempt to execute the function will reset the cooldown\n * period.\n *\n * @method\n * @param {Function} func The function to debounce\n * @param {Number} wait The number of milliseconds to wait after the final attempt to execute\n * @param {Object} [options]\n * @param {boolean} [options.pending=false] Whether to wrap the debounced method in a pending promise\n * @param {boolean} [options.cancel=false] Whether to add a cancel method to the debounced function\n * @return {Function}\n */\nexport const debounce = (\n func,\n wait,\n {\n pending = false,\n cancel = false,\n } = {},\n) => {\n let timeout = null;\n\n const returnedFunction = (...args) => {\n if (pending && !debounceMap.has(returnedFunction)) {\n debounceMap.set(returnedFunction, new Pending('core/utils:debounce'));\n }\n clearTimeout(timeout);\n timeout = setTimeout(async () => {\n // Get the current pending promise and immediately empty it.\n // This is important to allow the function to be debounced again as soon as possible.\n // We do not resolve it until later - but that's fine because the promise is appropriately scoped.\n const pendingPromise = debounceMap.get(returnedFunction);\n debounceMap.delete(returnedFunction);\n\n // Allow the debounced function to return a Promise.\n // This ensures that Behat will not continue until the function has finished executing.\n await func.apply(this, args);\n\n // Resolve the pending promise if it exists.\n pendingPromise?.resolve();\n }, wait);\n };\n\n if (cancel) {\n returnedFunction.cancel = () => {\n const pendingPromise = debounceMap.get(returnedFunction);\n pendingPromise?.resolve();\n clearTimeout(timeout);\n };\n }\n\n return returnedFunction;\n};\n\n/**\n * Normalise the provided component such that '', 'moodle', and 'core' are treated consistently.\n *\n * @param {String} component\n * @returns {String}\n */\nexport const getNormalisedComponent = (component) => {\n if (component) {\n if (component !== 'moodle' && component !== 'core') {\n return component;\n }\n }\n\n return 'core';\n};\n\n/**\n * Wrap a Native Promise in a jQuery Whenable for b/c.\n *\n * @param {*} promise\n * @returns {jQuery}\n */\nexport const wrapPromiseInWhenable = (promise) => jQuery.when(promise);\n"],"names":["func","wait","onCooldown","runAgain","run","args","apply","this","setTimeout","recurse","debounceMap","Map","pending","cancel","timeout","returnedFunction","has","set","Pending","clearTimeout","async","pendingPromise","get","delete","resolve","component","promise","jQuery","when"],"mappings":"qbAwCwB,CAACA,KAAMC,YACvBC,YAAa,EACbC,SAAW,WACTC,IAAM,yCAAYC,6CAAAA,2BAGhBF,SAFa,OAAbA,SASAD,aAKJF,KAAKM,MAAMC,KAAMF,MACjBH,YAAa,EAEbM,YAAW,WACDC,QAAUN,SAChBD,YAAa,EACbC,SAAW,KAEPM,SACAL,IAAIC,QAETJ,eAGAG,WAMLM,YAAc,IAAIC,sBAeA,SACpBX,KACAC,UACAW,QACIA,SAAU,EADdC,OAEIA,QAAS,0DACT,GAEAC,QAAU,WAERC,iBAAmB,0CAAIV,kDAAAA,6BACrBO,UAAYF,YAAYM,IAAID,mBAC5BL,YAAYO,IAAIF,iBAAkB,IAAIG,iBAAQ,wBAElDC,aAAaL,SACbA,QAAUN,YAAWY,gBAIXC,eAAiBX,YAAYY,IAAIP,kBACvCL,YAAYa,OAAOR,wBAIbf,KAAKM,gBAAYD,MAGvBgB,MAAAA,gBAAAA,eAAgBG,YACjBvB,cAGHY,SACAE,iBAAiBF,OAAS,WAChBQ,eAAiBX,YAAYY,IAAIP,kBACvCM,MAAAA,gBAAAA,eAAgBG,UAChBL,aAAaL,WAIdC,kDAS4BU,WAC/BA,WACkB,WAAdA,WAAwC,SAAdA,UACnBA,UAIR,sCAS2BC,SAAYC,gBAAOC,KAAKF"} \ No newline at end of file diff --git a/lib/amd/src/fetch.js b/lib/amd/src/fetch.js new file mode 100644 index 00000000000..f73d0b8d372 --- /dev/null +++ b/lib/amd/src/fetch.js @@ -0,0 +1,237 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/** + * The core/fetch module allows you to make web service requests to the Moodle API. + * + * @module core/fetch + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Cfg from 'core/config'; +import PendingPromise from './pending'; + +/** + * Normalise the component name to remove the core_ prefix. + * + * @param {string} component + * @returns {string} + */ +const normaliseComponent = (component) => component.replace(/^core_/, ''); + +/** + * Get the Request object for a given API request. + * + * @param {string} component The frankenstyle component name + * @param {string} endpoint The endpoint within the componet to call + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @param {string|Object|FormData} [params.body = null] The HTTP method to use + * @param {string} [params.method = "GET"] The HTTP method to use + * @returns {Request} + */ +const getRequest = ( + component, + endpoint, + { + params = {}, + body = null, + method = 'GET', + } +) => { + const url = new URL(`${Cfg.apibase}rest/v2/${component}/${endpoint}`); + const options = { + method, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + }; + + Object.entries(params).forEach(([key, value]) => { + url.searchParams.append(key, value); + }); + + if (body) { + if (body instanceof FormData) { + options.body = body; + } else if (body instanceof Object) { + options.body = JSON.stringify(body); + } else { + options.body = body; + } + } + + return new Request(url, options); +}; + +/** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @param {string|Object|FormData} [params.body = null] The HTTP method to use + * @param {string} [params.method = "GET"] The HTTP method to use + * @returns {Promise} + */ +const request = async( + component, + action, + { + params = {}, + body = null, + method = 'GET', + } = {}, +) => { + const pending = new PendingPromise(`Requesting ${component}/${action} with ${method}`); + const result = await fetch( + getRequest( + normaliseComponent(component), + action, + {params, method, body}, + ), + ); + + pending.resolve(); + + if (result.ok) { + return result.json(); + } + + throw new Error(result.statusText); +}; + +/** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @returns {Promise} + */ +const performGet = ( + component, + action, + { + params = {}, + } = {}, +) => request( + component, + action, + {params, method: 'GET'}, +); + +/** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @returns {Promise} + */ +const performHead = ( + component, + action, + { + params = {}, + } = {}, +) => request( + component, + action, + {params, method: 'HEAD'}, +); + +/** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {string|Object|FormData} params.body The HTTP method to use + * @returns {Promise} + */ +const performPost = ( + component, + action, + { + body, + } = {}, +) => request( + component, + action, + {body, method: 'POST'}, +); + +/** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {string|Object|FormData} params.body The HTTP method to use + * @returns {Promise} + */ +const performPut = ( + component, + action, + { + body, + } = {}, +) => request( + component, + action, + {body, method: 'POST'}, +); + +/** + * Make a request to the Moodle API. + * + * @param {string} component The frankenstyle component name + * @param {string} action The component action to perform + * @param {object} params + * @param {object} [params.params = {}] The parameters to pass to the API + * @param {string|Object|FormData} [params.body = null] The HTTP method to use + * @returns {Promise} + */ +const performDelete = ( + component, + action, + { + params = {}, + body = null, + } = {}, +) => request( + component, + action, + { + body, + params, + method: 'DELETE', + }, +); + +export { + request, + performGet, + performHead, + performPost, + performPut, + performDelete, +}; diff --git a/lib/amd/src/utils.js b/lib/amd/src/utils.js index 0a59eab25d8..6a7bef0f65a 100644 --- a/lib/amd/src/utils.js +++ b/lib/amd/src/utils.js @@ -22,6 +22,7 @@ */ import Pending from 'core/pending'; +import jQuery from 'jquery'; /** * Create a wrapper function to throttle the execution of the given @@ -147,3 +148,11 @@ export const getNormalisedComponent = (component) => { return 'core'; }; + +/** + * Wrap a Native Promise in a jQuery Whenable for b/c. + * + * @param {*} promise + * @returns {jQuery} + */ +export const wrapPromiseInWhenable = (promise) => jQuery.when(promise); diff --git a/lib/classes/output/requirements/page_requirements_manager.php b/lib/classes/output/requirements/page_requirements_manager.php index 039d68a0ef2..d58e75995c8 100644 --- a/lib/classes/output/requirements/page_requirements_manager.php +++ b/lib/classes/output/requirements/page_requirements_manager.php @@ -298,7 +298,7 @@ class page_requirements_manager { * @return array List of safe config values that are available to javascript. */ public function get_config_for_javascript(moodle_page $page, renderer_base $renderer) { - global $CFG; + global $CFG, $USER; if (empty($this->M_cfg)) { $iconsystem = \core\output\icon_system::instance(); @@ -336,6 +336,7 @@ class page_requirements_manager { 'langrev' => get_string_manager()->get_revision(), 'templaterev' => $this->get_templaterev(), 'siteId' => (int) SITEID, + 'userId' => (int) $USER->id, ]; if ($CFG->debugdeveloper) { $this->M_cfg['developerdebug'] = true; diff --git a/user/amd/build/repository.min.js b/user/amd/build/repository.min.js index 19b649ff48b..edbf4e9a78e 100644 --- a/user/amd/build/repository.min.js +++ b/user/amd/build/repository.min.js @@ -1,3 +1,10 @@ -define("core_user/repository",["exports","core/ajax"],(function(_exports,_ajax){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.unenrolUser=_exports.submitUserEnrolmentForm=_exports.setUserPreferences=_exports.setUserPreference=_exports.sendMessagesToUsers=_exports.getUserPreferences=_exports.getUserPreference=_exports.createNotesForUsers=void 0;_exports.getUserPreference=function(name){let userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return getUserPreferences(name,userid).then((response=>response.preferences[0].value))};const getUserPreferences=function(){let name=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return(0,_ajax.call)([{methodname:"core_user_get_user_preferences",args:{name:name,userid:userid}}])[0]};_exports.getUserPreferences=getUserPreferences;_exports.setUserPreference=function(name){let value=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,userid=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;return setUserPreferences([{name:name,value:value,userid:userid}])};const setUserPreferences=preferences=>(0,_ajax.call)([{methodname:"core_user_set_user_preferences",args:{preferences:preferences}}])[0];_exports.setUserPreferences=setUserPreferences;_exports.unenrolUser=userEnrolmentId=>(0,_ajax.call)([{methodname:"core_enrol_unenrol_user_enrolment",args:{ueid:userEnrolmentId}}])[0];_exports.submitUserEnrolmentForm=formdata=>(0,_ajax.call)([{methodname:"core_enrol_submit_user_enrolment_form",args:{formdata:formdata}}])[0];_exports.createNotesForUsers=notes=>(0,_ajax.call)([{methodname:"core_notes_create_notes",args:{notes:notes}}])[0];_exports.sendMessagesToUsers=messages=>(0,_ajax.call)([{methodname:"core_message_send_instant_messages",args:{messages:messages}}])[0]})); +define("core_user/repository",["exports","core/config","core/ajax","core/fetch"],(function(_exports,_config,_ajax,_fetch){var obj; +/** + * Module to handle AJAX interactions. + * + * @module core_user/repository + * @copyright 2020 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.unenrolUser=_exports.submitUserEnrolmentForm=_exports.setUserPreferences=_exports.setUserPreference=_exports.sendMessagesToUsers=_exports.getUserPreferences=_exports.getUserPreference=_exports.createNotesForUsers=void 0,_config=(obj=_config)&&obj.__esModule?obj:{default:obj};const checkUserId=userid=>{if(0!==Number(userid)&&Number(userid)!==_config.default.userId)throw new Error("Invalid user ID: ".concat(userid,". It is only possible to manage preferences for the current user."))},addLegacySavedProperty=(response,preferences)=>{const debugLogger={get:(target,prop,receiver)=>"then"===prop?null:"saved"===prop?(window.console.warn("The saved property is deprecated. Please use the response object directly."),preferences.filter((preference=>target.hasOwnProperty(preference.name))).map((preference=>({name:preference.name,userid:_config.default.userid})))):Reflect.get(target,prop,receiver)};return Promise.resolve(new Proxy(response,debugLogger))};_exports.getUserPreference=function(name){let userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return getUserPreferences(name,userid).then((response=>response[name]))};const getUserPreferences=function(){let name=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,userid=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;checkUserId(userid);const endpoint=["current","preferences"];return name&&endpoint.push(name),(0,_fetch.performGet)("core_user",endpoint.join("/"))};_exports.getUserPreferences=getUserPreferences;_exports.setUserPreference=function(name){let value=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,userid=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0;return checkUserId(userid),(0,_fetch.performPost)("core_user","current/preferences/".concat(name),{body:{value:value}}).then((response=>addLegacySavedProperty(response,[{name:name}])))};_exports.setUserPreferences=preferences=>(preferences.forEach((preference=>checkUserId(preference.userid))),(0,_fetch.performPost)("core_user","current/preferences",{body:{preferences:Object.fromEntries(preferences.map((preference=>[preference.name,preference.value])))}}).then((response=>addLegacySavedProperty(response,preferences))));_exports.unenrolUser=userEnrolmentId=>(0,_ajax.call)([{methodname:"core_enrol_unenrol_user_enrolment",args:{ueid:userEnrolmentId}}])[0];_exports.submitUserEnrolmentForm=formdata=>(0,_ajax.call)([{methodname:"core_enrol_submit_user_enrolment_form",args:{formdata:formdata}}])[0];_exports.createNotesForUsers=notes=>(0,_ajax.call)([{methodname:"core_notes_create_notes",args:{notes:notes}}])[0];_exports.sendMessagesToUsers=messages=>(0,_ajax.call)([{methodname:"core_message_send_instant_messages",args:{messages:messages}}])[0]})); //# sourceMappingURL=repository.min.js.map \ No newline at end of file diff --git a/user/amd/build/repository.min.js.map b/user/amd/build/repository.min.js.map index b59633bfe53..731768594ea 100644 --- a/user/amd/build/repository.min.js.map +++ b/user/amd/build/repository.min.js.map @@ -1 +1 @@ -{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to handle AJAX interactions.\n *\n * @module core_user/repository\n * @copyright 2020 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\n\n/**\n * Get single user preference\n *\n * @param {String} name Name of the preference\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise}\n */\nexport const getUserPreference = (name, userid = 0) => {\n return getUserPreferences(name, userid)\n .then(response => response.preferences[0].value);\n};\n\n/**\n * Get multiple user preferences\n *\n * @param {String|null} name Name of the preference (omit if you want to retrieve all)\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise}\n */\nexport const getUserPreferences = (name = null, userid = 0) => {\n return fetchMany([{\n methodname: 'core_user_get_user_preferences',\n args: {name, userid}\n }])[0];\n};\n\n/**\n * Set single user preference\n *\n * @param {String} name Name of the preference\n * @param {String|null} value Value of the preference (omit if you want to remove the current value)\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise}\n */\nexport const setUserPreference = (name, value = null, userid = 0) => {\n return setUserPreferences([{name, value, userid}]);\n};\n\n/**\n * Set multiple user preferences\n *\n * @param {Object[]} preferences Array of preferences containing name/value/userid attributes\n * @return {Promise}\n */\nexport const setUserPreferences = (preferences) => {\n return fetchMany([{\n methodname: 'core_user_set_user_preferences',\n args: {preferences}\n }])[0];\n};\n\n/**\n * Unenrol the user with the specified user enrolmentid ID.\n *\n * @param {Number} userEnrolmentId\n * @return {Promise}\n */\nexport const unenrolUser = userEnrolmentId => {\n return fetchMany([{\n methodname: 'core_enrol_unenrol_user_enrolment',\n args: {\n ueid: userEnrolmentId,\n },\n }])[0];\n};\n\n/**\n * Submit the user enrolment form with the specified form data.\n *\n * @param {String} formdata\n * @return {Promise}\n */\nexport const submitUserEnrolmentForm = formdata => {\n return fetchMany([{\n methodname: 'core_enrol_submit_user_enrolment_form',\n args: {\n formdata,\n },\n }])[0];\n};\n\nexport const createNotesForUsers = notes => {\n return fetchMany([{\n methodname: 'core_notes_create_notes',\n args: {\n notes\n }\n }])[0];\n};\n\nexport const sendMessagesToUsers = messages => {\n return fetchMany([{\n methodname: 'core_message_send_instant_messages',\n args: {messages}\n }])[0];\n};\n"],"names":["name","userid","getUserPreferences","then","response","preferences","value","methodname","args","setUserPreferences","userEnrolmentId","ueid","formdata","notes","messages"],"mappings":"wYAgCiC,SAACA,UAAMC,8DAAS,SACtCC,mBAAmBF,KAAMC,QAC3BE,MAAKC,UAAYA,SAASC,YAAY,GAAGC,eAUrCJ,mBAAqB,eAACF,4DAAO,KAAMC,8DAAS,SAC9C,cAAU,CAAC,CACdM,WAAY,iCACZC,KAAM,CAACR,KAAAA,KAAMC,OAAAA,WACb,8EAWyB,SAACD,UAAMM,6DAAQ,KAAML,8DAAS,SACpDQ,mBAAmB,CAAC,CAACT,KAAAA,KAAMM,MAAAA,MAAOL,OAAAA,iBAShCQ,mBAAsBJ,cACxB,cAAU,CAAC,CACdE,WAAY,iCACZC,KAAM,CAACH,YAAAA,gBACP,uEASmBK,kBAChB,cAAU,CAAC,CACdH,WAAY,oCACZC,KAAM,CACFG,KAAMD,oBAEV,oCAS+BE,WAC5B,cAAU,CAAC,CACdL,WAAY,wCACZC,KAAM,CACFI,SAAAA,aAEJ,gCAG2BC,QACxB,cAAU,CAAC,CACdN,WAAY,0BACZC,KAAM,CACFK,MAAAA,UAEJ,gCAG2BC,WACxB,cAAU,CAAC,CACdP,WAAY,qCACZC,KAAM,CAACM,SAAAA,aACP"} \ No newline at end of file +{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to handle AJAX interactions.\n *\n * @module core_user/repository\n * @copyright 2020 Andrew Nicols \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Config from 'core/config';\nimport {call as fetchMany} from 'core/ajax';\nimport {performGet, performPost} from 'core/fetch';\n\nconst checkUserId = (userid) => {\n if (Number(userid) === 0) {\n return;\n }\n if (Number(userid) === Config.userId) {\n return;\n }\n throw new Error(\n `Invalid user ID: ${userid}. It is only possible to manage preferences for the current user.`,\n );\n};\n\n/**\n * Turn the response object into a Proxy object that will log a warning if the saved property is accessed.\n *\n * @param {Object} response\n * @param {Object} preferences The preferences that might be in the response\n * @return {Promise}\n */\nconst addLegacySavedProperty = (response, preferences) => {\n const debugLogger = {\n get(target, prop, receiver) {\n if (prop === 'then') {\n // To proxy a Promise we have to return null when the then key is requested.\n return null;\n }\n if (prop === 'saved') {\n window.console.warn(\n 'The saved property is deprecated. Please use the response object directly.',\n );\n\n return preferences\n .filter((preference) => target.hasOwnProperty(preference.name))\n .map((preference) => ({\n name: preference.name,\n userid: Config.userid,\n }));\n }\n return Reflect.get(target, prop, receiver);\n },\n };\n\n return Promise.resolve(new Proxy(response, debugLogger));\n};\n\n/**\n * Get single user preference\n *\n * @param {String} name Name of the preference\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise}\n */\nexport const getUserPreference = (name, userid = 0) => getUserPreferences(name, userid)\n .then((response) => response[name]);\n\n/**\n * Get multiple user preferences\n *\n * @param {String|null} name Name of the preference (omit if you want to retrieve all)\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise>}\n */\nexport const getUserPreferences = (name = null, userid = 0) => {\n checkUserId(userid);\n const endpoint = ['current', 'preferences'];\n\n if (name) {\n endpoint.push(name);\n }\n\n return performGet('core_user', endpoint.join('/'));\n};\n\n/**\n * Set single user preference\n *\n * @param {String} name Name of the preference\n * @param {String|null} value Value of the preference (omit if you want to remove the current value)\n * @param {Number} userid User ID (defaults to current user)\n * @return {Promise}\n */\nexport const setUserPreference = (name, value = null, userid = 0) => {\n checkUserId(userid);\n return performPost(\n 'core_user',\n `current/preferences/${name}`,\n {\n body: {value},\n },\n )\n // Return the result of the fetch call, and also add in the legacy saved property.\n .then((response) => addLegacySavedProperty(response, [{name}]));\n};\n\n/**\n * Set multiple user preferences\n *\n * @param {Object[]} preferences Array of preferences containing name/value/userid attributes\n * @return {Promise}\n */\nexport const setUserPreferences = (preferences) => {\n preferences.forEach((preference) => checkUserId(preference.userid));\n return performPost(\n 'core_user',\n 'current/preferences',\n {\n body: {\n preferences: Object.fromEntries (preferences.map((preference) => ([preference.name, preference.value]))),\n },\n },\n )\n // Return the result of the fetch call, and also add in the legacy saved property.\n .then((response) => addLegacySavedProperty(response, preferences));\n};\n\n/**\n * Unenrol the user with the specified user enrolmentid ID.\n *\n * @param {Number} userEnrolmentId\n * @return {Promise}\n */\nexport const unenrolUser = userEnrolmentId => {\n return fetchMany([{\n methodname: 'core_enrol_unenrol_user_enrolment',\n args: {\n ueid: userEnrolmentId,\n },\n }])[0];\n};\n\n/**\n * Submit the user enrolment form with the specified form data.\n *\n * @param {String} formdata\n * @return {Promise}\n */\nexport const submitUserEnrolmentForm = formdata => {\n return fetchMany([{\n methodname: 'core_enrol_submit_user_enrolment_form',\n args: {\n formdata,\n },\n }])[0];\n};\n\nexport const createNotesForUsers = notes => {\n return fetchMany([{\n methodname: 'core_notes_create_notes',\n args: {\n notes\n }\n }])[0];\n};\n\nexport const sendMessagesToUsers = messages => {\n return fetchMany([{\n methodname: 'core_message_send_instant_messages',\n args: {messages}\n }])[0];\n};\n"],"names":["checkUserId","userid","Number","Config","userId","Error","addLegacySavedProperty","response","preferences","debugLogger","get","target","prop","receiver","window","console","warn","filter","preference","hasOwnProperty","name","map","Reflect","Promise","resolve","Proxy","getUserPreferences","then","endpoint","push","join","value","body","forEach","Object","fromEntries","userEnrolmentId","methodname","args","ueid","formdata","notes","messages"],"mappings":";;;;;;;gWA2BMA,YAAeC,YACM,IAAnBC,OAAOD,SAGPC,OAAOD,UAAYE,gBAAOC,aAGxB,IAAIC,iCACcJ,8EAWtBK,uBAAyB,CAACC,SAAUC,qBAChCC,YAAc,CAChBC,IAAG,CAACC,OAAQC,KAAMC,WACD,SAATD,KAEO,KAEE,UAATA,MACAE,OAAOC,QAAQC,KACX,8EAGGR,YACFS,QAAQC,YAAeP,OAAOQ,eAAeD,WAAWE,QACxDC,KAAKH,cACFE,KAAMF,WAAWE,KACjBnB,OAAQE,gBAAOF,YAGpBqB,QAAQZ,IAAIC,OAAQC,KAAMC,kBAIlCU,QAAQC,QAAQ,IAAIC,MAAMlB,SAAUE,0CAUd,SAACW,UAAMnB,8DAAS,SAAMyB,mBAAmBN,KAAMnB,QAC3E0B,MAAMpB,UAAaA,SAASa,eASpBM,mBAAqB,eAACN,4DAAO,KAAMnB,8DAAS,EACrDD,YAAYC,cACN2B,SAAW,CAAC,UAAW,sBAEzBR,MACAQ,SAASC,KAAKT,OAGX,qBAAW,YAAaQ,SAASE,KAAK,iFAWhB,SAACV,UAAMW,6DAAQ,KAAM9B,8DAAS,SAC3DD,YAAYC,SACL,sBACH,0CACuBmB,MACvB,CACIY,KAAM,CAACD,MAAAA,SAIdJ,MAAMpB,UAAaD,uBAAuBC,SAAU,CAAC,CAACa,KAAAA,uCASxBZ,cAC/BA,YAAYyB,SAASf,YAAelB,YAAYkB,WAAWjB,WACpD,sBACH,YACA,sBACA,CACI+B,KAAM,CACFxB,YAAa0B,OAAOC,YAAa3B,YAAYa,KAAKH,YAAgB,CAACA,WAAWE,KAAMF,WAAWa,aAK1GJ,MAAMpB,UAAaD,uBAAuBC,SAAUC,qCAS9B4B,kBAChB,cAAU,CAAC,CACdC,WAAY,oCACZC,KAAM,CACFC,KAAMH,oBAEV,oCAS+BI,WAC5B,cAAU,CAAC,CACdH,WAAY,wCACZC,KAAM,CACFE,SAAAA,aAEJ,gCAG2BC,QACxB,cAAU,CAAC,CACdJ,WAAY,0BACZC,KAAM,CACFG,MAAAA,UAEJ,gCAG2BC,WACxB,cAAU,CAAC,CACdL,WAAY,qCACZC,KAAM,CAACI,SAAAA,aACP"} \ No newline at end of file diff --git a/user/amd/src/repository.js b/user/amd/src/repository.js index f6c078539a1..1b8b592e72d 100644 --- a/user/amd/src/repository.js +++ b/user/amd/src/repository.js @@ -21,7 +21,54 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +import Config from 'core/config'; import {call as fetchMany} from 'core/ajax'; +import {performGet, performPost} from 'core/fetch'; + +const checkUserId = (userid) => { + if (Number(userid) === 0) { + return; + } + if (Number(userid) === Config.userId) { + return; + } + throw new Error( + `Invalid user ID: ${userid}. It is only possible to manage preferences for the current user.`, + ); +}; + +/** + * Turn the response object into a Proxy object that will log a warning if the saved property is accessed. + * + * @param {Object} response + * @param {Object} preferences The preferences that might be in the response + * @return {Promise} + */ +const addLegacySavedProperty = (response, preferences) => { + const debugLogger = { + get(target, prop, receiver) { + if (prop === 'then') { + // To proxy a Promise we have to return null when the then key is requested. + return null; + } + if (prop === 'saved') { + window.console.warn( + 'The saved property is deprecated. Please use the response object directly.', + ); + + return preferences + .filter((preference) => target.hasOwnProperty(preference.name)) + .map((preference) => ({ + name: preference.name, + userid: Config.userid, + })); + } + return Reflect.get(target, prop, receiver); + }, + }; + + return Promise.resolve(new Proxy(response, debugLogger)); +}; /** * Get single user preference @@ -30,23 +77,25 @@ import {call as fetchMany} from 'core/ajax'; * @param {Number} userid User ID (defaults to current user) * @return {Promise} */ -export const getUserPreference = (name, userid = 0) => { - return getUserPreferences(name, userid) - .then(response => response.preferences[0].value); -}; +export const getUserPreference = (name, userid = 0) => getUserPreferences(name, userid) + .then((response) => response[name]); /** * Get multiple user preferences * * @param {String|null} name Name of the preference (omit if you want to retrieve all) * @param {Number} userid User ID (defaults to current user) - * @return {Promise} + * @return {Promise>} */ export const getUserPreferences = (name = null, userid = 0) => { - return fetchMany([{ - methodname: 'core_user_get_user_preferences', - args: {name, userid} - }])[0]; + checkUserId(userid); + const endpoint = ['current', 'preferences']; + + if (name) { + endpoint.push(name); + } + + return performGet('core_user', endpoint.join('/')); }; /** @@ -58,7 +107,16 @@ export const getUserPreferences = (name = null, userid = 0) => { * @return {Promise} */ export const setUserPreference = (name, value = null, userid = 0) => { - return setUserPreferences([{name, value, userid}]); + checkUserId(userid); + return performPost( + 'core_user', + `current/preferences/${name}`, + { + body: {value}, + }, + ) + // Return the result of the fetch call, and also add in the legacy saved property. + .then((response) => addLegacySavedProperty(response, [{name}])); }; /** @@ -68,10 +126,18 @@ export const setUserPreference = (name, value = null, userid = 0) => { * @return {Promise} */ export const setUserPreferences = (preferences) => { - return fetchMany([{ - methodname: 'core_user_set_user_preferences', - args: {preferences} - }])[0]; + preferences.forEach((preference) => checkUserId(preference.userid)); + return performPost( + 'core_user', + 'current/preferences', + { + body: { + preferences: Object.fromEntries (preferences.map((preference) => ([preference.name, preference.value]))), + }, + }, + ) + // Return the result of the fetch call, and also add in the legacy saved property. + .then((response) => addLegacySavedProperty(response, preferences)); }; /** From 091ae55c20a9a7746628655c345a4b535b00ca71 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 31 Oct 2023 10:19:46 +0800 Subject: [PATCH 12/16] MDL-81031 admin: Add swagger UI --- admin/settings/development.php | 11 ++++- admin/swaggerui.php | 87 ++++++++++++++++++++++++++++++++++ config-dist.php | 8 ++++ lang/en/admin.php | 1 + lib/classes/url.php | 22 +++++++++ lib/setup.php | 3 ++ lib/tests/url_test.php | 17 +++++++ 7 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 admin/swaggerui.php diff --git a/admin/settings/development.php b/admin/settings/development.php index a588b5eb24e..15aa7a28d6a 100644 --- a/admin/settings/development.php +++ b/admin/settings/development.php @@ -109,7 +109,16 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page // Add the 'profiling' page to admin block. $ADMIN->add('development', $temp); - // Web service test clients DO NOT COMMIT : THE EXTERNAL WEB PAGE IS NOT AN ADMIN PAGE !!!!! + $ADMIN->add( + parentname: 'development', + something: new admin_externalpage( + name: 'swaggerui', + visiblename: new lang_string('swaggerui', 'admin'), + url: "$CFG->wwwroot/admin/swaggerui.php", + ), + ); + + // Web service test clients DO NOT COMMIT : THE EXTERNAL WEB PAGE IS NOT AN ADMIN PAGE !!!!! $ADMIN->add('development', new admin_externalpage('testclient', new lang_string('testclient', 'webservice'), "$CFG->wwwroot/$CFG->admin/webservice/testclient.php")); diff --git a/admin/swaggerui.php b/admin/swaggerui.php new file mode 100644 index 00000000000..45a66d1470d --- /dev/null +++ b/admin/swaggerui.php @@ -0,0 +1,87 @@ +. + +/** + * Swagger UI for Moodle + * + * @package core_admin + * @copyright Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require('../config.php'); +require_once($CFG->libdir . '/adminlib.php'); + +$swaggerversion = '5.17.14'; + +$PAGE->set_url('/admin/swaggerui.php'); + +admin_externalpage_setup('swaggerui'); + +$PAGE->requires->css(new moodle_url("https://unpkg.com/swagger-ui-dist@{$swaggerversion}/swagger-ui.css")); + +echo $OUTPUT->header(); + +// These have to be manually added for now because they must be made cross-origin. The `js` method does not yet support this. +echo html_writer::tag( + tagname: 'script', + contents: '', + attributes: [ + 'src' => new moodle_url("https://unpkg.com/swagger-ui-dist@{$swaggerversion}/swagger-ui-bundle.js"), + 'crossorigin' => 'crossorigin', + ], +); +echo html_writer::tag( + tagname: 'script', + contents: '', + attributes: [ + 'src' => new moodle_url("https://unpkg.com/swagger-ui-plugin-hierarchical-tags"), + 'crossorigin' => 'crossorigin', + ], +); + +$openapipath = moodle_url::routed_path('/api/rest/v2/openapi.json')->out(); +$swaggerinit = <<requires->js_init_code( + jscode: $swaggerinit, + ondomready: true, +); + +echo html_writer::div('', '', [ + 'id' => 'swagger-ui', +]); + +echo $OUTPUT->footer(); diff --git a/config-dist.php b/config-dist.php index 5e0356c2c53..c0fb1163afb 100644 --- a/config-dist.php +++ b/config-dist.php @@ -180,6 +180,14 @@ $CFG->wwwroot = 'http://example.com/moodle'; $CFG->dataroot = '/home/example/moodledata'; +// Whether the Moodle router is fully configured. +// +// From Moodle 4.5 this is set to false. +// The default value will change in a future release. +// +// When not configured on the web server it must be accessed via https://example.com/moodle/r.php +// When configured the on the web server the 'r.php' may be removed. +$CFG->routerconfigured = false; //========================================================================= // 4. DATA FILES PERMISSIONS diff --git a/lang/en/admin.php b/lang/en/admin.php index 47fe143a29c..c017f789529 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -1354,6 +1354,7 @@ $string['supportemailsubject'] = 'Site support request - {$a}'; $string['supportavailability'] = 'Support availability'; $string['supportname'] = 'Support name'; $string['supportpage'] = 'Contact site support link'; +$string['swaggerui'] = 'Moodle REST API UI (SwaggerUI)'; $string['suspenduser'] = 'Suspend user account'; $string['switchlang'] = 'Switch lang directory'; $string['systempaths'] = 'System paths'; diff --git a/lib/classes/url.php b/lib/classes/url.php index af29e06d832..d55ee3688db 100644 --- a/lib/classes/url.php +++ b/lib/classes/url.php @@ -622,6 +622,28 @@ class url { return $url; } + /** + * Create a new moodle_url instance from routed path. + * + * @param string $path The routed path + * @param null|array $params The path parameters + * @param null|string $anchor The anchor + * @return self + */ + public static function routed_path( + string $path, + ?array $params = null, + ?string $anchor = null, + ): self { + global $CFG; + + if (!$CFG->routerconfigured) { + $path = '/r.php/' . ltrim($path, '/'); + } + $url = new self($path, $params, $anchor); + return $url; + } + /** * General moodle file url. * diff --git a/lib/setup.php b/lib/setup.php index e9f286a979c..a27e758ee56 100644 --- a/lib/setup.php +++ b/lib/setup.php @@ -196,6 +196,9 @@ if (!isset($CFG->wwwroot) or $CFG->wwwroot === 'http://example.com/moodle') { exit(1); } +// The router configuration is mandatory. +$CFG->routerconfigured = !empty($CFG->routerconfigured); + // Make sure there is some database table prefix. if (!isset($CFG->prefix)) { $CFG->prefix = ''; diff --git a/lib/tests/url_test.php b/lib/tests/url_test.php index 069dcc4be41..5b4cd2a6e31 100644 --- a/lib/tests/url_test.php +++ b/lib/tests/url_test.php @@ -669,4 +669,21 @@ final class url_test extends \advanced_testcase { ], ]; } + + /** + * Test that URL routed paths are generated correctly depending on the value of $CFG->routerconfigured. + */ + public function test_routed_path(): void { + global $CFG; + + $this->resetAfterTest(); + + $CFG->routerconfigured = false; + $url = url::routed_path('/example'); + $this->assertSame('/r.php/example', $url->out_as_local_url(false)); + + $CFG->routerconfigured = true; + $url = url::routed_path('/example'); + $this->assertSame('/example', $url->out_as_local_url(false)); + } } From f64ce7b46e1bd1695539a0abf424034b2e81c951 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 8 Nov 2023 15:28:05 +0800 Subject: [PATCH 13/16] MDL-81031 core: Add JS client-side validation --- lib/classes/param.php | 26 ++++++++++++++ lib/classes/param_clientside_regex.php | 40 +++++++++++++++++++++ lib/classes/router/schema/openapi_base.php | 4 +++ lib/tests/router/request_validator_test.php | 10 ++++++ lib/tests/router/schema/parameter_test.php | 1 + 5 files changed, 81 insertions(+) create mode 100644 lib/classes/param_clientside_regex.php diff --git a/lib/classes/param.php b/lib/classes/param.php index 9c817ff4f5b..91a78bb1b64 100644 --- a/lib/classes/param.php +++ b/lib/classes/param.php @@ -36,22 +36,26 @@ enum param: string { /** * PARAM_ALPHA - contains only English ascii letters [a-zA-Z]. */ + #[param_clientside_regex('^[a-zA-Z]+$')] case ALPHA = 'alpha'; /** * PARAM_ALPHAEXT the same contents as PARAM_ALPHA (English ascii letters [a-zA-Z]) plus the chars in quotes: "_-" allowed * NOTE: originally this allowed "/" too, please use PARAM_SAFEPATH if "/" needed */ + #[param_clientside_regex('^[a-zA-Z_\-]*$')] case ALPHAEXT = 'alphaext'; /** * PARAM_ALPHANUM - expected numbers 0-9 and English ascii letters [a-zA-Z] only. */ + #[param_clientside_regex('^[a-zA-Z0-9]*$')] case ALPHANUM = 'alphanum'; /** * PARAM_ALPHANUMEXT - expected numbers 0-9, letters (English ascii letters [a-zA-Z]) and _- only. */ + #[param_clientside_regex('^[a-zA-Z0-9_\-]*$')] case ALPHANUMEXT = 'alphanumext'; /** @@ -108,6 +112,7 @@ enum param: string { * This is preferred over PARAM_FLOAT for numbers typed in by the user. * Cleans localised numbers to computer readable numbers; false for invalid numbers. */ + #[param_clientside_regex('^\d*([\.,])\d+$')] case LOCALISEDFLOAT = 'localisedfloat'; /** @@ -165,6 +170,7 @@ enum param: string { /** * PARAM_SAFEDIR - safe directory name, suitable for include() and require() */ + #[param_clientside_regex('^[a-zA-Z0-9_\-]*$')] case SAFEDIR = 'safedir'; /** @@ -173,11 +179,13 @@ enum param: string { * * This is NOT intended to be used for absolute paths or any user uploaded files. */ + #[param_clientside_regex('^[a-zA-Z0-9\/_\-]*$')] case SAFEPATH = 'safepath'; /** * PARAM_SEQUENCE - expects a sequence of numbers like 8 to 1,5,6,4,6,8,9. Numbers and comma only. */ + #[param_clientside_regex('^[0-9,]*$')] case SEQUENCE = 'sequence'; /** @@ -316,6 +324,7 @@ enum param: string { * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter. * NOTE: numbers and underscores are strongly discouraged in plugin names! */ + #[param_clientside_regex('^[a-z][a-z0-9]*(_(?:[a-z][a-z0-9_](?!__))*)?[a-z0-9]+$')] case COMPONENT = 'component'; /** @@ -323,6 +332,7 @@ enum param: string { * It is usually used together with context id and component. * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter. */ + #[param_clientside_regex('^[a-z](?:[a-z0-9_](?!__))*[a-z0-9]+$')] case AREA = 'area'; /** @@ -330,6 +340,7 @@ enum param: string { * Only lowercase ascii letters, numbers and underscores are allowed, it has to start with a letter. * NOTE: numbers and underscores are strongly discouraged in plugin names! Underscores are forbidden in module names. */ + #[param_clientside_regex('^[a-z](?:[a-z0-9_](?!__))*[a-z0-9]+$')] case PLUGIN = 'plugin'; /** @@ -409,6 +420,21 @@ enum param: string { return $this->{$methodname}($value); } + /** + * Get the clientside regular expression for this parameter. + * + * @return null|string + */ + public function get_clientside_expression(): ?string { + $ref = new \ReflectionClassConstant(self::class, $this->name); + $attributes = $ref->getAttributes(param_clientside_regex::class); + if (count($attributes) === 0) { + return null; + } + + return $attributes[0]->newInstance()->regex; + } + /** * Returns a value for the named variable, taken from request arguments. * diff --git a/lib/classes/param_clientside_regex.php b/lib/classes/param_clientside_regex.php new file mode 100644 index 00000000000..c2c6009e37d --- /dev/null +++ b/lib/classes/param_clientside_regex.php @@ -0,0 +1,40 @@ +. + +namespace core; + +use Attribute; + +/** + * A JS-compatible regular expression to validate the format of a param. + * + * @package core + * @copyright 2023 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +#[Attribute(Attribute::TARGET_CLASS_CONSTANT)] +class param_clientside_regex { + /** + * Create a clientside regular expression for use with a \core\param enum case. + * + * @param string $regex The Regular Expression that validates the param case + */ + public function __construct( + /** @var string The Regular Expression that validates the param case */ + public readonly string $regex, + ) { + } +} diff --git a/lib/classes/router/schema/openapi_base.php b/lib/classes/router/schema/openapi_base.php index c464acfa7e5..a8a61fed2a9 100644 --- a/lib/classes/router/schema/openapi_base.php +++ b/lib/classes/router/schema/openapi_base.php @@ -234,6 +234,10 @@ abstract class openapi_base { default => 'string', }; + if ($pattern = $type->get_clientside_expression()) { + $data->pattern = $pattern; + } + return $data; } } diff --git a/lib/tests/router/request_validator_test.php b/lib/tests/router/request_validator_test.php index 49a8498b3d3..6ecdb1f88bf 100644 --- a/lib/tests/router/request_validator_test.php +++ b/lib/tests/router/request_validator_test.php @@ -122,7 +122,12 @@ final class request_validator_test extends route_testcase { * When a pathtype fails to validate, it will result in an HttpNotFoundException. */ public function test_validate_request_invalid_path_component(): void { + // Most of the path types are converted to regexes and will lead to a 404 before they get this far. $type = param::INT; + $this->assertEmpty( + $type->get_clientside_expression(), + 'This test requires a type with no clientside expression. Please update the test.', + ); $route = new route( path: '/example/{required}', @@ -145,7 +150,12 @@ final class request_validator_test extends route_testcase { * When a pathtype fails to validate, it will result in an HttpNotFoundException. */ public function test_validate_request_invalid_path_component_native(): void { + // Most of the path types are converted to regexes and will lead to a 404 before they get this far. $type = param::ALPHA; + $this->assertNotEmpty( + $type->get_clientside_expression(), + 'This test requires a type with clientside expression. Please update the test.', + ); $route = new route( path: '/example/{required}', diff --git a/lib/tests/router/schema/parameter_test.php b/lib/tests/router/schema/parameter_test.php index d488d91c7b8..a4a867e79ad 100644 --- a/lib/tests/router/schema/parameter_test.php +++ b/lib/tests/router/schema/parameter_test.php @@ -187,5 +187,6 @@ final class parameter_test extends route_testcase { $description = $param->get_openapi_description(new specification()); $this->assertNotNull($description->schema); $this->assertEquals('string', $description->schema->type); + $this->assertIsString($description->schema->pattern); } } From b3988ffa18e60a0fb771ebbcd29ae1cfa085b37a Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Fri, 12 Jul 2024 14:57:50 +0800 Subject: [PATCH 14/16] MDL-81031 core: Load standard libraries in router init --- r.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/r.php b/r.php index 3292af8f6be..2d59ba587f4 100644 --- a/r.php +++ b/r.php @@ -28,5 +28,29 @@ define('ABORT_AFTER_CONFIG', true); require_once('config.php'); +// Load the rest of the setup. +require_once("{$CFG->libdir}/setuplib.php"); // Functions that MUST be loaded first. + +// Load up standard libraries. +require_once("{$CFG->libdir}/filterlib.php"); // Functions for filtering test as it is output. +require_once("{$CFG->libdir}/ajax/ajaxlib.php"); // Functions for managing our use of JavaScript and YUI. +require_once("{$CFG->libdir}/weblib.php"); // Functions relating to HTTP and content. +require_once("{$CFG->libdir}/outputlib.php"); // Functions for generating output. +require_once("{$CFG->libdir}/navigationlib.php"); // Class for generating Navigation structure. +require_once("{$CFG->libdir}/dmllib.php"); // Database access. +require_once("{$CFG->libdir}/datalib.php"); // Legacy lib with a big-mix of functions. +require_once("{$CFG->libdir}/accesslib.php"); // Access control functions. +require_once("{$CFG->libdir}/deprecatedlib.php"); // Deprecated functions included for backward compatibility. +require_once("{$CFG->libdir}/moodlelib.php"); // Other general-purpose functions. +require_once("{$CFG->libdir}/enrollib.php"); // Enrolment related functions. +require_once("{$CFG->libdir}/pagelib.php"); // Library that defines the moodle_page class, used for $PAGE. +require_once("{$CFG->libdir}/blocklib.php"); // Library for controlling blocks. +require_once("{$CFG->libdir}/grouplib.php"); // Groups functions. +require_once("{$CFG->libdir}/sessionlib.php"); // All session and cookie related stuff. +require_once("{$CFG->libdir}/editorlib.php"); // All text editor related functions and classes. +require_once("{$CFG->libdir}/messagelib.php"); // Messagelib functions. +require_once("{$CFG->libdir}/modinfolib.php"); // Cached information on course-module instances. +require_once("{$CFG->dirroot}/cache/lib.php"); // Cache API. + $router = \core\di::get(\core\router::class); $router->serve(); From 90b2057620430f19db4477299f2f2997ed01d80b Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 31 Jul 2024 07:51:52 +0800 Subject: [PATCH 15/16] MDL-81031 core: Correct hinting for default exception handler --- lib/setuplib.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/setuplib.php b/lib/setuplib.php index eb2d347bbe0..1cb639f1a7b 100644 --- a/lib/setuplib.php +++ b/lib/setuplib.php @@ -130,10 +130,10 @@ function get_whoops(): ?\Whoops\Run { /** * Default exception handler. * - * @param Exception $ex + * @param Throwable $ex * @return void -does not return. Terminates execution! */ -function default_exception_handler($ex) { +function default_exception_handler(Throwable $ex): void { global $CFG, $DB, $OUTPUT, $USER, $FULLME, $SESSION, $PAGE; // detect active db transactions, rollback and log as error From 2b9af8ca386ef3b5083092697f53d1f5914ea5c0 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 31 Jul 2024 15:01:18 +0800 Subject: [PATCH 16/16] MDL-81031 mod_assign: Fix incorrect use of setUserPreference --- mod/assign/amd/build/grading_navigation.min.js | 2 +- mod/assign/amd/build/grading_navigation.min.js.map | 2 +- mod/assign/amd/src/grading_navigation.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mod/assign/amd/build/grading_navigation.min.js b/mod/assign/amd/build/grading_navigation.min.js index 6f8fa093d3f..1f95051dcd1 100644 --- a/mod/assign/amd/build/grading_navigation.min.js +++ b/mod/assign/amd/build/grading_navigation.min.js @@ -6,6 +6,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 3.1 */ -define("mod_assign/grading_navigation",["jquery","core/notification","core/str","core/form-autocomplete","core/ajax","core_user/repository","mod_assign/grading_form_change_checker"],(function($,notification,str,autocomplete,ajax,UserRepository,checker){var GradingNavigation=function(selector){this._regionSelector=selector,this._region=$(selector),this._filters=[],this._users=[],this._filteredUsers=[],this._lastXofYUpdate=0,this._firstLoadUsers=!0;let url=new URL(window.location);parseInt(url.searchParams.get("treset"))>0&&(url.searchParams.delete("treset"),window.history.replaceState({},"",url)),this._loadAllUsers(),this._region.find('[data-action="previous-user"]').on("click",this._handlePreviousUser.bind(this)),this._region.find('[data-action="next-user"]').on("click",this._handleNextUser.bind(this)),this._region.find('[data-action="change-user"]').on("change",this._handleChangeUser.bind(this)),this._region.find('[data-region="user-filters"]').on("click",this._toggleExpandFilters.bind(this)),this._region.find('[data-region="user-resettable"]').on("click",this._toggleResetTable.bind()),$(document).on("user-changed",this._refreshSelector.bind(this)),$(document).on("done-saving-show-next",this._handleNextUser.bind(this));var toggleLink=this._region.find('[data-region="user-filters"]');$(document.getElementById(toggleLink.attr("aria-controls"))).on("change","select",this._filterChanged.bind(this));var userid=$('[data-region="grading-navigation-panel"]').data("first-userid");userid&&this._selectUserById(userid),str.get_string("changeuser","mod_assign").done((function(s){autocomplete.enhance("[data-action=change-user]",!1,"mod_assign/participant_selector",s)})).fail(notification.exception),$(document).bind("start-loading-user",function(){this._isLoading=!0}.bind(this)),$(document).bind("finish-loading-user",function(){this._isLoading=!1}.bind(this))};return GradingNavigation.prototype._isLoading=!1,GradingNavigation.prototype._regionSelector=null,GradingNavigation.prototype._filters=null,GradingNavigation.prototype._users=null,GradingNavigation.prototype._region=null,GradingNavigation.prototype._lastFilters="",GradingNavigation.prototype._loadAllUsers=function(){var select=this._region.find("[data-action=change-user]"),assignmentid=select.attr("data-assignmentid"),groupid=select.attr("data-groupid"),filterPanel=this._region.find('[data-region="configure-filters"]'),filter=filterPanel.find('select[name="filter"]').val(),workflowFilter=filterPanel.find('select[name="workflowfilter"]');workflowFilter&&(filter+=","+workflowFilter.val());var markerFilter=filterPanel.find('select[name="markerfilter"]');return markerFilter&&(filter+=","+markerFilter.val()),this._lastFilters!=filter&&(this._lastFilters=filter,ajax.call([{methodname:"mod_assign_list_participants",args:{assignid:assignmentid,groupid:groupid,filter:"",onlyids:!0,tablesort:!0},done:this._usersLoaded.bind(this),fail:notification.exception}]),!0)},GradingNavigation.prototype._usersLoaded=function(users){if(this._firstLoadUsers=!1,this._filteredUsers=this._users=users,this._users.length){var toggleLink=this._region.find('[data-region="user-filters"]');$(document.getElementById(toggleLink.attr("aria-controls"))).find('select[name="filter"]').trigger("change")}else this._selectNoUser();this._triggerNextUserEvent()},GradingNavigation.prototype._checkClickOutsideConfigureFilters=function(event){var configPanel=this._region.find('[data-region="configure-filters"]');if(!configPanel.is(event.target)&&0===configPanel.has(event.target).length){var toggleLink=this._region.find('[data-region="user-filters"]');configPanel.hide(),configPanel.attr("aria-hidden","true"),toggleLink.attr("aria-expanded","false"),$(document).unbind("click.mod_assign_grading_navigation")}},GradingNavigation.prototype._updateFilterPreferences=function(userId,filterList,preferenceNames){var preferences=[],i=0;if(0==filterList.length||this._firstLoadUsers){var deferred=$.Deferred();return deferred.resolve(),deferred}for(i=0;i0&&!isNaN(useridnumber)&&useridnumber>0&&$(document).trigger("user-changed",useridnumber)))},GradingNavigation.prototype._toggleExpandFilters=function(event){event.preventDefault();var toggleLink=$(event.target).closest('[data-region="user-filters"]'),expanded="true"==toggleLink.attr("aria-expanded"),configPanel=$(document.getElementById(toggleLink.attr("aria-controls")));expanded?(configPanel.hide(),configPanel.attr("aria-hidden","true"),toggleLink.attr("aria-expanded","false"),$(document).unbind("click.mod_assign_grading_navigation")):(configPanel.css("display","inline-block"),configPanel.attr("aria-hidden","false"),toggleLink.attr("aria-expanded","true"),event.stopPropagation(),$(document).on("click.mod_assign_grading_navigation",this._checkClickOutsideConfigureFilters.bind(this)))},GradingNavigation.prototype._toggleResetTable=function(){let url=new URL(window.location);url.searchParams.set("treset","1"),window.location.href=url},GradingNavigation.prototype._handlePreviousUser=function(e){e.preventDefault();var currentUserId=this._region.find("[data-action=change-user]").attr("data-selected"),i=0,currentIndex=0;for(i=0;i0&&$(document).trigger("user-changed",userid)}else count&&this._selectUserById(this._filteredUsers[newIndex].id)},GradingNavigation.prototype._setCountString=function(x,y){var updateNumber;this._lastXofYUpdate++,updateNumber=this._lastXofYUpdate;var param={x:x,y:y};str.get_string("xofy","mod_assign",param).done(function(s){updateNumber==this._lastXofYUpdate&&this._region.find('[data-region="user-count-summary"]').text(s)}.bind(this)).fail(notification.exception)},GradingNavigation.prototype._refreshCount=function(){var userid=this._region.find("[data-action=change-user]").attr("data-selected"),i=0,currentIndex=0;if(isNaN(userid)||userid<=0)this._region.find('[data-region="user-count"]').hide();else{for(this._region.find('[data-region="user-count"]').show(),i=0;i0){var url=new URL(window.location);if(parseInt(url.searchParams.get("blindid"))>0){var newid=this._filteredUsers[currentIndex-1].recordid;url.searchParams.set("blindid",newid)}else url.searchParams.set("userid",userid);window.history.replaceState({},"",url)}}},GradingNavigation.prototype._refreshSelector=function(event,userid){var select=this._region.find("[data-action=change-user]");userid=parseInt(userid,10),!isNaN(userid)&&userid>0&&select.attr("data-selected",userid),this._refreshCount()},GradingNavigation.prototype._triggerNextUserEvent=function(){this._filteredUsers.length>1?$(document).trigger("next-user",{nextUserId:null,nextUser:!0}):$(document).trigger("next-user",{nextUser:!1})},GradingNavigation.prototype._handleChangeUser=function(){var select=this._region.find("[data-action=change-user]"),userid=parseInt(select.val(),10);this._isLoading||(checker.checkFormForChanges('[data-region="grade-panel"] .gradeform')?str.get_strings([{key:"unsavedchanges",component:"mod_assign"},{key:"unsavedchangesquestion",component:"mod_assign"},{key:"saveandcontinue",component:"mod_assign"},{key:"cancel",component:"core"}]).done((function(strs){notification.confirm(strs[0],strs[1],strs[2],strs[3],(function(){$(document).trigger("save-changes",userid)}))})):!isNaN(userid)&&userid>0&&(select.attr("data-selected",userid),$(document).trigger("user-changed",userid)))},GradingNavigation})); +define("mod_assign/grading_navigation",["jquery","core/notification","core/str","core/form-autocomplete","core/ajax","core_user/repository","mod_assign/grading_form_change_checker"],(function($,notification,str,autocomplete,ajax,UserRepository,checker){var GradingNavigation=function(selector){this._regionSelector=selector,this._region=$(selector),this._filters=[],this._users=[],this._filteredUsers=[],this._lastXofYUpdate=0,this._firstLoadUsers=!0;let url=new URL(window.location);parseInt(url.searchParams.get("treset"))>0&&(url.searchParams.delete("treset"),window.history.replaceState({},"",url)),this._loadAllUsers(),this._region.find('[data-action="previous-user"]').on("click",this._handlePreviousUser.bind(this)),this._region.find('[data-action="next-user"]').on("click",this._handleNextUser.bind(this)),this._region.find('[data-action="change-user"]').on("change",this._handleChangeUser.bind(this)),this._region.find('[data-region="user-filters"]').on("click",this._toggleExpandFilters.bind(this)),this._region.find('[data-region="user-resettable"]').on("click",this._toggleResetTable.bind()),$(document).on("user-changed",this._refreshSelector.bind(this)),$(document).on("done-saving-show-next",this._handleNextUser.bind(this));var toggleLink=this._region.find('[data-region="user-filters"]');$(document.getElementById(toggleLink.attr("aria-controls"))).on("change","select",this._filterChanged.bind(this));var userid=$('[data-region="grading-navigation-panel"]').data("first-userid");userid&&this._selectUserById(userid),str.get_string("changeuser","mod_assign").done((function(s){autocomplete.enhance("[data-action=change-user]",!1,"mod_assign/participant_selector",s)})).fail(notification.exception),$(document).bind("start-loading-user",function(){this._isLoading=!0}.bind(this)),$(document).bind("finish-loading-user",function(){this._isLoading=!1}.bind(this))};return GradingNavigation.prototype._isLoading=!1,GradingNavigation.prototype._regionSelector=null,GradingNavigation.prototype._filters=null,GradingNavigation.prototype._users=null,GradingNavigation.prototype._region=null,GradingNavigation.prototype._lastFilters="",GradingNavigation.prototype._loadAllUsers=function(){var select=this._region.find("[data-action=change-user]"),assignmentid=select.attr("data-assignmentid"),groupid=select.attr("data-groupid"),filterPanel=this._region.find('[data-region="configure-filters"]'),filter=filterPanel.find('select[name="filter"]').val(),workflowFilter=filterPanel.find('select[name="workflowfilter"]');workflowFilter&&(filter+=","+workflowFilter.val());var markerFilter=filterPanel.find('select[name="markerfilter"]');return markerFilter&&(filter+=","+markerFilter.val()),this._lastFilters!=filter&&(this._lastFilters=filter,ajax.call([{methodname:"mod_assign_list_participants",args:{assignid:assignmentid,groupid:groupid,filter:"",onlyids:!0,tablesort:!0},done:this._usersLoaded.bind(this),fail:notification.exception}]),!0)},GradingNavigation.prototype._usersLoaded=function(users){if(this._firstLoadUsers=!1,this._filteredUsers=this._users=users,this._users.length){var toggleLink=this._region.find('[data-region="user-filters"]');$(document.getElementById(toggleLink.attr("aria-controls"))).find('select[name="filter"]').trigger("change")}else this._selectNoUser();this._triggerNextUserEvent()},GradingNavigation.prototype._checkClickOutsideConfigureFilters=function(event){var configPanel=this._region.find('[data-region="configure-filters"]');if(!configPanel.is(event.target)&&0===configPanel.has(event.target).length){var toggleLink=this._region.find('[data-region="user-filters"]');configPanel.hide(),configPanel.attr("aria-hidden","true"),toggleLink.attr("aria-expanded","false"),$(document).unbind("click.mod_assign_grading_navigation")}},GradingNavigation.prototype._updateFilterPreferences=function(userId,filterList,preferenceNames){var preferences=[],i=0;if(0==filterList.length||this._firstLoadUsers){var deferred=$.Deferred();return deferred.resolve(),deferred}for(i=0;i0&&!isNaN(useridnumber)&&useridnumber>0&&$(document).trigger("user-changed",useridnumber)))},GradingNavigation.prototype._toggleExpandFilters=function(event){event.preventDefault();var toggleLink=$(event.target).closest('[data-region="user-filters"]'),expanded="true"==toggleLink.attr("aria-expanded"),configPanel=$(document.getElementById(toggleLink.attr("aria-controls")));expanded?(configPanel.hide(),configPanel.attr("aria-hidden","true"),toggleLink.attr("aria-expanded","false"),$(document).unbind("click.mod_assign_grading_navigation")):(configPanel.css("display","inline-block"),configPanel.attr("aria-hidden","false"),toggleLink.attr("aria-expanded","true"),event.stopPropagation(),$(document).on("click.mod_assign_grading_navigation",this._checkClickOutsideConfigureFilters.bind(this)))},GradingNavigation.prototype._toggleResetTable=function(){let url=new URL(window.location);url.searchParams.set("treset","1"),window.location.href=url},GradingNavigation.prototype._handlePreviousUser=function(e){e.preventDefault();var currentUserId=this._region.find("[data-action=change-user]").attr("data-selected"),i=0,currentIndex=0;for(i=0;i0&&$(document).trigger("user-changed",userid)}else count&&this._selectUserById(this._filteredUsers[newIndex].id)},GradingNavigation.prototype._setCountString=function(x,y){var updateNumber;this._lastXofYUpdate++,updateNumber=this._lastXofYUpdate;var param={x:x,y:y};str.get_string("xofy","mod_assign",param).done(function(s){updateNumber==this._lastXofYUpdate&&this._region.find('[data-region="user-count-summary"]').text(s)}.bind(this)).fail(notification.exception)},GradingNavigation.prototype._refreshCount=function(){var userid=this._region.find("[data-action=change-user]").attr("data-selected"),i=0,currentIndex=0;if(isNaN(userid)||userid<=0)this._region.find('[data-region="user-count"]').hide();else{for(this._region.find('[data-region="user-count"]').show(),i=0;i0){var url=new URL(window.location);if(parseInt(url.searchParams.get("blindid"))>0){var newid=this._filteredUsers[currentIndex-1].recordid;url.searchParams.set("blindid",newid)}else url.searchParams.set("userid",userid);window.history.replaceState({},"",url)}}},GradingNavigation.prototype._refreshSelector=function(event,userid){var select=this._region.find("[data-action=change-user]");userid=parseInt(userid,10),!isNaN(userid)&&userid>0&&select.attr("data-selected",userid),this._refreshCount()},GradingNavigation.prototype._triggerNextUserEvent=function(){this._filteredUsers.length>1?$(document).trigger("next-user",{nextUserId:null,nextUser:!0}):$(document).trigger("next-user",{nextUser:!1})},GradingNavigation.prototype._handleChangeUser=function(){var select=this._region.find("[data-action=change-user]"),userid=parseInt(select.val(),10);this._isLoading||(checker.checkFormForChanges('[data-region="grade-panel"] .gradeform')?str.get_strings([{key:"unsavedchanges",component:"mod_assign"},{key:"unsavedchangesquestion",component:"mod_assign"},{key:"saveandcontinue",component:"mod_assign"},{key:"cancel",component:"core"}]).done((function(strs){notification.confirm(strs[0],strs[1],strs[2],strs[3],(function(){$(document).trigger("save-changes",userid)}))})):!isNaN(userid)&&userid>0&&(select.attr("data-selected",userid),$(document).trigger("user-changed",userid)))},GradingNavigation})); //# sourceMappingURL=grading_navigation.min.js.map \ No newline at end of file diff --git a/mod/assign/amd/build/grading_navigation.min.js.map b/mod/assign/amd/build/grading_navigation.min.js.map index c375784c8bb..7b20b6d3e13 100644 --- a/mod/assign/amd/build/grading_navigation.min.js.map +++ b/mod/assign/amd/build/grading_navigation.min.js.map @@ -1 +1 @@ -{"version":3,"file":"grading_navigation.min.js","sources":["../src/grading_navigation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript to handle changing users via the user selector in the header.\n *\n * @module mod_assign/grading_navigation\n * @copyright 2016 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.1\n */\ndefine(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete',\n 'core/ajax', 'core_user/repository', 'mod_assign/grading_form_change_checker'],\n function($, notification, str, autocomplete, ajax, UserRepository, checker) {\n\n /**\n * GradingNavigation class.\n *\n * @class mod_assign/grading_navigation\n * @param {String} selector The selector for the page region containing the user navigation.\n */\n var GradingNavigation = function(selector) {\n this._regionSelector = selector;\n this._region = $(selector);\n this._filters = [];\n this._users = [];\n this._filteredUsers = [];\n this._lastXofYUpdate = 0;\n this._firstLoadUsers = true;\n\n let url = new URL(window.location);\n if (parseInt(url.searchParams.get('treset')) > 0) {\n // Remove 'treset' url parameter to make sure that\n // table preferences won't be reset on page refresh.\n url.searchParams.delete('treset');\n window.history.replaceState({}, \"\", url);\n }\n\n // Get the current user list from a webservice.\n this._loadAllUsers();\n\n // We do not allow navigation while ajax requests are pending.\n // Attach listeners to the select and arrow buttons.\n\n this._region.find('[data-action=\"previous-user\"]').on('click', this._handlePreviousUser.bind(this));\n this._region.find('[data-action=\"next-user\"]').on('click', this._handleNextUser.bind(this));\n this._region.find('[data-action=\"change-user\"]').on('change', this._handleChangeUser.bind(this));\n this._region.find('[data-region=\"user-filters\"]').on('click', this._toggleExpandFilters.bind(this));\n this._region.find('[data-region=\"user-resettable\"]').on('click', this._toggleResetTable.bind());\n\n $(document).on('user-changed', this._refreshSelector.bind(this));\n $(document).on('done-saving-show-next', this._handleNextUser.bind(this));\n\n // Position the configure filters panel under the link that expands it.\n var toggleLink = this._region.find('[data-region=\"user-filters\"]');\n var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));\n\n configPanel.on('change', 'select', this._filterChanged.bind(this));\n\n var userid = $('[data-region=\"grading-navigation-panel\"]').data('first-userid');\n if (userid) {\n this._selectUserById(userid);\n }\n\n str.get_string('changeuser', 'mod_assign').done(function(s) {\n autocomplete.enhance('[data-action=change-user]', false, 'mod_assign/participant_selector', s);\n }\n ).fail(notification.exception);\n\n $(document).bind(\"start-loading-user\", function() {\n this._isLoading = true;\n }.bind(this));\n $(document).bind(\"finish-loading-user\", function() {\n this._isLoading = false;\n }.bind(this));\n };\n\n /** @property {Boolean} Boolean tracking active ajax requests. */\n GradingNavigation.prototype._isLoading = false;\n\n /** @property {String} Selector for the page region containing the user navigation. */\n GradingNavigation.prototype._regionSelector = null;\n\n /** @property {Array} The list of active filter keys */\n GradingNavigation.prototype._filters = null;\n\n /** @property {Array} The list of users */\n GradingNavigation.prototype._users = null;\n\n /** @property {JQuery} JQuery node for the page region containing the user navigation. */\n GradingNavigation.prototype._region = null;\n\n /** @property {String} Last active filters */\n GradingNavigation.prototype._lastFilters = '';\n\n /**\n * Load the list of all users for this assignment.\n *\n * @private\n * @method _loadAllUsers\n * @return {Boolean} True if the user list was fetched.\n */\n GradingNavigation.prototype._loadAllUsers = function() {\n var select = this._region.find('[data-action=change-user]');\n var assignmentid = select.attr('data-assignmentid');\n var groupid = select.attr('data-groupid');\n\n var filterPanel = this._region.find('[data-region=\"configure-filters\"]');\n var filter = filterPanel.find('select[name=\"filter\"]').val();\n var workflowFilter = filterPanel.find('select[name=\"workflowfilter\"]');\n if (workflowFilter) {\n filter += ',' + workflowFilter.val();\n }\n var markerFilter = filterPanel.find('select[name=\"markerfilter\"]');\n if (markerFilter) {\n filter += ',' + markerFilter.val();\n }\n\n if (this._lastFilters == filter) {\n return false;\n }\n this._lastFilters = filter;\n\n ajax.call([{\n methodname: 'mod_assign_list_participants',\n args: {assignid: assignmentid, groupid: groupid, filter: '', onlyids: true, tablesort: true},\n done: this._usersLoaded.bind(this),\n fail: notification.exception\n }]);\n return true;\n };\n\n /**\n * Call back to rebuild the user selector and x of y info when the user list is updated.\n *\n * @private\n * @method _usersLoaded\n * @param {Array} users\n */\n GradingNavigation.prototype._usersLoaded = function(users) {\n this._firstLoadUsers = false;\n this._filteredUsers = this._users = users;\n if (this._users.length) {\n // Position the configure filters panel under the link that expands it.\n var toggleLink = this._region.find('[data-region=\"user-filters\"]');\n var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));\n\n configPanel.find('select[name=\"filter\"]').trigger('change');\n } else {\n this._selectNoUser();\n }\n this._triggerNextUserEvent();\n };\n\n /**\n * Close the configure filters panel if a click is detected outside of it.\n *\n * @private\n * @method _checkClickOutsideConfigureFilters\n * @param {Event} event\n */\n GradingNavigation.prototype._checkClickOutsideConfigureFilters = function(event) {\n var configPanel = this._region.find('[data-region=\"configure-filters\"]');\n\n if (!configPanel.is(event.target) && configPanel.has(event.target).length === 0) {\n var toggleLink = this._region.find('[data-region=\"user-filters\"]');\n\n configPanel.hide();\n configPanel.attr('aria-hidden', 'true');\n toggleLink.attr('aria-expanded', 'false');\n $(document).unbind('click.mod_assign_grading_navigation');\n }\n };\n\n /**\n * Close the configure filters panel if a click is detected outside of it.\n *\n * @private\n * @method _updateFilterPreference\n * @param {Number} userId The current user id.\n * @param {Array} filterList The list of current filter values.\n * @param {Array} preferenceNames The names of the preferences to update\n * @return {Promise} Resolved when all the preferences are updated.\n */\n GradingNavigation.prototype._updateFilterPreferences = function(userId, filterList, preferenceNames) {\n var preferences = [],\n i = 0;\n\n if (filterList.length == 0 || this._firstLoadUsers) {\n // Nothing to update.\n var deferred = $.Deferred();\n deferred.resolve();\n return deferred;\n }\n // General filter.\n // Set the user preferences to the current filters.\n for (i = 0; i < filterList.length; i++) {\n var newValue = filterList[i];\n if (newValue == 'none') {\n newValue = '';\n }\n\n preferences.push({\n userid: userId,\n name: preferenceNames[i],\n value: newValue\n });\n }\n\n return UserRepository.setUserPreferences(preferences);\n };\n /**\n * Turn a filter on or off.\n *\n * @private\n * @method _filterChanged\n */\n GradingNavigation.prototype._filterChanged = function() {\n // There are 3 types of filter right now.\n var filterPanel = this._region.find('[data-region=\"configure-filters\"]');\n var filters = filterPanel.find('select');\n var preferenceNames = [];\n\n this._filters = [];\n filters.each(function(idx, ele) {\n var element = $(ele);\n this._filters.push(element.val());\n preferenceNames.push('assign_' + element.prop('name'));\n }.bind(this));\n\n // Update the active filter string.\n var filterlist = [];\n filterPanel.find('option:checked').each(function(idx, ele) {\n filterlist[filterlist.length] = $(ele).text();\n });\n if (filterlist.length) {\n this._region.find('[data-region=\"user-filters\"] span').text(filterlist.join(', '));\n } else {\n str.get_string('nofilters', 'mod_assign').done(function(s) {\n this._region.find('[data-region=\"user-filters\"] span').text(s);\n }.bind(this)).fail(notification.exception);\n }\n\n var select = this._region.find('[data-action=change-user]');\n var currentUserID = select.data('currentuserid');\n this._updateFilterPreferences(currentUserID, this._filters, preferenceNames).done(function() {\n // Reload the list of users to apply the new filters.\n if (!this._loadAllUsers()) {\n var userid = parseInt(select.attr('data-selected'));\n let foundIndex = null;\n // Search the returned users for the current selection.\n $.each(this._filteredUsers, function(index, user) {\n if (userid == user.id) {\n foundIndex = index;\n }\n });\n\n if (this._filteredUsers.length && foundIndex !== null) {\n this._selectUserById(this._filteredUsers[foundIndex].id);\n } else {\n this._selectNoUser();\n }\n\n }\n }.bind(this)).fail(notification.exception);\n this._refreshCount();\n };\n\n /**\n * Select no users, because no users match the filters.\n *\n * @private\n * @method _selectNoUser\n */\n GradingNavigation.prototype._selectNoUser = function() {\n // Detect unsaved changes, and offer to save them - otherwise change user right now.\n if (this._isLoading) {\n return;\n }\n if (checker.checkFormForChanges('[data-region=\"grade-panel\"] .gradeform')) {\n // Form has changes, so we need to confirm before switching users.\n str.get_strings([\n {key: 'unsavedchanges', component: 'mod_assign'},\n {key: 'unsavedchangesquestion', component: 'mod_assign'},\n {key: 'saveandcontinue', component: 'mod_assign'},\n {key: 'cancel', component: 'core'},\n ]).done(function(strs) {\n notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {\n $(document).trigger('save-changes', -1);\n });\n });\n } else {\n $(document).trigger('user-changed', -1);\n }\n };\n\n /**\n * Select the specified user by id.\n *\n * @private\n * @method _selectUserById\n * @param {Number} userid\n */\n GradingNavigation.prototype._selectUserById = function(userid) {\n var select = this._region.find('[data-action=change-user]');\n var useridnumber = parseInt(userid, 10);\n\n // Detect unsaved changes, and offer to save them - otherwise change user right now.\n if (this._isLoading) {\n return;\n }\n if (checker.checkFormForChanges('[data-region=\"grade-panel\"] .gradeform')) {\n // Form has changes, so we need to confirm before switching users.\n str.get_strings([\n {key: 'unsavedchanges', component: 'mod_assign'},\n {key: 'unsavedchangesquestion', component: 'mod_assign'},\n {key: 'saveandcontinue', component: 'mod_assign'},\n {key: 'cancel', component: 'core'},\n ]).done(function(strs) {\n notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {\n $(document).trigger('save-changes', useridnumber);\n });\n });\n } else {\n select.attr('data-selected', userid);\n\n // If we have some filtered users, and userid is specified, then trigger change.\n if (this._filteredUsers.length > 0 && !isNaN(useridnumber) && useridnumber > 0) {\n $(document).trigger('user-changed', useridnumber);\n }\n }\n };\n\n /**\n * Expand or collapse the filter config panel.\n *\n * @private\n * @method _toggleExpandFilters\n * @param {Event} event\n */\n GradingNavigation.prototype._toggleExpandFilters = function(event) {\n event.preventDefault();\n var toggleLink = $(event.target).closest('[data-region=\"user-filters\"]');\n var expanded = toggleLink.attr('aria-expanded') == 'true';\n var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));\n\n if (expanded) {\n configPanel.hide();\n configPanel.attr('aria-hidden', 'true');\n toggleLink.attr('aria-expanded', 'false');\n $(document).unbind('click.mod_assign_grading_navigation');\n } else {\n configPanel.css('display', 'inline-block');\n configPanel.attr('aria-hidden', 'false');\n toggleLink.attr('aria-expanded', 'true');\n event.stopPropagation();\n $(document).on('click.mod_assign_grading_navigation', this._checkClickOutsideConfigureFilters.bind(this));\n }\n };\n\n /**\n * Reset table preferences.\n *\n * @private\n * @method _toggleResetTable\n */\n GradingNavigation.prototype._toggleResetTable = function() {\n let url = new URL(window.location);\n url.searchParams.set('treset', '1');\n window.location.href = url;\n };\n\n /**\n * Change to the previous user in the grading list.\n *\n * @private\n * @method _handlePreviousUser\n * @param {Event} e\n */\n GradingNavigation.prototype._handlePreviousUser = function(e) {\n e.preventDefault();\n var select = this._region.find('[data-action=change-user]');\n var currentUserId = select.attr('data-selected');\n var i = 0;\n var currentIndex = 0;\n\n for (i = 0; i < this._filteredUsers.length; i++) {\n if (this._filteredUsers[i].id == currentUserId) {\n currentIndex = i;\n break;\n }\n }\n\n var count = this._filteredUsers.length;\n var newIndex = (currentIndex - 1);\n if (newIndex < 0) {\n newIndex = count - 1;\n }\n\n if (count) {\n this._selectUserById(this._filteredUsers[newIndex].id);\n }\n };\n\n /**\n * Change to the next user in the grading list.\n *\n * @param {Event} e\n * @param {Boolean} saved Has the form already been saved? Skips checking for changes if true.\n */\n GradingNavigation.prototype._handleNextUser = function(e, saved) {\n e.preventDefault();\n var select = this._region.find('[data-action=change-user]');\n var currentUserId = select.attr('data-selected');\n var i = 0;\n var currentIndex = 0;\n\n for (i = 0; i < this._filteredUsers.length; i++) {\n if (this._filteredUsers[i].id == currentUserId) {\n currentIndex = i;\n break;\n }\n }\n\n var count = this._filteredUsers.length;\n var newIndex = (currentIndex + 1) % count;\n\n if (saved && count) {\n // If we've already saved the grade, skip checking if we've made any changes.\n var userid = this._filteredUsers[newIndex].id;\n var useridnumber = parseInt(userid, 10);\n select.attr('data-selected', userid);\n if (!isNaN(useridnumber) && useridnumber > 0) {\n $(document).trigger('user-changed', userid);\n }\n } else if (count) {\n this._selectUserById(this._filteredUsers[newIndex].id);\n }\n };\n\n /**\n * Set count string. This method only sets the value for the last time it was ever called to deal\n * with promises that return in a non-predictable order.\n *\n * @private\n * @method _setCountString\n * @param {Number} x\n * @param {Number} y\n */\n GradingNavigation.prototype._setCountString = function(x, y) {\n var updateNumber = 0;\n this._lastXofYUpdate++;\n updateNumber = this._lastXofYUpdate;\n\n var param = {x: x, y: y};\n str.get_string('xofy', 'mod_assign', param).done(function(s) {\n if (updateNumber == this._lastXofYUpdate) {\n this._region.find('[data-region=\"user-count-summary\"]').text(s);\n }\n }.bind(this)).fail(notification.exception);\n };\n\n /**\n * Rebuild the x of y string.\n *\n * @private\n * @method _refreshCount\n */\n GradingNavigation.prototype._refreshCount = function() {\n var select = this._region.find('[data-action=change-user]');\n var userid = select.attr('data-selected');\n var i = 0;\n var currentIndex = 0;\n\n if (isNaN(userid) || userid <= 0) {\n this._region.find('[data-region=\"user-count\"]').hide();\n } else {\n this._region.find('[data-region=\"user-count\"]').show();\n\n for (i = 0; i < this._filteredUsers.length; i++) {\n if (this._filteredUsers[i].id == userid) {\n currentIndex = i;\n break;\n }\n }\n var count = this._filteredUsers.length;\n if (count) {\n currentIndex += 1;\n }\n this._setCountString(currentIndex, count);\n // Update window URL\n if (currentIndex > 0) {\n var url = new URL(window.location);\n if (parseInt(url.searchParams.get('blindid')) > 0) {\n var newid = this._filteredUsers[currentIndex - 1].recordid;\n url.searchParams.set('blindid', newid);\n } else {\n url.searchParams.set('userid', userid);\n }\n // We do this so a browser refresh will return to the same user.\n window.history.replaceState({}, \"\", url);\n }\n }\n };\n\n /**\n * Respond to a user-changed event by updating the selector.\n *\n * @private\n * @method _refreshSelector\n * @param {Event} event\n * @param {String} userid\n */\n GradingNavigation.prototype._refreshSelector = function(event, userid) {\n var select = this._region.find('[data-action=change-user]');\n userid = parseInt(userid, 10);\n\n if (!isNaN(userid) && userid > 0) {\n select.attr('data-selected', userid);\n }\n this._refreshCount();\n };\n\n /**\n * Trigger the next user event depending on the number of filtered users\n *\n * @private\n * @method _triggerNextUserEvent\n */\n GradingNavigation.prototype._triggerNextUserEvent = function() {\n if (this._filteredUsers.length > 1) {\n $(document).trigger('next-user', {nextUserId: null, nextUser: true});\n } else {\n $(document).trigger('next-user', {nextUser: false});\n }\n };\n\n /**\n * Change to a different user in the grading list.\n *\n * @private\n * @method _handleChangeUser\n */\n GradingNavigation.prototype._handleChangeUser = function() {\n var select = this._region.find('[data-action=change-user]');\n var userid = parseInt(select.val(), 10);\n\n if (this._isLoading) {\n return;\n }\n if (checker.checkFormForChanges('[data-region=\"grade-panel\"] .gradeform')) {\n // Form has changes, so we need to confirm before switching users.\n str.get_strings([\n {key: 'unsavedchanges', component: 'mod_assign'},\n {key: 'unsavedchangesquestion', component: 'mod_assign'},\n {key: 'saveandcontinue', component: 'mod_assign'},\n {key: 'cancel', component: 'core'},\n ]).done(function(strs) {\n notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {\n $(document).trigger('save-changes', userid);\n });\n });\n } else {\n if (!isNaN(userid) && userid > 0) {\n select.attr('data-selected', userid);\n\n $(document).trigger('user-changed', userid);\n }\n }\n };\n\n return GradingNavigation;\n});\n"],"names":["define","$","notification","str","autocomplete","ajax","UserRepository","checker","GradingNavigation","selector","_regionSelector","_region","_filters","_users","_filteredUsers","_lastXofYUpdate","_firstLoadUsers","url","URL","window","location","parseInt","searchParams","get","delete","history","replaceState","_loadAllUsers","find","on","this","_handlePreviousUser","bind","_handleNextUser","_handleChangeUser","_toggleExpandFilters","_toggleResetTable","document","_refreshSelector","toggleLink","getElementById","attr","_filterChanged","userid","data","_selectUserById","get_string","done","s","enhance","fail","exception","_isLoading","prototype","_lastFilters","select","assignmentid","groupid","filterPanel","filter","val","workflowFilter","markerFilter","call","methodname","args","assignid","onlyids","tablesort","_usersLoaded","users","length","trigger","_selectNoUser","_triggerNextUserEvent","_checkClickOutsideConfigureFilters","event","configPanel","is","target","has","hide","unbind","_updateFilterPreferences","userId","filterList","preferenceNames","preferences","i","deferred","Deferred","resolve","newValue","push","name","value","setUserPreferences","filters","each","idx","ele","element","prop","filterlist","text","join","currentUserID","foundIndex","index","user","id","_refreshCount","checkFormForChanges","get_strings","key","component","strs","confirm","useridnumber","isNaN","preventDefault","closest","expanded","css","stopPropagation","set","href","e","currentUserId","currentIndex","count","newIndex","saved","_setCountString","x","y","updateNumber","param","show","newid","recordid","nextUserId","nextUser"],"mappings":";;;;;;;;AAuBAA,uCAAO,CAAC,SAAU,oBAAqB,WAAY,yBAC3C,YAAa,uBAAwB,2CACtC,SAASC,EAAGC,aAAcC,IAAKC,aAAcC,KAAMC,eAAgBC,aAQlEC,kBAAoB,SAASC,eACxBC,gBAAkBD,cAClBE,QAAUV,EAAEQ,eACZG,SAAW,QACXC,OAAS,QACTC,eAAiB,QACjBC,gBAAkB,OAClBC,iBAAkB,MAEnBC,IAAM,IAAIC,IAAIC,OAAOC,UACrBC,SAASJ,IAAIK,aAAaC,IAAI,WAAa,IAG3CN,IAAIK,aAAaE,OAAO,UACxBL,OAAOM,QAAQC,aAAa,GAAI,GAAIT,WAInCU,qBAKAhB,QAAQiB,KAAK,iCAAiCC,GAAG,QAASC,KAAKC,oBAAoBC,KAAKF,YACxFnB,QAAQiB,KAAK,6BAA6BC,GAAG,QAASC,KAAKG,gBAAgBD,KAAKF,YAChFnB,QAAQiB,KAAK,+BAA+BC,GAAG,SAAUC,KAAKI,kBAAkBF,KAAKF,YACrFnB,QAAQiB,KAAK,gCAAgCC,GAAG,QAASC,KAAKK,qBAAqBH,KAAKF,YACxFnB,QAAQiB,KAAK,mCAAmCC,GAAG,QAASC,KAAKM,kBAAkBJ,QAExF/B,EAAEoC,UAAUR,GAAG,eAAgBC,KAAKQ,iBAAiBN,KAAKF,OAC1D7B,EAAEoC,UAAUR,GAAG,wBAAyBC,KAAKG,gBAAgBD,KAAKF,WAG9DS,WAAaT,KAAKnB,QAAQiB,KAAK,gCACjB3B,EAAEoC,SAASG,eAAeD,WAAWE,KAAK,mBAEhDZ,GAAG,SAAU,SAAUC,KAAKY,eAAeV,KAAKF,WAExDa,OAAS1C,EAAE,4CAA4C2C,KAAK,gBAC5DD,aACKE,gBAAgBF,QAGzBxC,IAAI2C,WAAW,aAAc,cAAcC,MAAK,SAASC,GACjD5C,aAAa6C,QAAQ,6BAA6B,EAAO,kCAAmCD,MAElGE,KAAKhD,aAAaiD,WAEpBlD,EAAEoC,UAAUL,KAAK,qBAAsB,gBAC9BoB,YAAa,GACpBpB,KAAKF,OACP7B,EAAEoC,UAAUL,KAAK,sBAAuB,gBAC/BoB,YAAa,GACpBpB,KAAKF,eAIXtB,kBAAkB6C,UAAUD,YAAa,EAGzC5C,kBAAkB6C,UAAU3C,gBAAkB,KAG9CF,kBAAkB6C,UAAUzC,SAAW,KAGvCJ,kBAAkB6C,UAAUxC,OAAS,KAGrCL,kBAAkB6C,UAAU1C,QAAU,KAGtCH,kBAAkB6C,UAAUC,aAAe,GAS3C9C,kBAAkB6C,UAAU1B,cAAgB,eACpC4B,OAASzB,KAAKnB,QAAQiB,KAAK,6BAC3B4B,aAAeD,OAAOd,KAAK,qBAC3BgB,QAAUF,OAAOd,KAAK,gBAEtBiB,YAAc5B,KAAKnB,QAAQiB,KAAK,qCAChC+B,OAASD,YAAY9B,KAAK,yBAAyBgC,MACnDC,eAAiBH,YAAY9B,KAAK,iCAClCiC,iBACAF,QAAU,IAAME,eAAeD,WAE/BE,aAAeJ,YAAY9B,KAAK,sCAChCkC,eACAH,QAAU,IAAMG,aAAaF,OAG7B9B,KAAKwB,cAAgBK,cAGpBL,aAAeK,OAEpBtD,KAAK0D,KAAK,CAAC,CACPC,WAAY,+BACZC,KAAM,CAACC,SAAUV,aAAcC,QAASA,QAASE,OAAQ,GAAIQ,SAAS,EAAMC,WAAW,GACvFrB,KAAMjB,KAAKuC,aAAarC,KAAKF,MAC7BoB,KAAMhD,aAAaiD,cAEhB,IAUX3C,kBAAkB6C,UAAUgB,aAAe,SAASC,eAC3CtD,iBAAkB,OAClBF,eAAiBgB,KAAKjB,OAASyD,MAChCxC,KAAKjB,OAAO0D,OAAQ,KAEhBhC,WAAaT,KAAKnB,QAAQiB,KAAK,gCACjB3B,EAAEoC,SAASG,eAAeD,WAAWE,KAAK,mBAEhDb,KAAK,yBAAyB4C,QAAQ,oBAE7CC,qBAEJC,yBAUTlE,kBAAkB6C,UAAUsB,mCAAqC,SAASC,WAClEC,YAAc/C,KAAKnB,QAAQiB,KAAK,yCAE/BiD,YAAYC,GAAGF,MAAMG,SAAoD,IAAzCF,YAAYG,IAAIJ,MAAMG,QAAQR,OAAc,KACzEhC,WAAaT,KAAKnB,QAAQiB,KAAK,gCAEnCiD,YAAYI,OACZJ,YAAYpC,KAAK,cAAe,QAChCF,WAAWE,KAAK,gBAAiB,SACjCxC,EAAEoC,UAAU6C,OAAO,yCAc3B1E,kBAAkB6C,UAAU8B,yBAA2B,SAASC,OAAQC,WAAYC,qBAC5EC,YAAc,GACdC,EAAI,KAEiB,GAArBH,WAAWd,QAAezC,KAAKd,gBAAiB,KAE5CyE,SAAWxF,EAAEyF,kBACjBD,SAASE,UACFF,aAIND,EAAI,EAAGA,EAAIH,WAAWd,OAAQiB,IAAK,KAChCI,SAAWP,WAAWG,GACV,QAAZI,WACAA,SAAW,IAGfL,YAAYM,KAAK,CACblD,OAAQyC,OACRU,KAAMR,gBAAgBE,GACtBO,MAAOH,kBAIRtF,eAAe0F,mBAAmBT,cAQ7C/E,kBAAkB6C,UAAUX,eAAiB,eAErCgB,YAAc5B,KAAKnB,QAAQiB,KAAK,qCAChCqE,QAAUvC,YAAY9B,KAAK,UAC3B0D,gBAAkB,QAEjB1E,SAAW,GAChBqF,QAAQC,KAAK,SAASC,IAAKC,SACnBC,QAAUpG,EAAEmG,UACXxF,SAASiF,KAAKQ,QAAQzC,OAC3B0B,gBAAgBO,KAAK,UAAYQ,QAAQC,KAAK,UAChDtE,KAAKF,WAGHyE,WAAa,GACjB7C,YAAY9B,KAAK,kBAAkBsE,MAAK,SAASC,IAAKC,KAClDG,WAAWA,WAAWhC,QAAUtE,EAAEmG,KAAKI,UAEvCD,WAAWhC,YACN5D,QAAQiB,KAAK,qCAAqC4E,KAAKD,WAAWE,KAAK,OAE5EtG,IAAI2C,WAAW,YAAa,cAAcC,KAAK,SAASC,QAC/CrC,QAAQiB,KAAK,qCAAqC4E,KAAKxD,IAC9DhB,KAAKF,OAAOoB,KAAKhD,aAAaiD,eAGhCI,OAASzB,KAAKnB,QAAQiB,KAAK,6BAC3B8E,cAAgBnD,OAAOX,KAAK,sBAC3BuC,yBAAyBuB,cAAe5E,KAAKlB,SAAU0E,iBAAiBvC,KAAK,eAEzEjB,KAAKH,gBAAiB,KACnBgB,OAAStB,SAASkC,OAAOd,KAAK,sBAC9BkE,WAAa,KAEjB1G,EAAEiG,KAAKpE,KAAKhB,gBAAgB,SAAS8F,MAAOC,MACpClE,QAAUkE,KAAKC,KACfH,WAAaC,UAIjB9E,KAAKhB,eAAeyD,QAAyB,OAAfoC,gBACzB9D,gBAAgBf,KAAKhB,eAAe6F,YAAYG,SAEhDrC,kBAIfzC,KAAKF,OAAOoB,KAAKhD,aAAaiD,gBAC3B4D,iBASTvG,kBAAkB6C,UAAUoB,cAAgB,WAEpC3C,KAAKsB,aAGL7C,QAAQyG,oBAAoB,0CAE5B7G,IAAI8G,YAAY,CACZ,CAACC,IAAK,iBAAkBC,UAAW,cACnC,CAACD,IAAK,yBAA0BC,UAAW,cAC3C,CAACD,IAAK,kBAAmBC,UAAW,cACpC,CAACD,IAAK,SAAUC,UAAW,UAC5BpE,MAAK,SAASqE,MACblH,aAAamH,QAAQD,KAAK,GAAIA,KAAK,GAAIA,KAAK,GAAIA,KAAK,IAAI,WACrDnH,EAAEoC,UAAUmC,QAAQ,gBAAiB,SAI7CvE,EAAEoC,UAAUmC,QAAQ,gBAAiB,KAW7ChE,kBAAkB6C,UAAUR,gBAAkB,SAASF,YAC/CY,OAASzB,KAAKnB,QAAQiB,KAAK,6BAC3B0F,aAAejG,SAASsB,OAAQ,IAGhCb,KAAKsB,aAGL7C,QAAQyG,oBAAoB,0CAE5B7G,IAAI8G,YAAY,CACZ,CAACC,IAAK,iBAAkBC,UAAW,cACnC,CAACD,IAAK,yBAA0BC,UAAW,cAC3C,CAACD,IAAK,kBAAmBC,UAAW,cACpC,CAACD,IAAK,SAAUC,UAAW,UAC5BpE,MAAK,SAASqE,MACblH,aAAamH,QAAQD,KAAK,GAAIA,KAAK,GAAIA,KAAK,GAAIA,KAAK,IAAI,WACrDnH,EAAEoC,UAAUmC,QAAQ,eAAgB8C,qBAI5C/D,OAAOd,KAAK,gBAAiBE,QAGzBb,KAAKhB,eAAeyD,OAAS,IAAMgD,MAAMD,eAAiBA,aAAe,GACzErH,EAAEoC,UAAUmC,QAAQ,eAAgB8C,iBAYhD9G,kBAAkB6C,UAAUlB,qBAAuB,SAASyC,OACxDA,MAAM4C,qBACFjF,WAAatC,EAAE2E,MAAMG,QAAQ0C,QAAQ,gCACrCC,SAA+C,QAApCnF,WAAWE,KAAK,iBAC3BoC,YAAc5E,EAAEoC,SAASG,eAAeD,WAAWE,KAAK,mBAExDiF,UACA7C,YAAYI,OACZJ,YAAYpC,KAAK,cAAe,QAChCF,WAAWE,KAAK,gBAAiB,SACjCxC,EAAEoC,UAAU6C,OAAO,yCAEnBL,YAAY8C,IAAI,UAAW,gBAC3B9C,YAAYpC,KAAK,cAAe,SAChCF,WAAWE,KAAK,gBAAiB,QACjCmC,MAAMgD,kBACN3H,EAAEoC,UAAUR,GAAG,sCAAuCC,KAAK6C,mCAAmC3C,KAAKF,SAU3GtB,kBAAkB6C,UAAUjB,kBAAoB,eACxCnB,IAAM,IAAIC,IAAIC,OAAOC,UACzBH,IAAIK,aAAauG,IAAI,SAAU,KAC/B1G,OAAOC,SAAS0G,KAAO7G,KAU3BT,kBAAkB6C,UAAUtB,oBAAsB,SAASgG,GACvDA,EAAEP,qBAEEQ,cADSlG,KAAKnB,QAAQiB,KAAK,6BACJa,KAAK,iBAC5B+C,EAAI,EACJyC,aAAe,MAEdzC,EAAI,EAAGA,EAAI1D,KAAKhB,eAAeyD,OAAQiB,OACpC1D,KAAKhB,eAAe0E,GAAGsB,IAAMkB,cAAe,CAC5CC,aAAezC,YAKnB0C,MAAQpG,KAAKhB,eAAeyD,OAC5B4D,SAAYF,aAAe,EAC3BE,SAAW,IACXA,SAAWD,MAAQ,GAGnBA,YACKrF,gBAAgBf,KAAKhB,eAAeqH,UAAUrB,KAU3DtG,kBAAkB6C,UAAUpB,gBAAkB,SAAS8F,EAAGK,OACtDL,EAAEP,qBACEjE,OAASzB,KAAKnB,QAAQiB,KAAK,6BAC3BoG,cAAgBzE,OAAOd,KAAK,iBAC5B+C,EAAI,EACJyC,aAAe,MAEdzC,EAAI,EAAGA,EAAI1D,KAAKhB,eAAeyD,OAAQiB,OACpC1D,KAAKhB,eAAe0E,GAAGsB,IAAMkB,cAAe,CAC5CC,aAAezC,YAKnB0C,MAAQpG,KAAKhB,eAAeyD,OAC5B4D,UAAYF,aAAe,GAAKC,SAEhCE,OAASF,MAAO,KAEZvF,OAASb,KAAKhB,eAAeqH,UAAUrB,GACvCQ,aAAejG,SAASsB,OAAQ,IACpCY,OAAOd,KAAK,gBAAiBE,SACxB4E,MAAMD,eAAiBA,aAAe,GACvCrH,EAAEoC,UAAUmC,QAAQ,eAAgB7B,aAEjCuF,YACFrF,gBAAgBf,KAAKhB,eAAeqH,UAAUrB,KAa3DtG,kBAAkB6C,UAAUgF,gBAAkB,SAASC,EAAGC,OAClDC,kBACCzH,kBACLyH,aAAe1G,KAAKf,oBAEhB0H,MAAQ,CAACH,EAAGA,EAAGC,EAAGA,GACtBpI,IAAI2C,WAAW,OAAQ,aAAc2F,OAAO1F,KAAK,SAASC,GAClDwF,cAAgB1G,KAAKf,sBAChBJ,QAAQiB,KAAK,sCAAsC4E,KAAKxD,IAEnEhB,KAAKF,OAAOoB,KAAKhD,aAAaiD,YASpC3C,kBAAkB6C,UAAU0D,cAAgB,eAEpCpE,OADSb,KAAKnB,QAAQiB,KAAK,6BACXa,KAAK,iBACrB+C,EAAI,EACJyC,aAAe,KAEfV,MAAM5E,SAAWA,QAAU,OACtBhC,QAAQiB,KAAK,8BAA8BqD,WAC7C,UACEtE,QAAQiB,KAAK,8BAA8B8G,OAE3ClD,EAAI,EAAGA,EAAI1D,KAAKhB,eAAeyD,OAAQiB,OACpC1D,KAAKhB,eAAe0E,GAAGsB,IAAMnE,OAAQ,CACrCsF,aAAezC,YAInB0C,MAAQpG,KAAKhB,eAAeyD,UAC5B2D,QACAD,cAAgB,QAEfI,gBAAgBJ,aAAcC,OAE/BD,aAAe,EAAG,KACdhH,IAAM,IAAIC,IAAIC,OAAOC,aACrBC,SAASJ,IAAIK,aAAaC,IAAI,YAAc,EAAG,KAC3CoH,MAAQ7G,KAAKhB,eAAemH,aAAe,GAAGW,SAClD3H,IAAIK,aAAauG,IAAI,UAAWc,YAEhC1H,IAAIK,aAAauG,IAAI,SAAUlF,QAGnCxB,OAAOM,QAAQC,aAAa,GAAI,GAAIT,QAahDT,kBAAkB6C,UAAUf,iBAAmB,SAASsC,MAAOjC,YACvDY,OAASzB,KAAKnB,QAAQiB,KAAK,6BAC/Be,OAAStB,SAASsB,OAAQ,KAErB4E,MAAM5E,SAAWA,OAAS,GAC3BY,OAAOd,KAAK,gBAAiBE,aAE5BoE,iBASTvG,kBAAkB6C,UAAUqB,sBAAwB,WAC5C5C,KAAKhB,eAAeyD,OAAS,EAC7BtE,EAAEoC,UAAUmC,QAAQ,YAAa,CAACqE,WAAY,KAAMC,UAAU,IAE9D7I,EAAEoC,UAAUmC,QAAQ,YAAa,CAACsE,UAAU,KAUpDtI,kBAAkB6C,UAAUnB,kBAAoB,eACxCqB,OAASzB,KAAKnB,QAAQiB,KAAK,6BAC3Be,OAAStB,SAASkC,OAAOK,MAAO,IAEhC9B,KAAKsB,aAGL7C,QAAQyG,oBAAoB,0CAE5B7G,IAAI8G,YAAY,CACZ,CAACC,IAAK,iBAAkBC,UAAW,cACnC,CAACD,IAAK,yBAA0BC,UAAW,cAC3C,CAACD,IAAK,kBAAmBC,UAAW,cACpC,CAACD,IAAK,SAAUC,UAAW,UAC5BpE,MAAK,SAASqE,MACblH,aAAamH,QAAQD,KAAK,GAAIA,KAAK,GAAIA,KAAK,GAAIA,KAAK,IAAI,WACrDnH,EAAEoC,UAAUmC,QAAQ,eAAgB7B,eAIvC4E,MAAM5E,SAAWA,OAAS,IAC3BY,OAAOd,KAAK,gBAAiBE,QAE7B1C,EAAEoC,UAAUmC,QAAQ,eAAgB7B,WAKzCnC"} \ No newline at end of file +{"version":3,"file":"grading_navigation.min.js","sources":["../src/grading_navigation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript to handle changing users via the user selector in the header.\n *\n * @module mod_assign/grading_navigation\n * @copyright 2016 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.1\n */\ndefine(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete',\n 'core/ajax', 'core_user/repository', 'mod_assign/grading_form_change_checker'],\n function($, notification, str, autocomplete, ajax, UserRepository, checker) {\n\n /**\n * GradingNavigation class.\n *\n * @class mod_assign/grading_navigation\n * @param {String} selector The selector for the page region containing the user navigation.\n */\n var GradingNavigation = function(selector) {\n this._regionSelector = selector;\n this._region = $(selector);\n this._filters = [];\n this._users = [];\n this._filteredUsers = [];\n this._lastXofYUpdate = 0;\n this._firstLoadUsers = true;\n\n let url = new URL(window.location);\n if (parseInt(url.searchParams.get('treset')) > 0) {\n // Remove 'treset' url parameter to make sure that\n // table preferences won't be reset on page refresh.\n url.searchParams.delete('treset');\n window.history.replaceState({}, \"\", url);\n }\n\n // Get the current user list from a webservice.\n this._loadAllUsers();\n\n // We do not allow navigation while ajax requests are pending.\n // Attach listeners to the select and arrow buttons.\n\n this._region.find('[data-action=\"previous-user\"]').on('click', this._handlePreviousUser.bind(this));\n this._region.find('[data-action=\"next-user\"]').on('click', this._handleNextUser.bind(this));\n this._region.find('[data-action=\"change-user\"]').on('change', this._handleChangeUser.bind(this));\n this._region.find('[data-region=\"user-filters\"]').on('click', this._toggleExpandFilters.bind(this));\n this._region.find('[data-region=\"user-resettable\"]').on('click', this._toggleResetTable.bind());\n\n $(document).on('user-changed', this._refreshSelector.bind(this));\n $(document).on('done-saving-show-next', this._handleNextUser.bind(this));\n\n // Position the configure filters panel under the link that expands it.\n var toggleLink = this._region.find('[data-region=\"user-filters\"]');\n var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));\n\n configPanel.on('change', 'select', this._filterChanged.bind(this));\n\n var userid = $('[data-region=\"grading-navigation-panel\"]').data('first-userid');\n if (userid) {\n this._selectUserById(userid);\n }\n\n str.get_string('changeuser', 'mod_assign').done(function(s) {\n autocomplete.enhance('[data-action=change-user]', false, 'mod_assign/participant_selector', s);\n }\n ).fail(notification.exception);\n\n $(document).bind(\"start-loading-user\", function() {\n this._isLoading = true;\n }.bind(this));\n $(document).bind(\"finish-loading-user\", function() {\n this._isLoading = false;\n }.bind(this));\n };\n\n /** @property {Boolean} Boolean tracking active ajax requests. */\n GradingNavigation.prototype._isLoading = false;\n\n /** @property {String} Selector for the page region containing the user navigation. */\n GradingNavigation.prototype._regionSelector = null;\n\n /** @property {Array} The list of active filter keys */\n GradingNavigation.prototype._filters = null;\n\n /** @property {Array} The list of users */\n GradingNavigation.prototype._users = null;\n\n /** @property {JQuery} JQuery node for the page region containing the user navigation. */\n GradingNavigation.prototype._region = null;\n\n /** @property {String} Last active filters */\n GradingNavigation.prototype._lastFilters = '';\n\n /**\n * Load the list of all users for this assignment.\n *\n * @private\n * @method _loadAllUsers\n * @return {Boolean} True if the user list was fetched.\n */\n GradingNavigation.prototype._loadAllUsers = function() {\n var select = this._region.find('[data-action=change-user]');\n var assignmentid = select.attr('data-assignmentid');\n var groupid = select.attr('data-groupid');\n\n var filterPanel = this._region.find('[data-region=\"configure-filters\"]');\n var filter = filterPanel.find('select[name=\"filter\"]').val();\n var workflowFilter = filterPanel.find('select[name=\"workflowfilter\"]');\n if (workflowFilter) {\n filter += ',' + workflowFilter.val();\n }\n var markerFilter = filterPanel.find('select[name=\"markerfilter\"]');\n if (markerFilter) {\n filter += ',' + markerFilter.val();\n }\n\n if (this._lastFilters == filter) {\n return false;\n }\n this._lastFilters = filter;\n\n ajax.call([{\n methodname: 'mod_assign_list_participants',\n args: {assignid: assignmentid, groupid: groupid, filter: '', onlyids: true, tablesort: true},\n done: this._usersLoaded.bind(this),\n fail: notification.exception\n }]);\n return true;\n };\n\n /**\n * Call back to rebuild the user selector and x of y info when the user list is updated.\n *\n * @private\n * @method _usersLoaded\n * @param {Array} users\n */\n GradingNavigation.prototype._usersLoaded = function(users) {\n this._firstLoadUsers = false;\n this._filteredUsers = this._users = users;\n if (this._users.length) {\n // Position the configure filters panel under the link that expands it.\n var toggleLink = this._region.find('[data-region=\"user-filters\"]');\n var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));\n\n configPanel.find('select[name=\"filter\"]').trigger('change');\n } else {\n this._selectNoUser();\n }\n this._triggerNextUserEvent();\n };\n\n /**\n * Close the configure filters panel if a click is detected outside of it.\n *\n * @private\n * @method _checkClickOutsideConfigureFilters\n * @param {Event} event\n */\n GradingNavigation.prototype._checkClickOutsideConfigureFilters = function(event) {\n var configPanel = this._region.find('[data-region=\"configure-filters\"]');\n\n if (!configPanel.is(event.target) && configPanel.has(event.target).length === 0) {\n var toggleLink = this._region.find('[data-region=\"user-filters\"]');\n\n configPanel.hide();\n configPanel.attr('aria-hidden', 'true');\n toggleLink.attr('aria-expanded', 'false');\n $(document).unbind('click.mod_assign_grading_navigation');\n }\n };\n\n /**\n * Close the configure filters panel if a click is detected outside of it.\n *\n * @private\n * @method _updateFilterPreference\n * @param {Number} userId The current user id.\n * @param {Array} filterList The list of current filter values.\n * @param {Array} preferenceNames The names of the preferences to update\n * @return {Promise} Resolved when all the preferences are updated.\n */\n GradingNavigation.prototype._updateFilterPreferences = function(userId, filterList, preferenceNames) {\n var preferences = [],\n i = 0;\n\n if (filterList.length == 0 || this._firstLoadUsers) {\n // Nothing to update.\n var deferred = $.Deferred();\n deferred.resolve();\n return deferred;\n }\n // General filter.\n // Set the user preferences to the current filters.\n for (i = 0; i < filterList.length; i++) {\n var newValue = filterList[i];\n if (newValue == 'none') {\n newValue = '';\n }\n\n preferences.push({\n userid: userId,\n name: preferenceNames[i],\n value: newValue\n });\n }\n\n return UserRepository.setUserPreferences(preferences);\n };\n /**\n * Turn a filter on or off.\n *\n * @private\n * @method _filterChanged\n */\n GradingNavigation.prototype._filterChanged = function() {\n // There are 3 types of filter right now.\n var filterPanel = this._region.find('[data-region=\"configure-filters\"]');\n var filters = filterPanel.find('select');\n var preferenceNames = [];\n\n this._filters = [];\n filters.each(function(idx, ele) {\n var element = $(ele);\n this._filters.push(element.val());\n preferenceNames.push('assign_' + element.prop('name'));\n }.bind(this));\n\n // Update the active filter string.\n var filterlist = [];\n filterPanel.find('option:checked').each(function(idx, ele) {\n filterlist[filterlist.length] = $(ele).text();\n });\n if (filterlist.length) {\n this._region.find('[data-region=\"user-filters\"] span').text(filterlist.join(', '));\n } else {\n str.get_string('nofilters', 'mod_assign').done(function(s) {\n this._region.find('[data-region=\"user-filters\"] span').text(s);\n }.bind(this)).fail(notification.exception);\n }\n\n var select = this._region.find('[data-action=change-user]');\n var currentUserID = select.data('currentuserid');\n this._updateFilterPreferences(currentUserID, this._filters, preferenceNames).then(function() {\n // Reload the list of users to apply the new filters.\n if (!this._loadAllUsers()) {\n var userid = parseInt(select.attr('data-selected'));\n let foundIndex = null;\n // Search the returned users for the current selection.\n $.each(this._filteredUsers, function(index, user) {\n if (userid == user.id) {\n foundIndex = index;\n }\n });\n\n if (this._filteredUsers.length && foundIndex !== null) {\n this._selectUserById(this._filteredUsers[foundIndex].id);\n } else {\n this._selectNoUser();\n }\n\n }\n }.bind(this)).catch(notification.exception);\n this._refreshCount();\n };\n\n /**\n * Select no users, because no users match the filters.\n *\n * @private\n * @method _selectNoUser\n */\n GradingNavigation.prototype._selectNoUser = function() {\n // Detect unsaved changes, and offer to save them - otherwise change user right now.\n if (this._isLoading) {\n return;\n }\n if (checker.checkFormForChanges('[data-region=\"grade-panel\"] .gradeform')) {\n // Form has changes, so we need to confirm before switching users.\n str.get_strings([\n {key: 'unsavedchanges', component: 'mod_assign'},\n {key: 'unsavedchangesquestion', component: 'mod_assign'},\n {key: 'saveandcontinue', component: 'mod_assign'},\n {key: 'cancel', component: 'core'},\n ]).done(function(strs) {\n notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {\n $(document).trigger('save-changes', -1);\n });\n });\n } else {\n $(document).trigger('user-changed', -1);\n }\n };\n\n /**\n * Select the specified user by id.\n *\n * @private\n * @method _selectUserById\n * @param {Number} userid\n */\n GradingNavigation.prototype._selectUserById = function(userid) {\n var select = this._region.find('[data-action=change-user]');\n var useridnumber = parseInt(userid, 10);\n\n // Detect unsaved changes, and offer to save them - otherwise change user right now.\n if (this._isLoading) {\n return;\n }\n if (checker.checkFormForChanges('[data-region=\"grade-panel\"] .gradeform')) {\n // Form has changes, so we need to confirm before switching users.\n str.get_strings([\n {key: 'unsavedchanges', component: 'mod_assign'},\n {key: 'unsavedchangesquestion', component: 'mod_assign'},\n {key: 'saveandcontinue', component: 'mod_assign'},\n {key: 'cancel', component: 'core'},\n ]).done(function(strs) {\n notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {\n $(document).trigger('save-changes', useridnumber);\n });\n });\n } else {\n select.attr('data-selected', userid);\n\n // If we have some filtered users, and userid is specified, then trigger change.\n if (this._filteredUsers.length > 0 && !isNaN(useridnumber) && useridnumber > 0) {\n $(document).trigger('user-changed', useridnumber);\n }\n }\n };\n\n /**\n * Expand or collapse the filter config panel.\n *\n * @private\n * @method _toggleExpandFilters\n * @param {Event} event\n */\n GradingNavigation.prototype._toggleExpandFilters = function(event) {\n event.preventDefault();\n var toggleLink = $(event.target).closest('[data-region=\"user-filters\"]');\n var expanded = toggleLink.attr('aria-expanded') == 'true';\n var configPanel = $(document.getElementById(toggleLink.attr('aria-controls')));\n\n if (expanded) {\n configPanel.hide();\n configPanel.attr('aria-hidden', 'true');\n toggleLink.attr('aria-expanded', 'false');\n $(document).unbind('click.mod_assign_grading_navigation');\n } else {\n configPanel.css('display', 'inline-block');\n configPanel.attr('aria-hidden', 'false');\n toggleLink.attr('aria-expanded', 'true');\n event.stopPropagation();\n $(document).on('click.mod_assign_grading_navigation', this._checkClickOutsideConfigureFilters.bind(this));\n }\n };\n\n /**\n * Reset table preferences.\n *\n * @private\n * @method _toggleResetTable\n */\n GradingNavigation.prototype._toggleResetTable = function() {\n let url = new URL(window.location);\n url.searchParams.set('treset', '1');\n window.location.href = url;\n };\n\n /**\n * Change to the previous user in the grading list.\n *\n * @private\n * @method _handlePreviousUser\n * @param {Event} e\n */\n GradingNavigation.prototype._handlePreviousUser = function(e) {\n e.preventDefault();\n var select = this._region.find('[data-action=change-user]');\n var currentUserId = select.attr('data-selected');\n var i = 0;\n var currentIndex = 0;\n\n for (i = 0; i < this._filteredUsers.length; i++) {\n if (this._filteredUsers[i].id == currentUserId) {\n currentIndex = i;\n break;\n }\n }\n\n var count = this._filteredUsers.length;\n var newIndex = (currentIndex - 1);\n if (newIndex < 0) {\n newIndex = count - 1;\n }\n\n if (count) {\n this._selectUserById(this._filteredUsers[newIndex].id);\n }\n };\n\n /**\n * Change to the next user in the grading list.\n *\n * @param {Event} e\n * @param {Boolean} saved Has the form already been saved? Skips checking for changes if true.\n */\n GradingNavigation.prototype._handleNextUser = function(e, saved) {\n e.preventDefault();\n var select = this._region.find('[data-action=change-user]');\n var currentUserId = select.attr('data-selected');\n var i = 0;\n var currentIndex = 0;\n\n for (i = 0; i < this._filteredUsers.length; i++) {\n if (this._filteredUsers[i].id == currentUserId) {\n currentIndex = i;\n break;\n }\n }\n\n var count = this._filteredUsers.length;\n var newIndex = (currentIndex + 1) % count;\n\n if (saved && count) {\n // If we've already saved the grade, skip checking if we've made any changes.\n var userid = this._filteredUsers[newIndex].id;\n var useridnumber = parseInt(userid, 10);\n select.attr('data-selected', userid);\n if (!isNaN(useridnumber) && useridnumber > 0) {\n $(document).trigger('user-changed', userid);\n }\n } else if (count) {\n this._selectUserById(this._filteredUsers[newIndex].id);\n }\n };\n\n /**\n * Set count string. This method only sets the value for the last time it was ever called to deal\n * with promises that return in a non-predictable order.\n *\n * @private\n * @method _setCountString\n * @param {Number} x\n * @param {Number} y\n */\n GradingNavigation.prototype._setCountString = function(x, y) {\n var updateNumber = 0;\n this._lastXofYUpdate++;\n updateNumber = this._lastXofYUpdate;\n\n var param = {x: x, y: y};\n str.get_string('xofy', 'mod_assign', param).done(function(s) {\n if (updateNumber == this._lastXofYUpdate) {\n this._region.find('[data-region=\"user-count-summary\"]').text(s);\n }\n }.bind(this)).fail(notification.exception);\n };\n\n /**\n * Rebuild the x of y string.\n *\n * @private\n * @method _refreshCount\n */\n GradingNavigation.prototype._refreshCount = function() {\n var select = this._region.find('[data-action=change-user]');\n var userid = select.attr('data-selected');\n var i = 0;\n var currentIndex = 0;\n\n if (isNaN(userid) || userid <= 0) {\n this._region.find('[data-region=\"user-count\"]').hide();\n } else {\n this._region.find('[data-region=\"user-count\"]').show();\n\n for (i = 0; i < this._filteredUsers.length; i++) {\n if (this._filteredUsers[i].id == userid) {\n currentIndex = i;\n break;\n }\n }\n var count = this._filteredUsers.length;\n if (count) {\n currentIndex += 1;\n }\n this._setCountString(currentIndex, count);\n // Update window URL\n if (currentIndex > 0) {\n var url = new URL(window.location);\n if (parseInt(url.searchParams.get('blindid')) > 0) {\n var newid = this._filteredUsers[currentIndex - 1].recordid;\n url.searchParams.set('blindid', newid);\n } else {\n url.searchParams.set('userid', userid);\n }\n // We do this so a browser refresh will return to the same user.\n window.history.replaceState({}, \"\", url);\n }\n }\n };\n\n /**\n * Respond to a user-changed event by updating the selector.\n *\n * @private\n * @method _refreshSelector\n * @param {Event} event\n * @param {String} userid\n */\n GradingNavigation.prototype._refreshSelector = function(event, userid) {\n var select = this._region.find('[data-action=change-user]');\n userid = parseInt(userid, 10);\n\n if (!isNaN(userid) && userid > 0) {\n select.attr('data-selected', userid);\n }\n this._refreshCount();\n };\n\n /**\n * Trigger the next user event depending on the number of filtered users\n *\n * @private\n * @method _triggerNextUserEvent\n */\n GradingNavigation.prototype._triggerNextUserEvent = function() {\n if (this._filteredUsers.length > 1) {\n $(document).trigger('next-user', {nextUserId: null, nextUser: true});\n } else {\n $(document).trigger('next-user', {nextUser: false});\n }\n };\n\n /**\n * Change to a different user in the grading list.\n *\n * @private\n * @method _handleChangeUser\n */\n GradingNavigation.prototype._handleChangeUser = function() {\n var select = this._region.find('[data-action=change-user]');\n var userid = parseInt(select.val(), 10);\n\n if (this._isLoading) {\n return;\n }\n if (checker.checkFormForChanges('[data-region=\"grade-panel\"] .gradeform')) {\n // Form has changes, so we need to confirm before switching users.\n str.get_strings([\n {key: 'unsavedchanges', component: 'mod_assign'},\n {key: 'unsavedchangesquestion', component: 'mod_assign'},\n {key: 'saveandcontinue', component: 'mod_assign'},\n {key: 'cancel', component: 'core'},\n ]).done(function(strs) {\n notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {\n $(document).trigger('save-changes', userid);\n });\n });\n } else {\n if (!isNaN(userid) && userid > 0) {\n select.attr('data-selected', userid);\n\n $(document).trigger('user-changed', userid);\n }\n }\n };\n\n return GradingNavigation;\n});\n"],"names":["define","$","notification","str","autocomplete","ajax","UserRepository","checker","GradingNavigation","selector","_regionSelector","_region","_filters","_users","_filteredUsers","_lastXofYUpdate","_firstLoadUsers","url","URL","window","location","parseInt","searchParams","get","delete","history","replaceState","_loadAllUsers","find","on","this","_handlePreviousUser","bind","_handleNextUser","_handleChangeUser","_toggleExpandFilters","_toggleResetTable","document","_refreshSelector","toggleLink","getElementById","attr","_filterChanged","userid","data","_selectUserById","get_string","done","s","enhance","fail","exception","_isLoading","prototype","_lastFilters","select","assignmentid","groupid","filterPanel","filter","val","workflowFilter","markerFilter","call","methodname","args","assignid","onlyids","tablesort","_usersLoaded","users","length","trigger","_selectNoUser","_triggerNextUserEvent","_checkClickOutsideConfigureFilters","event","configPanel","is","target","has","hide","unbind","_updateFilterPreferences","userId","filterList","preferenceNames","preferences","i","deferred","Deferred","resolve","newValue","push","name","value","setUserPreferences","filters","each","idx","ele","element","prop","filterlist","text","join","currentUserID","then","foundIndex","index","user","id","catch","_refreshCount","checkFormForChanges","get_strings","key","component","strs","confirm","useridnumber","isNaN","preventDefault","closest","expanded","css","stopPropagation","set","href","e","currentUserId","currentIndex","count","newIndex","saved","_setCountString","x","y","updateNumber","param","show","newid","recordid","nextUserId","nextUser"],"mappings":";;;;;;;;AAuBAA,uCAAO,CAAC,SAAU,oBAAqB,WAAY,yBAC3C,YAAa,uBAAwB,2CACtC,SAASC,EAAGC,aAAcC,IAAKC,aAAcC,KAAMC,eAAgBC,aAQlEC,kBAAoB,SAASC,eACxBC,gBAAkBD,cAClBE,QAAUV,EAAEQ,eACZG,SAAW,QACXC,OAAS,QACTC,eAAiB,QACjBC,gBAAkB,OAClBC,iBAAkB,MAEnBC,IAAM,IAAIC,IAAIC,OAAOC,UACrBC,SAASJ,IAAIK,aAAaC,IAAI,WAAa,IAG3CN,IAAIK,aAAaE,OAAO,UACxBL,OAAOM,QAAQC,aAAa,GAAI,GAAIT,WAInCU,qBAKAhB,QAAQiB,KAAK,iCAAiCC,GAAG,QAASC,KAAKC,oBAAoBC,KAAKF,YACxFnB,QAAQiB,KAAK,6BAA6BC,GAAG,QAASC,KAAKG,gBAAgBD,KAAKF,YAChFnB,QAAQiB,KAAK,+BAA+BC,GAAG,SAAUC,KAAKI,kBAAkBF,KAAKF,YACrFnB,QAAQiB,KAAK,gCAAgCC,GAAG,QAASC,KAAKK,qBAAqBH,KAAKF,YACxFnB,QAAQiB,KAAK,mCAAmCC,GAAG,QAASC,KAAKM,kBAAkBJ,QAExF/B,EAAEoC,UAAUR,GAAG,eAAgBC,KAAKQ,iBAAiBN,KAAKF,OAC1D7B,EAAEoC,UAAUR,GAAG,wBAAyBC,KAAKG,gBAAgBD,KAAKF,WAG9DS,WAAaT,KAAKnB,QAAQiB,KAAK,gCACjB3B,EAAEoC,SAASG,eAAeD,WAAWE,KAAK,mBAEhDZ,GAAG,SAAU,SAAUC,KAAKY,eAAeV,KAAKF,WAExDa,OAAS1C,EAAE,4CAA4C2C,KAAK,gBAC5DD,aACKE,gBAAgBF,QAGzBxC,IAAI2C,WAAW,aAAc,cAAcC,MAAK,SAASC,GACjD5C,aAAa6C,QAAQ,6BAA6B,EAAO,kCAAmCD,MAElGE,KAAKhD,aAAaiD,WAEpBlD,EAAEoC,UAAUL,KAAK,qBAAsB,gBAC9BoB,YAAa,GACpBpB,KAAKF,OACP7B,EAAEoC,UAAUL,KAAK,sBAAuB,gBAC/BoB,YAAa,GACpBpB,KAAKF,eAIXtB,kBAAkB6C,UAAUD,YAAa,EAGzC5C,kBAAkB6C,UAAU3C,gBAAkB,KAG9CF,kBAAkB6C,UAAUzC,SAAW,KAGvCJ,kBAAkB6C,UAAUxC,OAAS,KAGrCL,kBAAkB6C,UAAU1C,QAAU,KAGtCH,kBAAkB6C,UAAUC,aAAe,GAS3C9C,kBAAkB6C,UAAU1B,cAAgB,eACpC4B,OAASzB,KAAKnB,QAAQiB,KAAK,6BAC3B4B,aAAeD,OAAOd,KAAK,qBAC3BgB,QAAUF,OAAOd,KAAK,gBAEtBiB,YAAc5B,KAAKnB,QAAQiB,KAAK,qCAChC+B,OAASD,YAAY9B,KAAK,yBAAyBgC,MACnDC,eAAiBH,YAAY9B,KAAK,iCAClCiC,iBACAF,QAAU,IAAME,eAAeD,WAE/BE,aAAeJ,YAAY9B,KAAK,sCAChCkC,eACAH,QAAU,IAAMG,aAAaF,OAG7B9B,KAAKwB,cAAgBK,cAGpBL,aAAeK,OAEpBtD,KAAK0D,KAAK,CAAC,CACPC,WAAY,+BACZC,KAAM,CAACC,SAAUV,aAAcC,QAASA,QAASE,OAAQ,GAAIQ,SAAS,EAAMC,WAAW,GACvFrB,KAAMjB,KAAKuC,aAAarC,KAAKF,MAC7BoB,KAAMhD,aAAaiD,cAEhB,IAUX3C,kBAAkB6C,UAAUgB,aAAe,SAASC,eAC3CtD,iBAAkB,OAClBF,eAAiBgB,KAAKjB,OAASyD,MAChCxC,KAAKjB,OAAO0D,OAAQ,KAEhBhC,WAAaT,KAAKnB,QAAQiB,KAAK,gCACjB3B,EAAEoC,SAASG,eAAeD,WAAWE,KAAK,mBAEhDb,KAAK,yBAAyB4C,QAAQ,oBAE7CC,qBAEJC,yBAUTlE,kBAAkB6C,UAAUsB,mCAAqC,SAASC,WAClEC,YAAc/C,KAAKnB,QAAQiB,KAAK,yCAE/BiD,YAAYC,GAAGF,MAAMG,SAAoD,IAAzCF,YAAYG,IAAIJ,MAAMG,QAAQR,OAAc,KACzEhC,WAAaT,KAAKnB,QAAQiB,KAAK,gCAEnCiD,YAAYI,OACZJ,YAAYpC,KAAK,cAAe,QAChCF,WAAWE,KAAK,gBAAiB,SACjCxC,EAAEoC,UAAU6C,OAAO,yCAc3B1E,kBAAkB6C,UAAU8B,yBAA2B,SAASC,OAAQC,WAAYC,qBAC5EC,YAAc,GACdC,EAAI,KAEiB,GAArBH,WAAWd,QAAezC,KAAKd,gBAAiB,KAE5CyE,SAAWxF,EAAEyF,kBACjBD,SAASE,UACFF,aAIND,EAAI,EAAGA,EAAIH,WAAWd,OAAQiB,IAAK,KAChCI,SAAWP,WAAWG,GACV,QAAZI,WACAA,SAAW,IAGfL,YAAYM,KAAK,CACblD,OAAQyC,OACRU,KAAMR,gBAAgBE,GACtBO,MAAOH,kBAIRtF,eAAe0F,mBAAmBT,cAQ7C/E,kBAAkB6C,UAAUX,eAAiB,eAErCgB,YAAc5B,KAAKnB,QAAQiB,KAAK,qCAChCqE,QAAUvC,YAAY9B,KAAK,UAC3B0D,gBAAkB,QAEjB1E,SAAW,GAChBqF,QAAQC,KAAK,SAASC,IAAKC,SACnBC,QAAUpG,EAAEmG,UACXxF,SAASiF,KAAKQ,QAAQzC,OAC3B0B,gBAAgBO,KAAK,UAAYQ,QAAQC,KAAK,UAChDtE,KAAKF,WAGHyE,WAAa,GACjB7C,YAAY9B,KAAK,kBAAkBsE,MAAK,SAASC,IAAKC,KAClDG,WAAWA,WAAWhC,QAAUtE,EAAEmG,KAAKI,UAEvCD,WAAWhC,YACN5D,QAAQiB,KAAK,qCAAqC4E,KAAKD,WAAWE,KAAK,OAE5EtG,IAAI2C,WAAW,YAAa,cAAcC,KAAK,SAASC,QAC/CrC,QAAQiB,KAAK,qCAAqC4E,KAAKxD,IAC9DhB,KAAKF,OAAOoB,KAAKhD,aAAaiD,eAGhCI,OAASzB,KAAKnB,QAAQiB,KAAK,6BAC3B8E,cAAgBnD,OAAOX,KAAK,sBAC3BuC,yBAAyBuB,cAAe5E,KAAKlB,SAAU0E,iBAAiBqB,KAAK,eAEzE7E,KAAKH,gBAAiB,KACnBgB,OAAStB,SAASkC,OAAOd,KAAK,sBAC9BmE,WAAa,KAEjB3G,EAAEiG,KAAKpE,KAAKhB,gBAAgB,SAAS+F,MAAOC,MACpCnE,QAAUmE,KAAKC,KACfH,WAAaC,UAIjB/E,KAAKhB,eAAeyD,QAAyB,OAAfqC,gBACzB/D,gBAAgBf,KAAKhB,eAAe8F,YAAYG,SAEhDtC,kBAIfzC,KAAKF,OAAOkF,MAAM9G,aAAaiD,gBAC5B8D,iBASTzG,kBAAkB6C,UAAUoB,cAAgB,WAEpC3C,KAAKsB,aAGL7C,QAAQ2G,oBAAoB,0CAE5B/G,IAAIgH,YAAY,CACZ,CAACC,IAAK,iBAAkBC,UAAW,cACnC,CAACD,IAAK,yBAA0BC,UAAW,cAC3C,CAACD,IAAK,kBAAmBC,UAAW,cACpC,CAACD,IAAK,SAAUC,UAAW,UAC5BtE,MAAK,SAASuE,MACbpH,aAAaqH,QAAQD,KAAK,GAAIA,KAAK,GAAIA,KAAK,GAAIA,KAAK,IAAI,WACrDrH,EAAEoC,UAAUmC,QAAQ,gBAAiB,SAI7CvE,EAAEoC,UAAUmC,QAAQ,gBAAiB,KAW7ChE,kBAAkB6C,UAAUR,gBAAkB,SAASF,YAC/CY,OAASzB,KAAKnB,QAAQiB,KAAK,6BAC3B4F,aAAenG,SAASsB,OAAQ,IAGhCb,KAAKsB,aAGL7C,QAAQ2G,oBAAoB,0CAE5B/G,IAAIgH,YAAY,CACZ,CAACC,IAAK,iBAAkBC,UAAW,cACnC,CAACD,IAAK,yBAA0BC,UAAW,cAC3C,CAACD,IAAK,kBAAmBC,UAAW,cACpC,CAACD,IAAK,SAAUC,UAAW,UAC5BtE,MAAK,SAASuE,MACbpH,aAAaqH,QAAQD,KAAK,GAAIA,KAAK,GAAIA,KAAK,GAAIA,KAAK,IAAI,WACrDrH,EAAEoC,UAAUmC,QAAQ,eAAgBgD,qBAI5CjE,OAAOd,KAAK,gBAAiBE,QAGzBb,KAAKhB,eAAeyD,OAAS,IAAMkD,MAAMD,eAAiBA,aAAe,GACzEvH,EAAEoC,UAAUmC,QAAQ,eAAgBgD,iBAYhDhH,kBAAkB6C,UAAUlB,qBAAuB,SAASyC,OACxDA,MAAM8C,qBACFnF,WAAatC,EAAE2E,MAAMG,QAAQ4C,QAAQ,gCACrCC,SAA+C,QAApCrF,WAAWE,KAAK,iBAC3BoC,YAAc5E,EAAEoC,SAASG,eAAeD,WAAWE,KAAK,mBAExDmF,UACA/C,YAAYI,OACZJ,YAAYpC,KAAK,cAAe,QAChCF,WAAWE,KAAK,gBAAiB,SACjCxC,EAAEoC,UAAU6C,OAAO,yCAEnBL,YAAYgD,IAAI,UAAW,gBAC3BhD,YAAYpC,KAAK,cAAe,SAChCF,WAAWE,KAAK,gBAAiB,QACjCmC,MAAMkD,kBACN7H,EAAEoC,UAAUR,GAAG,sCAAuCC,KAAK6C,mCAAmC3C,KAAKF,SAU3GtB,kBAAkB6C,UAAUjB,kBAAoB,eACxCnB,IAAM,IAAIC,IAAIC,OAAOC,UACzBH,IAAIK,aAAayG,IAAI,SAAU,KAC/B5G,OAAOC,SAAS4G,KAAO/G,KAU3BT,kBAAkB6C,UAAUtB,oBAAsB,SAASkG,GACvDA,EAAEP,qBAEEQ,cADSpG,KAAKnB,QAAQiB,KAAK,6BACJa,KAAK,iBAC5B+C,EAAI,EACJ2C,aAAe,MAEd3C,EAAI,EAAGA,EAAI1D,KAAKhB,eAAeyD,OAAQiB,OACpC1D,KAAKhB,eAAe0E,GAAGuB,IAAMmB,cAAe,CAC5CC,aAAe3C,YAKnB4C,MAAQtG,KAAKhB,eAAeyD,OAC5B8D,SAAYF,aAAe,EAC3BE,SAAW,IACXA,SAAWD,MAAQ,GAGnBA,YACKvF,gBAAgBf,KAAKhB,eAAeuH,UAAUtB,KAU3DvG,kBAAkB6C,UAAUpB,gBAAkB,SAASgG,EAAGK,OACtDL,EAAEP,qBACEnE,OAASzB,KAAKnB,QAAQiB,KAAK,6BAC3BsG,cAAgB3E,OAAOd,KAAK,iBAC5B+C,EAAI,EACJ2C,aAAe,MAEd3C,EAAI,EAAGA,EAAI1D,KAAKhB,eAAeyD,OAAQiB,OACpC1D,KAAKhB,eAAe0E,GAAGuB,IAAMmB,cAAe,CAC5CC,aAAe3C,YAKnB4C,MAAQtG,KAAKhB,eAAeyD,OAC5B8D,UAAYF,aAAe,GAAKC,SAEhCE,OAASF,MAAO,KAEZzF,OAASb,KAAKhB,eAAeuH,UAAUtB,GACvCS,aAAenG,SAASsB,OAAQ,IACpCY,OAAOd,KAAK,gBAAiBE,SACxB8E,MAAMD,eAAiBA,aAAe,GACvCvH,EAAEoC,UAAUmC,QAAQ,eAAgB7B,aAEjCyF,YACFvF,gBAAgBf,KAAKhB,eAAeuH,UAAUtB,KAa3DvG,kBAAkB6C,UAAUkF,gBAAkB,SAASC,EAAGC,OAClDC,kBACC3H,kBACL2H,aAAe5G,KAAKf,oBAEhB4H,MAAQ,CAACH,EAAGA,EAAGC,EAAGA,GACtBtI,IAAI2C,WAAW,OAAQ,aAAc6F,OAAO5F,KAAK,SAASC,GAClD0F,cAAgB5G,KAAKf,sBAChBJ,QAAQiB,KAAK,sCAAsC4E,KAAKxD,IAEnEhB,KAAKF,OAAOoB,KAAKhD,aAAaiD,YASpC3C,kBAAkB6C,UAAU4D,cAAgB,eAEpCtE,OADSb,KAAKnB,QAAQiB,KAAK,6BACXa,KAAK,iBACrB+C,EAAI,EACJ2C,aAAe,KAEfV,MAAM9E,SAAWA,QAAU,OACtBhC,QAAQiB,KAAK,8BAA8BqD,WAC7C,UACEtE,QAAQiB,KAAK,8BAA8BgH,OAE3CpD,EAAI,EAAGA,EAAI1D,KAAKhB,eAAeyD,OAAQiB,OACpC1D,KAAKhB,eAAe0E,GAAGuB,IAAMpE,OAAQ,CACrCwF,aAAe3C,YAInB4C,MAAQtG,KAAKhB,eAAeyD,UAC5B6D,QACAD,cAAgB,QAEfI,gBAAgBJ,aAAcC,OAE/BD,aAAe,EAAG,KACdlH,IAAM,IAAIC,IAAIC,OAAOC,aACrBC,SAASJ,IAAIK,aAAaC,IAAI,YAAc,EAAG,KAC3CsH,MAAQ/G,KAAKhB,eAAeqH,aAAe,GAAGW,SAClD7H,IAAIK,aAAayG,IAAI,UAAWc,YAEhC5H,IAAIK,aAAayG,IAAI,SAAUpF,QAGnCxB,OAAOM,QAAQC,aAAa,GAAI,GAAIT,QAahDT,kBAAkB6C,UAAUf,iBAAmB,SAASsC,MAAOjC,YACvDY,OAASzB,KAAKnB,QAAQiB,KAAK,6BAC/Be,OAAStB,SAASsB,OAAQ,KAErB8E,MAAM9E,SAAWA,OAAS,GAC3BY,OAAOd,KAAK,gBAAiBE,aAE5BsE,iBASTzG,kBAAkB6C,UAAUqB,sBAAwB,WAC5C5C,KAAKhB,eAAeyD,OAAS,EAC7BtE,EAAEoC,UAAUmC,QAAQ,YAAa,CAACuE,WAAY,KAAMC,UAAU,IAE9D/I,EAAEoC,UAAUmC,QAAQ,YAAa,CAACwE,UAAU,KAUpDxI,kBAAkB6C,UAAUnB,kBAAoB,eACxCqB,OAASzB,KAAKnB,QAAQiB,KAAK,6BAC3Be,OAAStB,SAASkC,OAAOK,MAAO,IAEhC9B,KAAKsB,aAGL7C,QAAQ2G,oBAAoB,0CAE5B/G,IAAIgH,YAAY,CACZ,CAACC,IAAK,iBAAkBC,UAAW,cACnC,CAACD,IAAK,yBAA0BC,UAAW,cAC3C,CAACD,IAAK,kBAAmBC,UAAW,cACpC,CAACD,IAAK,SAAUC,UAAW,UAC5BtE,MAAK,SAASuE,MACbpH,aAAaqH,QAAQD,KAAK,GAAIA,KAAK,GAAIA,KAAK,GAAIA,KAAK,IAAI,WACrDrH,EAAEoC,UAAUmC,QAAQ,eAAgB7B,eAIvC8E,MAAM9E,SAAWA,OAAS,IAC3BY,OAAOd,KAAK,gBAAiBE,QAE7B1C,EAAEoC,UAAUmC,QAAQ,eAAgB7B,WAKzCnC"} \ No newline at end of file diff --git a/mod/assign/amd/src/grading_navigation.js b/mod/assign/amd/src/grading_navigation.js index 6dadc557e99..2c79a91d2a5 100644 --- a/mod/assign/amd/src/grading_navigation.js +++ b/mod/assign/amd/src/grading_navigation.js @@ -255,7 +255,7 @@ define(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete', var select = this._region.find('[data-action=change-user]'); var currentUserID = select.data('currentuserid'); - this._updateFilterPreferences(currentUserID, this._filters, preferenceNames).done(function() { + this._updateFilterPreferences(currentUserID, this._filters, preferenceNames).then(function() { // Reload the list of users to apply the new filters. if (!this._loadAllUsers()) { var userid = parseInt(select.attr('data-selected')); @@ -274,7 +274,7 @@ define(['jquery', 'core/notification', 'core/str', 'core/form-autocomplete', } } - }.bind(this)).fail(notification.exception); + }.bind(this)).catch(notification.exception); this._refreshCount(); };