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