stanley-king 3 anni fa
parent
commit
e1c2e01677
100 ha cambiato i file con 27729 aggiunte e 0 eliminazioni
  1. 7 0
      vendor/autoload.php
  2. 445 0
      vendor/composer/ClassLoader.php
  3. 21 0
      vendor/composer/LICENSE
  4. 9 0
      vendor/composer/autoload_classmap.php
  5. 11 0
      vendor/composer/autoload_files.php
  6. 10 0
      vendor/composer/autoload_namespaces.php
  7. 18 0
      vendor/composer/autoload_psr4.php
  8. 73 0
      vendor/composer/autoload_real.php
  9. 100 0
      vendor/composer/autoload_static.php
  10. 672 0
      vendor/composer/installed.json
  11. 1 0
      vendor/ezyang/htmlpurifier
  12. 1 0
      vendor/maennchen/zipstream-php
  13. 1 0
      vendor/markbaker/complex
  14. 1 0
      vendor/markbaker/matrix
  15. 1 0
      vendor/myclabs/php-enum
  16. 226 0
      vendor/phpoffice/phpspreadsheet/.php-cs-fixer.dist.php
  17. 22 0
      vendor/phpoffice/phpspreadsheet/.phpcs.xml.dist
  18. 958 0
      vendor/phpoffice/phpspreadsheet/CHANGELOG.md
  19. 20 0
      vendor/phpoffice/phpspreadsheet/CONTRIBUTING.md
  20. 21 0
      vendor/phpoffice/phpspreadsheet/LICENSE
  21. 30 0
      vendor/phpoffice/phpspreadsheet/README.md
  22. 110 0
      vendor/phpoffice/phpspreadsheet/composer.json
  23. 6182 0
      vendor/phpoffice/phpspreadsheet/phpstan-baseline.neon
  24. 51 0
      vendor/phpoffice/phpspreadsheet/phpstan-conditional.php
  25. 26 0
      vendor/phpoffice/phpspreadsheet/phpstan.neon.dist
  26. 56 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ArrayEnabled.php
  27. 181 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/BinaryComparison.php
  28. 5453 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php
  29. 20 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Category.php
  30. 440 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database.php
  31. 45 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DAverage.php
  32. 43 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCount.php
  33. 42 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCountA.php
  34. 51 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DGet.php
  35. 46 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMax.php
  36. 46 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMin.php
  37. 45 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DProduct.php
  38. 46 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDev.php
  39. 46 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDevP.php
  40. 45 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DSum.php
  41. 46 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVar.php
  42. 46 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVarP.php
  43. 174 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php
  44. 915 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTime.php
  45. 38 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php
  46. 59 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php
  47. 172 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php
  48. 151 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php
  49. 157 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php
  50. 62 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php
  51. 118 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php
  52. 158 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php
  53. 306 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php
  54. 101 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php
  55. 119 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php
  56. 129 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php
  57. 132 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php
  58. 77 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php
  59. 278 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php
  60. 201 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php
  61. 132 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php
  62. 202 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php
  63. 174 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php
  64. 73 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php
  65. 140 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php
  66. 1446 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php
  67. 145 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselI.php
  68. 180 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php
  69. 119 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselK.php
  70. 126 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselY.php
  71. 273 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php
  72. 82 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Compare.php
  73. 121 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php
  74. 611 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php
  75. 133 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php
  76. 11 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Constants.php
  77. 68 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php
  78. 163 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php
  79. 213 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php
  80. 175 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php
  81. 174 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php
  82. 693 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php
  83. 33 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php
  84. 104 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Erf.php
  85. 76 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php
  86. 26 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php
  87. 22 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php
  88. 1430 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial.php
  89. 210 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Amortization.php
  90. 53 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php
  91. 199 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic.php
  92. 141 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php
  93. 216 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php
  94. 44 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php
  95. 115 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php
  96. 108 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php
  97. 258 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php
  98. 160 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php
  99. 19 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Constants.php
  100. 0 0
      vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Coupons.php

+ 7 - 0
vendor/autoload.php

@@ -0,0 +1,7 @@
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInit6f94f903accf18c0690247a05621fdb2::getLoader();

+ 445 - 0
vendor/composer/ClassLoader.php

@@ -0,0 +1,445 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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 <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see    http://www.php-fig.org/psr/psr-0/
+ * @see    http://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+    // PSR-4
+    private $prefixLengthsPsr4 = array();
+    private $prefixDirsPsr4 = array();
+    private $fallbackDirsPsr4 = array();
+
+    // PSR-0
+    private $prefixesPsr0 = array();
+    private $fallbackDirsPsr0 = array();
+
+    private $useIncludePath = false;
+    private $classMap = array();
+    private $classMapAuthoritative = false;
+    private $missingClasses = array();
+    private $apcuPrefix;
+
+    public function getPrefixes()
+    {
+        if (!empty($this->prefixesPsr0)) {
+            return call_user_func_array('array_merge', $this->prefixesPsr0);
+        }
+
+        return array();
+    }
+
+    public function getPrefixesPsr4()
+    {
+        return $this->prefixDirsPsr4;
+    }
+
+    public function getFallbackDirs()
+    {
+        return $this->fallbackDirsPsr0;
+    }
+
+    public function getFallbackDirsPsr4()
+    {
+        return $this->fallbackDirsPsr4;
+    }
+
+    public function getClassMap()
+    {
+        return $this->classMap;
+    }
+
+    /**
+     * @param array $classMap Class to filename map
+     */
+    public function addClassMap(array $classMap)
+    {
+        if ($this->classMap) {
+            $this->classMap = array_merge($this->classMap, $classMap);
+        } else {
+            $this->classMap = $classMap;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix, either
+     * appending or prepending to the ones previously set for this prefix.
+     *
+     * @param string       $prefix  The prefix
+     * @param array|string $paths   The PSR-0 root directories
+     * @param bool         $prepend Whether to prepend the directories
+     */
+    public function add($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            if ($prepend) {
+                $this->fallbackDirsPsr0 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr0
+                );
+            } else {
+                $this->fallbackDirsPsr0 = array_merge(
+                    $this->fallbackDirsPsr0,
+                    (array) $paths
+                );
+            }
+
+            return;
+        }
+
+        $first = $prefix[0];
+        if (!isset($this->prefixesPsr0[$first][$prefix])) {
+            $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+            return;
+        }
+        if ($prepend) {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixesPsr0[$first][$prefix]
+            );
+        } else {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                $this->prefixesPsr0[$first][$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace, either
+     * appending or prepending to the ones previously set for this namespace.
+     *
+     * @param string       $prefix  The prefix/namespace, with trailing '\\'
+     * @param array|string $paths   The PSR-4 base directories
+     * @param bool         $prepend Whether to prepend the directories
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function addPsr4($prefix, $paths, $prepend = false)
+    {
+        if (!$prefix) {
+            // Register directories for the root namespace.
+            if ($prepend) {
+                $this->fallbackDirsPsr4 = array_merge(
+                    (array) $paths,
+                    $this->fallbackDirsPsr4
+                );
+            } else {
+                $this->fallbackDirsPsr4 = array_merge(
+                    $this->fallbackDirsPsr4,
+                    (array) $paths
+                );
+            }
+        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+            // Register directories for a new namespace.
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        } elseif ($prepend) {
+            // Prepend directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                (array) $paths,
+                $this->prefixDirsPsr4[$prefix]
+            );
+        } else {
+            // Append directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                $this->prefixDirsPsr4[$prefix],
+                (array) $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix,
+     * replacing any others previously set for this prefix.
+     *
+     * @param string       $prefix The prefix
+     * @param array|string $paths  The PSR-0 base directories
+     */
+    public function set($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr0 = (array) $paths;
+        } else {
+            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace,
+     * replacing any others previously set for this namespace.
+     *
+     * @param string       $prefix The prefix/namespace, with trailing '\\'
+     * @param array|string $paths  The PSR-4 base directories
+     *
+     * @throws \InvalidArgumentException
+     */
+    public function setPsr4($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr4 = (array) $paths;
+        } else {
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Turns on searching the include path for class files.
+     *
+     * @param bool $useIncludePath
+     */
+    public function setUseIncludePath($useIncludePath)
+    {
+        $this->useIncludePath = $useIncludePath;
+    }
+
+    /**
+     * Can be used to check if the autoloader uses the include path to check
+     * for classes.
+     *
+     * @return bool
+     */
+    public function getUseIncludePath()
+    {
+        return $this->useIncludePath;
+    }
+
+    /**
+     * Turns off searching the prefix and fallback directories for classes
+     * that have not been registered with the class map.
+     *
+     * @param bool $classMapAuthoritative
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative)
+    {
+        $this->classMapAuthoritative = $classMapAuthoritative;
+    }
+
+    /**
+     * Should class lookup fail if not found in the current class map?
+     *
+     * @return bool
+     */
+    public function isClassMapAuthoritative()
+    {
+        return $this->classMapAuthoritative;
+    }
+
+    /**
+     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+     *
+     * @param string|null $apcuPrefix
+     */
+    public function setApcuPrefix($apcuPrefix)
+    {
+        $this->apcuPrefix = function_exists('apcu_fetch') && 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
+     */
+    public function register($prepend = false)
+    {
+        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+    }
+
+    /**
+     * Unregisters this instance as an autoloader.
+     */
+    public function unregister()
+    {
+        spl_autoload_unregister(array($this, 'loadClass'));
+    }
+
+    /**
+     * Loads the given class or interface.
+     *
+     * @param  string    $class The name of the class
+     * @return bool|null True if loaded, null otherwise
+     */
+    public function loadClass($class)
+    {
+        if ($file = $this->findFile($class)) {
+            includeFile($file);
+
+            return true;
+        }
+    }
+
+    /**
+     * Finds the path to the file where the class is defined.
+     *
+     * @param string $class The name of the class
+     *
+     * @return string|false The path if found, false otherwise
+     */
+    public function findFile($class)
+    {
+        // class map lookup
+        if (isset($this->classMap[$class])) {
+            return $this->classMap[$class];
+        }
+        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+            return false;
+        }
+        if (null !== $this->apcuPrefix) {
+            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+            if ($hit) {
+                return $file;
+            }
+        }
+
+        $file = $this->findFileWithExtension($class, '.php');
+
+        // Search for Hack files if we are running on HHVM
+        if (false === $file && defined('HHVM_VERSION')) {
+            $file = $this->findFileWithExtension($class, '.hh');
+        }
+
+        if (null !== $this->apcuPrefix) {
+            apcu_add($this->apcuPrefix.$class, $file);
+        }
+
+        if (false === $file) {
+            // Remember that this class does not exist.
+            $this->missingClasses[$class] = true;
+        }
+
+        return $file;
+    }
+
+    private function findFileWithExtension($class, $ext)
+    {
+        // PSR-4 lookup
+        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+        $first = $class[0];
+        if (isset($this->prefixLengthsPsr4[$first])) {
+            $subPath = $class;
+            while (false !== $lastPos = strrpos($subPath, '\\')) {
+                $subPath = substr($subPath, 0, $lastPos);
+                $search = $subPath . '\\';
+                if (isset($this->prefixDirsPsr4[$search])) {
+                    $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;
+    }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+    include $file;
+}

+ 21 - 0
vendor/composer/LICENSE

@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+

+ 9 - 0
vendor/composer/autoload_classmap.php

@@ -0,0 +1,9 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+);

+ 11 - 0
vendor/composer/autoload_files.php

@@ -0,0 +1,11 @@
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
+    '2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
+);

+ 10 - 0
vendor/composer/autoload_namespaces.php

@@ -0,0 +1,10 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'HTMLPurifier' => array($vendorDir . '/ezyang/htmlpurifier/library'),
+);

+ 18 - 0
vendor/composer/autoload_psr4.php

@@ -0,0 +1,18 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+    'ZipStream\\' => array($vendorDir . '/maennchen/zipstream-php/src'),
+    'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
+    'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
+    'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
+    'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
+    'PhpOffice\\PhpSpreadsheet\\' => array($vendorDir . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet'),
+    'MyCLabs\\Enum\\' => array($vendorDir . '/myclabs/php-enum/src'),
+    'Matrix\\' => array($vendorDir . '/markbaker/matrix/classes/src'),
+    'Complex\\' => array($vendorDir . '/markbaker/complex/classes/src'),
+);

+ 73 - 0
vendor/composer/autoload_real.php

@@ -0,0 +1,73 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInit6f94f903accf18c0690247a05621fdb2
+{
+    private static $loader;
+
+    public static function loadClassLoader($class)
+    {
+        if ('Composer\Autoload\ClassLoader' === $class) {
+            require __DIR__ . '/ClassLoader.php';
+        }
+    }
+
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
+    public static function getLoader()
+    {
+        if (null !== self::$loader) {
+            return self::$loader;
+        }
+
+        spl_autoload_register(array('ComposerAutoloaderInit6f94f903accf18c0690247a05621fdb2', 'loadClassLoader'), true, true);
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
+        spl_autoload_unregister(array('ComposerAutoloaderInit6f94f903accf18c0690247a05621fdb2', 'loadClassLoader'));
+
+        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
+        if ($useStaticLoader) {
+            require_once __DIR__ . '/autoload_static.php';
+
+            call_user_func(\Composer\Autoload\ComposerStaticInit6f94f903accf18c0690247a05621fdb2::getInitializer($loader));
+        } else {
+            $map = require __DIR__ . '/autoload_namespaces.php';
+            foreach ($map as $namespace => $path) {
+                $loader->set($namespace, $path);
+            }
+
+            $map = require __DIR__ . '/autoload_psr4.php';
+            foreach ($map as $namespace => $path) {
+                $loader->setPsr4($namespace, $path);
+            }
+
+            $classMap = require __DIR__ . '/autoload_classmap.php';
+            if ($classMap) {
+                $loader->addClassMap($classMap);
+            }
+        }
+
+        $loader->register(true);
+
+        if ($useStaticLoader) {
+            $includeFiles = Composer\Autoload\ComposerStaticInit6f94f903accf18c0690247a05621fdb2::$files;
+        } else {
+            $includeFiles = require __DIR__ . '/autoload_files.php';
+        }
+        foreach ($includeFiles as $fileIdentifier => $file) {
+            composerRequire6f94f903accf18c0690247a05621fdb2($fileIdentifier, $file);
+        }
+
+        return $loader;
+    }
+}
+
+function composerRequire6f94f903accf18c0690247a05621fdb2($fileIdentifier, $file)
+{
+    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+        require $file;
+
+        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+    }
+}

+ 100 - 0
vendor/composer/autoload_static.php

@@ -0,0 +1,100 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInit6f94f903accf18c0690247a05621fdb2
+{
+    public static $files = array (
+        '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
+        '2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
+    );
+
+    public static $prefixLengthsPsr4 = array (
+        'Z' => 
+        array (
+            'ZipStream\\' => 10,
+        ),
+        'S' => 
+        array (
+            'Symfony\\Polyfill\\Mbstring\\' => 26,
+        ),
+        'P' => 
+        array (
+            'Psr\\SimpleCache\\' => 16,
+            'Psr\\Http\\Message\\' => 17,
+            'Psr\\Http\\Client\\' => 16,
+            'PhpOffice\\PhpSpreadsheet\\' => 25,
+        ),
+        'M' => 
+        array (
+            'MyCLabs\\Enum\\' => 13,
+            'Matrix\\' => 7,
+        ),
+        'C' => 
+        array (
+            'Complex\\' => 8,
+        ),
+    );
+
+    public static $prefixDirsPsr4 = array (
+        'ZipStream\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/maennchen/zipstream-php/src',
+        ),
+        'Symfony\\Polyfill\\Mbstring\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
+        ),
+        'Psr\\SimpleCache\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/psr/simple-cache/src',
+        ),
+        'Psr\\Http\\Message\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/psr/http-factory/src',
+            1 => __DIR__ . '/..' . '/psr/http-message/src',
+        ),
+        'Psr\\Http\\Client\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/psr/http-client/src',
+        ),
+        'PhpOffice\\PhpSpreadsheet\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet',
+        ),
+        'MyCLabs\\Enum\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/myclabs/php-enum/src',
+        ),
+        'Matrix\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/markbaker/matrix/classes/src',
+        ),
+        'Complex\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/markbaker/complex/classes/src',
+        ),
+    );
+
+    public static $prefixesPsr0 = array (
+        'H' => 
+        array (
+            'HTMLPurifier' => 
+            array (
+                0 => __DIR__ . '/..' . '/ezyang/htmlpurifier/library',
+            ),
+        ),
+    );
+
+    public static function getInitializer(ClassLoader $loader)
+    {
+        return \Closure::bind(function () use ($loader) {
+            $loader->prefixLengthsPsr4 = ComposerStaticInit6f94f903accf18c0690247a05621fdb2::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInit6f94f903accf18c0690247a05621fdb2::$prefixDirsPsr4;
+            $loader->prefixesPsr0 = ComposerStaticInit6f94f903accf18c0690247a05621fdb2::$prefixesPsr0;
+
+        }, null, ClassLoader::class);
+    }
+}

+ 672 - 0
vendor/composer/installed.json

@@ -0,0 +1,672 @@
+[
+    {
+        "name": "ezyang/htmlpurifier",
+        "version": "v4.14.0",
+        "version_normalized": "4.14.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/ezyang/htmlpurifier.git",
+            "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/12ab42bd6e742c70c0a52f7b82477fcd44e64b75",
+            "reference": "12ab42bd6e742c70c0a52f7b82477fcd44e64b75",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.2"
+        },
+        "time": "2021-12-25T01:21:49+00:00",
+        "type": "library",
+        "installation-source": "source",
+        "autoload": {
+            "files": [
+                "library/HTMLPurifier.composer.php"
+            ],
+            "psr-0": {
+                "HTMLPurifier": "library/"
+            },
+            "exclude-from-classmap": [
+                "/library/HTMLPurifier/Language/"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "LGPL-2.1-or-later"
+        ],
+        "authors": [
+            {
+                "name": "Edward Z. Yang",
+                "email": "admin@htmlpurifier.org",
+                "homepage": "http://ezyang.com"
+            }
+        ],
+        "description": "Standards compliant HTML filter written in PHP",
+        "homepage": "http://htmlpurifier.org/",
+        "keywords": [
+            "html"
+        ]
+    },
+    {
+        "name": "maennchen/zipstream-php",
+        "version": "2.1.0",
+        "version_normalized": "2.1.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/maennchen/ZipStream-PHP.git",
+            "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/c4c5803cc1f93df3d2448478ef79394a5981cc58",
+            "reference": "c4c5803cc1f93df3d2448478ef79394a5981cc58",
+            "shasum": ""
+        },
+        "require": {
+            "myclabs/php-enum": "^1.5",
+            "php": ">= 7.1",
+            "psr/http-message": "^1.0",
+            "symfony/polyfill-mbstring": "^1.0"
+        },
+        "require-dev": {
+            "ext-zip": "*",
+            "guzzlehttp/guzzle": ">= 6.3",
+            "mikey179/vfsstream": "^1.6",
+            "phpunit/phpunit": ">= 7.5"
+        },
+        "time": "2020-05-30T13:11:16+00:00",
+        "type": "library",
+        "installation-source": "source",
+        "autoload": {
+            "psr-4": {
+                "ZipStream\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Paul Duncan",
+                "email": "pabs@pablotron.org"
+            },
+            {
+                "name": "Jonatan Männchen",
+                "email": "jonatan@maennchen.ch"
+            },
+            {
+                "name": "Jesse Donat",
+                "email": "donatj@gmail.com"
+            },
+            {
+                "name": "András Kolesár",
+                "email": "kolesar@kolesar.hu"
+            }
+        ],
+        "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
+        "keywords": [
+            "stream",
+            "zip"
+        ],
+        "funding": [
+            {
+                "url": "https://opencollective.com/zipstream",
+                "type": "open_collective"
+            }
+        ]
+    },
+    {
+        "name": "markbaker/complex",
+        "version": "3.0.1",
+        "version_normalized": "3.0.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/MarkBaker/PHPComplex.git",
+            "reference": "ab8bc271e404909db09ff2d5ffa1e538085c0f22"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/ab8bc271e404909db09ff2d5ffa1e538085c0f22",
+            "reference": "ab8bc271e404909db09ff2d5ffa1e538085c0f22",
+            "shasum": ""
+        },
+        "require": {
+            "php": "^7.2 || ^8.0"
+        },
+        "require-dev": {
+            "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
+            "phpcompatibility/php-compatibility": "^9.0",
+            "phpunit/phpunit": "^7.0 || ^8.0 || ^9.3",
+            "squizlabs/php_codesniffer": "^3.4"
+        },
+        "time": "2021-06-29T15:32:53+00:00",
+        "type": "library",
+        "installation-source": "source",
+        "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"
+        ]
+    },
+    {
+        "name": "markbaker/matrix",
+        "version": "3.0.0",
+        "version_normalized": "3.0.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/MarkBaker/PHPMatrix.git",
+            "reference": "c66aefcafb4f6c269510e9ac46b82619a904c576"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/c66aefcafb4f6c269510e9ac46b82619a904c576",
+            "reference": "c66aefcafb4f6c269510e9ac46b82619a904c576",
+            "shasum": ""
+        },
+        "require": {
+            "php": "^7.1 || ^8.0"
+        },
+        "require-dev": {
+            "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
+            "phpcompatibility/php-compatibility": "^9.0",
+            "phpdocumentor/phpdocumentor": "2.*",
+            "phploc/phploc": "^4.0",
+            "phpmd/phpmd": "2.*",
+            "phpunit/phpunit": "^7.0 || ^8.0 || ^9.3",
+            "sebastian/phpcpd": "^4.0",
+            "squizlabs/php_codesniffer": "^3.4"
+        },
+        "time": "2021-07-01T19:01:15+00:00",
+        "type": "library",
+        "installation-source": "source",
+        "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"
+        ]
+    },
+    {
+        "name": "myclabs/php-enum",
+        "version": "1.8.3",
+        "version_normalized": "1.8.3.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/myclabs/php-enum.git",
+            "reference": "b942d263c641ddb5190929ff840c68f78713e937"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/myclabs/php-enum/zipball/b942d263c641ddb5190929ff840c68f78713e937",
+            "reference": "b942d263c641ddb5190929ff840c68f78713e937",
+            "shasum": ""
+        },
+        "require": {
+            "ext-json": "*",
+            "php": "^7.3 || ^8.0"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "^9.5",
+            "squizlabs/php_codesniffer": "1.*",
+            "vimeo/psalm": "^4.6.2"
+        },
+        "time": "2021-07-05T08:18:36+00:00",
+        "type": "library",
+        "installation-source": "source",
+        "autoload": {
+            "psr-4": {
+                "MyCLabs\\Enum\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "PHP Enum contributors",
+                "homepage": "https://github.com/myclabs/php-enum/graphs/contributors"
+            }
+        ],
+        "description": "PHP Enum implementation",
+        "homepage": "http://github.com/myclabs/php-enum",
+        "keywords": [
+            "enum"
+        ],
+        "funding": [
+            {
+                "url": "https://github.com/mnapoli",
+                "type": "github"
+            },
+            {
+                "url": "https://tidelift.com/funding/github/packagist/myclabs/php-enum",
+                "type": "tidelift"
+            }
+        ]
+    },
+    {
+        "name": "phpoffice/phpspreadsheet",
+        "version": "1.22.0",
+        "version_normalized": "1.22.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
+            "reference": "3a9e29b4f386a08a151a33578e80ef1747037a48"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/3a9e29b4f386a08a151a33578e80ef1747037a48",
+            "reference": "3a9e29b4f386a08a151a33578e80ef1747037a48",
+            "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.13",
+            "maennchen/zipstream-php": "^2.1",
+            "markbaker/complex": "^3.0",
+            "markbaker/matrix": "^3.0",
+            "php": "^7.3 || ^8.0",
+            "psr/http-client": "^1.0",
+            "psr/http-factory": "^1.0",
+            "psr/simple-cache": "^1.0"
+        },
+        "require-dev": {
+            "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
+            "dompdf/dompdf": "^1.0",
+            "friendsofphp/php-cs-fixer": "^3.2",
+            "jpgraph/jpgraph": "^4.0",
+            "mpdf/mpdf": "8.0.17",
+            "phpcompatibility/php-compatibility": "^9.3",
+            "phpstan/phpstan": "^1.1",
+            "phpstan/phpstan-phpunit": "^1.0",
+            "phpunit/phpunit": "^8.5 || ^9.0",
+            "squizlabs/php_codesniffer": "^3.6",
+            "tecnickcom/tcpdf": "^6.4"
+        },
+        "suggest": {
+            "dompdf/dompdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)",
+            "jpgraph/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 (doesn't yet support PHP8)"
+        },
+        "time": "2022-02-18T12:57:07+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"
+        ]
+    },
+    {
+        "name": "psr/http-client",
+        "version": "1.0.1",
+        "version_normalized": "1.0.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/php-fig/http-client.git",
+            "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+            "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+            "shasum": ""
+        },
+        "require": {
+            "php": "^7.0 || ^8.0",
+            "psr/http-message": "^1.0"
+        },
+        "time": "2020-06-29T06:28:15+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.0.x-dev"
+            }
+        },
+        "installation-source": "source",
+        "autoload": {
+            "psr-4": {
+                "Psr\\Http\\Client\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "PHP-FIG",
+                "homepage": "http://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"
+        ]
+    },
+    {
+        "name": "psr/http-factory",
+        "version": "1.0.1",
+        "version_normalized": "1.0.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/php-fig/http-factory.git",
+            "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
+            "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=7.0.0",
+            "psr/http-message": "^1.0"
+        },
+        "time": "2019-04-30T12:38:16+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.0.x-dev"
+            }
+        },
+        "installation-source": "source",
+        "autoload": {
+            "psr-4": {
+                "Psr\\Http\\Message\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "PHP-FIG",
+                "homepage": "http://www.php-fig.org/"
+            }
+        ],
+        "description": "Common interfaces for PSR-7 HTTP message factories",
+        "keywords": [
+            "factory",
+            "http",
+            "message",
+            "psr",
+            "psr-17",
+            "psr-7",
+            "request",
+            "response"
+        ]
+    },
+    {
+        "name": "psr/http-message",
+        "version": "1.0.1",
+        "version_normalized": "1.0.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/php-fig/http-message.git",
+            "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+            "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.0"
+        },
+        "time": "2016-08-06T14:39:51+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.0.x-dev"
+            }
+        },
+        "installation-source": "source",
+        "autoload": {
+            "psr-4": {
+                "Psr\\Http\\Message\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "PHP-FIG",
+                "homepage": "http://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"
+        ]
+    },
+    {
+        "name": "psr/simple-cache",
+        "version": "1.0.1",
+        "version_normalized": "1.0.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/php-fig/simple-cache.git",
+            "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+            "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.3.0"
+        },
+        "time": "2017-10-23T01:57:42+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-master": "1.0.x-dev"
+            }
+        },
+        "installation-source": "source",
+        "autoload": {
+            "psr-4": {
+                "Psr\\SimpleCache\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "PHP-FIG",
+                "homepage": "http://www.php-fig.org/"
+            }
+        ],
+        "description": "Common interfaces for simple caching",
+        "keywords": [
+            "cache",
+            "caching",
+            "psr",
+            "psr-16",
+            "simple-cache"
+        ]
+    },
+    {
+        "name": "symfony/polyfill-mbstring",
+        "version": "v1.25.0",
+        "version_normalized": "1.25.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/symfony/polyfill-mbstring.git",
+            "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825",
+            "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=7.1"
+        },
+        "provide": {
+            "ext-mbstring": "*"
+        },
+        "suggest": {
+            "ext-mbstring": "For best performance"
+        },
+        "time": "2021-11-30T18:21:41+00:00",
+        "type": "library",
+        "extra": {
+            "branch-alias": {
+                "dev-main": "1.23-dev"
+            },
+            "thanks": {
+                "name": "symfony/polyfill",
+                "url": "https://github.com/symfony/polyfill"
+            }
+        },
+        "installation-source": "source",
+        "autoload": {
+            "files": [
+                "bootstrap.php"
+            ],
+            "psr-4": {
+                "Symfony\\Polyfill\\Mbstring\\": ""
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Nicolas Grekas",
+                "email": "p@tchwork.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors"
+            }
+        ],
+        "description": "Symfony polyfill for the Mbstring extension",
+        "homepage": "https://symfony.com",
+        "keywords": [
+            "compatibility",
+            "mbstring",
+            "polyfill",
+            "portable",
+            "shim"
+        ],
+        "funding": [
+            {
+                "url": "https://symfony.com/sponsor",
+                "type": "custom"
+            },
+            {
+                "url": "https://github.com/fabpot",
+                "type": "github"
+            },
+            {
+                "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+                "type": "tidelift"
+            }
+        ]
+    }
+]

+ 1 - 0
vendor/ezyang/htmlpurifier

@@ -0,0 +1 @@
+Subproject commit 12ab42bd6e742c70c0a52f7b82477fcd44e64b75

+ 1 - 0
vendor/maennchen/zipstream-php

@@ -0,0 +1 @@
+Subproject commit c4c5803cc1f93df3d2448478ef79394a5981cc58

+ 1 - 0
vendor/markbaker/complex

@@ -0,0 +1 @@
+Subproject commit ab8bc271e404909db09ff2d5ffa1e538085c0f22

+ 1 - 0
vendor/markbaker/matrix

@@ -0,0 +1 @@
+Subproject commit c66aefcafb4f6c269510e9ac46b82619a904c576

+ 1 - 0
vendor/myclabs/php-enum

@@ -0,0 +1 @@
+Subproject commit b942d263c641ddb5190929ff840c68f78713e937

+ 226 - 0
vendor/phpoffice/phpspreadsheet/.php-cs-fixer.dist.php

@@ -0,0 +1,226 @@
+<?php
+
+$finder = PhpCsFixer\Finder::create()
+    ->exclude('vendor')
+    ->in(__DIR__);
+
+$config = new PhpCsFixer\Config();
+$config
+    ->setRiskyAllowed(true)
+    ->setFinder($finder)
+    ->setCacheFile(sys_get_temp_dir() . '/php-cs-fixer' . preg_replace('~\W~', '-', __DIR__))
+    ->setRules([
+        'align_multiline_comment' => true,
+        'array_indentation' => true,
+        'array_syntax' => ['syntax' => 'short'],
+        'backtick_to_shell_exec' => true,
+        'binary_operator_spaces' => true,
+        'blank_line_after_namespace' => true,
+        'blank_line_after_opening_tag' => true,
+        'blank_line_before_statement' => true,
+        'braces' => true,
+        'cast_spaces' => true,
+        'class_attributes_separation' => ['elements' => ['method' => 'one', 'property' => 'one']], // const are often grouped with other related const
+        'class_definition' => true,
+        'class_keyword_remove' => false, // ::class keyword gives us better support in IDE
+        'combine_consecutive_issets' => true,
+        'combine_consecutive_unsets' => true,
+        'combine_nested_dirname' => true,
+        'comment_to_phpdoc' => true,
+        'compact_nullable_typehint' => true,
+        'concat_space' => ['spacing' => 'one'],
+        'constant_case' => true,
+        'date_time_immutable' => false, // Break our unit tests
+        'declare_equal_normalize' => true,
+        'declare_strict_types' => false, // Too early to adopt strict types
+        'dir_constant' => true,
+        'doctrine_annotation_array_assignment' => true,
+        'doctrine_annotation_braces' => true,
+        'doctrine_annotation_indentation' => true,
+        'doctrine_annotation_spaces' => true,
+        'elseif' => true,
+        'encoding' => true,
+        'ereg_to_preg' => true,
+        'escape_implicit_backslashes' => true,
+        'explicit_indirect_variable' => false, // I feel it makes the code actually harder to read
+        'explicit_string_variable' => false, // I feel it makes the code actually harder to read
+        'final_class' => false, // We need non-final classes
+        'final_internal_class' => true,
+        'final_public_method_for_abstract_class' => false, // We need non-final methods
+        'self_static_accessor' => true,
+        'fopen_flag_order' => true,
+        'fopen_flags' => true,
+        'full_opening_tag' => true,
+        'fully_qualified_strict_types' => true,
+        'function_declaration' => true,
+        'function_to_constant' => true,
+        'function_typehint_space' => true,
+        'general_phpdoc_annotation_remove' => ['annotations' => ['access', 'category', 'copyright', 'throws']],
+        'global_namespace_import' => true,
+        'header_comment' => false, // We don't use common header in all our files
+        'heredoc_indentation' => false, // Requires PHP >= 7.3
+        'heredoc_to_nowdoc' => false, // Not sure about this one
+        'implode_call' => true,
+        'include' => true,
+        'increment_style' => true,
+        'indentation_type' => true,
+        'is_null' => true,
+        'line_ending' => true,
+        'linebreak_after_opening_tag' => true,
+        'list_syntax' => ['syntax' => 'short'],
+        'logical_operators' => true,
+        'lowercase_cast' => true,
+        'lowercase_keywords' => true,
+        'lowercase_static_reference' => true,
+        'magic_constant_casing' => true,
+        'magic_method_casing' => true,
+        'mb_str_functions' => false, // No, too dangerous to change that
+        'method_argument_space' => true,
+        'method_chaining_indentation' => true,
+        'modernize_types_casting' => true,
+        'multiline_comment_opening_closing' => true,
+        'multiline_whitespace_before_semicolons' => true,
+        'native_constant_invocation' => false, // Micro optimization that look messy
+        'native_function_casing' => true,
+        'native_function_invocation' => false, // I suppose this would be best, but I am still unconvinced about the visual aspect of it
+        'native_function_type_declaration_casing' => true,
+        'new_with_braces' => true,
+        'no_alias_functions' => true,
+        'no_alternative_syntax' => true,
+        'no_binary_string' => true,
+        'no_blank_lines_after_class_opening' => true,
+        'no_blank_lines_after_phpdoc' => true,
+        'no_blank_lines_before_namespace' => false, // we want 1 blank line before namespace
+        'no_break_comment' => true,
+        'no_closing_tag' => true,
+        'no_empty_comment' => true,
+        'no_empty_phpdoc' => true,
+        'no_empty_statement' => true,
+        'no_extra_blank_lines' => true,
+        'no_homoglyph_names' => true,
+        'no_leading_import_slash' => true,
+        'no_leading_namespace_whitespace' => true,
+        'no_mixed_echo_print' => true,
+        'no_multiline_whitespace_around_double_arrow' => true,
+        'no_null_property_initialization' => true,
+        'no_php4_constructor' => true,
+        'no_short_bool_cast' => true,
+        'echo_tag_syntax' => ['format' => 'long'],
+        'no_singleline_whitespace_before_semicolons' => true,
+        'no_spaces_after_function_name' => true,
+        'no_spaces_around_offset' => true,
+        'no_spaces_inside_parenthesis' => true,
+        'no_superfluous_elseif' => false, // Might be risky on a huge code base
+        'no_superfluous_phpdoc_tags' => ['allow_mixed' => true],
+        'no_trailing_comma_in_list_call' => true,
+        'no_trailing_comma_in_singleline_array' => true,
+        'no_trailing_whitespace' => true,
+        'no_trailing_whitespace_in_comment' => true,
+        'no_unneeded_control_parentheses' => true,
+        'no_unneeded_curly_braces' => true,
+        'no_unneeded_final_method' => true,
+        'no_unreachable_default_argument_value' => true,
+        'no_unset_cast' => true,
+        'no_unset_on_property' => true,
+        'no_unused_imports' => true,
+        'no_useless_else' => true,
+        'no_useless_return' => true,
+        'no_whitespace_before_comma_in_array' => true,
+        'no_whitespace_in_blank_line' => true,
+        'non_printable_character' => true,
+        'normalize_index_brace' => true,
+        'not_operator_with_space' => false, // No we prefer to keep '!' without spaces
+        'not_operator_with_successor_space' => false, // idem
+        'nullable_type_declaration_for_default_null_value' => true,
+        'object_operator_without_whitespace' => true,
+        'ordered_class_elements' => false, // We prefer to keep some freedom
+        'ordered_imports' => true,
+        'ordered_interfaces' => true,
+        'php_unit_construct' => true,
+        'php_unit_dedicate_assert' => true,
+        'php_unit_dedicate_assert_internal_type' => true,
+        'php_unit_expectation' => true,
+        'php_unit_fqcn_annotation' => true,
+        'php_unit_internal_class' => false, // Because tests are excluded from package
+        'php_unit_method_casing' => true,
+        'php_unit_mock' => true,
+        'php_unit_mock_short_will_return' => true,
+        'php_unit_namespaced' => true,
+        'php_unit_no_expectation_annotation' => true,
+        'phpdoc_order_by_value' => ['annotations' => ['covers']],
+        'php_unit_set_up_tear_down_visibility' => true,
+        'php_unit_size_class' => false, // That seems extra work to maintain for little benefits
+        'php_unit_strict' => false, // We sometime actually need assertEquals
+        'php_unit_test_annotation' => true,
+        'php_unit_test_case_static_method_calls' => ['call_type' => 'self'],
+        'php_unit_test_class_requires_covers' => false, // We don't care as much as we should about coverage
+        'phpdoc_add_missing_param_annotation' => false, // Don't add things that bring no value
+        'phpdoc_align' => false, // Waste of time
+        'phpdoc_annotation_without_dot' => true,
+        'phpdoc_indent' => true,
+        //'phpdoc_inline_tag' => true,
+        'phpdoc_line_span' => false, // Unfortunately our old comments turn even uglier with this
+        'phpdoc_no_access' => true,
+        'phpdoc_no_alias_tag' => true,
+        'phpdoc_no_empty_return' => true,
+        'phpdoc_no_package' => true,
+        'phpdoc_no_useless_inheritdoc' => true,
+        'phpdoc_order' => true,
+        'phpdoc_return_self_reference' => true,
+        'phpdoc_scalar' => true,
+        'phpdoc_separation' => true,
+        'phpdoc_single_line_var_spacing' => true,
+        'phpdoc_summary' => true,
+        'phpdoc_to_comment' => true,
+        'phpdoc_to_param_type' => false, // Because experimental, but interesting for one shot use
+        'phpdoc_to_return_type' => false, // idem
+        'phpdoc_trim' => true,
+        'phpdoc_trim_consecutive_blank_line_separation' => true,
+        'phpdoc_types' => true,
+        'phpdoc_types_order' => true,
+        'phpdoc_var_annotation_correct_order' => true,
+        'phpdoc_var_without_name' => true,
+        'pow_to_exponentiation' => true,
+        'protected_to_private' => true,
+        //'psr0' => true,
+        //'psr4' => true,
+        'random_api_migration' => true,
+        'return_assignment' => false, // Sometimes useful for clarity or debug
+        'return_type_declaration' => true,
+        'self_accessor' => true,
+        'self_static_accessor' => true,
+        'semicolon_after_instruction' => false, // Buggy in `samples/index.php`
+        'set_type_to_cast' => true,
+        'short_scalar_cast' => true,
+        'simple_to_complex_string_variable' => false, // Would differ from TypeScript without obvious advantages
+        'simplified_null_return' => false, // Even if technically correct we prefer to be explicit
+        'single_blank_line_at_eof' => true,
+        'single_blank_line_before_namespace' => true,
+        'single_class_element_per_statement' => true,
+        'single_import_per_statement' => true,
+        'single_line_after_imports' => true,
+        'single_line_comment_style' => true,
+        'single_line_throw' => false, // I don't see any reason for having a special case for Exception
+        'single_quote' => true,
+        'single_trait_insert_per_statement' => true,
+        'space_after_semicolon' => true,
+        'standardize_increment' => true,
+        'standardize_not_equals' => true,
+        'static_lambda' => false, // Risky if we can't guarantee nobody use `bindTo()`
+        'strict_comparison' => false, // No, too dangerous to change that
+        'strict_param' => false, // No, too dangerous to change that
+        'string_line_ending' => true,
+        'switch_case_semicolon_to_colon' => true,
+        'switch_case_space' => true,
+        'ternary_operator_spaces' => true,
+        'ternary_to_null_coalescing' => true,
+        'trailing_comma_in_multiline' => true,
+        'trim_array_spaces' => true,
+        'unary_operator_spaces' => true,
+        'visibility_required' => ['elements' => ['property', 'method']], // not const
+        'void_return' => true,
+        'whitespace_after_comma_in_array' => true,
+        'yoda_style' => false,
+    ]);
+
+return $config;

+ 22 - 0
vendor/phpoffice/phpspreadsheet/.phpcs.xml.dist

@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="PHP_CodeSniffer"
+         xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
+
+    <file>samples</file>
+    <file>src</file>
+    <file>tests</file>
+
+    <exclude-pattern>samples/Header.php</exclude-pattern>
+    <exclude-pattern>*/tests/Core/*/*Test\.(inc|css|js)$</exclude-pattern>
+
+    <arg name="report-width" value="200"/>
+    <arg name="parallel" value="80"/>
+    <arg name="cache" value="/tmp/.phpspreadsheet.phpcs-cache"/>
+    <arg name="colors"/>
+    <arg value="np"/>
+
+    <!-- Include the whole PSR12 standard -->
+    <rule ref="PSR12">
+        <exclude name="PSR2.Methods.MethodDeclaration.Underscore"/>
+    </rule>
+</ruleset>

File diff suppressed because it is too large
+ 958 - 0
vendor/phpoffice/phpspreadsheet/CHANGELOG.md


+ 20 - 0
vendor/phpoffice/phpspreadsheet/CONTRIBUTING.md

@@ -0,0 +1,20 @@
+# Want to contribute?
+
+If you would like to contribute, here are some notes and guidelines:
+
+ - All new development happens on feature/fix branches, and are then merged to the `master` branch once stable; so the `master` branch is always the most up-to-date, working code
+ - Tagged releases are made from the `master` branch
+ - If you are going to be submitting a pull request, please fork from `master`, and submit your pull request back as a fix/feature branch referencing the GitHub issue number
+ - Code style might be automatically fixed by `composer fix`
+ - All code changes must be validated by `composer check`
+ - [Helpful article about forking](https://help.github.com/articles/fork-a-repo/ "Forking a GitHub repository")
+ - [Helpful article about pull requests](https://help.github.com/articles/using-pull-requests/ "Pull Requests")
+
+## How to release
+
+1. Complete CHANGELOG.md and commit
+2. Create an annotated tag
+    1. `git tag -a 1.2.3`
+    2. Tag subject must be the version number, eg: `1.2.3`
+    3. Tag body must be a copy-paste of the changelog entries
+3. Push tag with `git push --tags`, GitHub Actions will create a GitHub release automatically

+ 21 - 0
vendor/phpoffice/phpspreadsheet/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 PhpSpreadsheet Authors
+
+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.

+ 30 - 0
vendor/phpoffice/phpspreadsheet/README.md

@@ -0,0 +1,30 @@
+# PhpSpreadsheet
+
+[![Build Status](https://github.com/PHPOffice/PhpSpreadsheet/workflows/main/badge.svg)](https://github.com/PHPOffice/PhpSpreadsheet/actions)
+[![Code Quality](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/?branch=master)
+[![Code Coverage](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/?branch=master)
+[![Total Downloads](https://img.shields.io/packagist/dt/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet)
+[![Latest Stable Version](https://img.shields.io/github/v/release/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet)
+[![License](https://img.shields.io/github/license/PHPOffice/PhpSpreadsheet)](https://packagist.org/packages/phpoffice/phpspreadsheet)
+[![Join the chat at https://gitter.im/PHPOffice/PhpSpreadsheet](https://img.shields.io/badge/GITTER-join%20chat-green.svg)](https://gitter.im/PHPOffice/PhpSpreadsheet)
+
+PhpSpreadsheet is a library written in pure PHP and offers a set of classes that
+allow you to read and write various spreadsheet file formats such as Excel and LibreOffice Calc.
+
+## Documentation
+
+Read more about it, including install instructions, in the [official documentation](https://phpspreadsheet.readthedocs.io). Or check out the [API documentation](https://phpoffice.github.io/PhpSpreadsheet).
+
+Please ask your support questions on [StackOverflow](https://stackoverflow.com/questions/tagged/phpspreadsheet), or have a quick chat on [Gitter](https://gitter.im/PHPOffice/PhpSpreadsheet).
+
+## PHPExcel vs PhpSpreadsheet ?
+
+PhpSpreadsheet is the next version of PHPExcel. It breaks compatibility to dramatically improve the code base quality (namespaces, PSR compliance, use of latest PHP language features, etc.).
+
+Because all efforts have shifted to PhpSpreadsheet, PHPExcel will no longer be maintained. All contributions for PHPExcel, patches and new features, should target PhpSpreadsheet `master` branch.
+
+Do you need to migrate? There is [an automated tool](/docs/topics/migration-from-PHPExcel.md) for that.
+
+## License
+
+PhpSpreadsheet is licensed under [MIT](https://github.com/PHPOffice/PhpSpreadsheet/blob/master/LICENSE).

+ 110 - 0
vendor/phpoffice/phpspreadsheet/composer.json

@@ -0,0 +1,110 @@
+{
+    "name": "phpoffice/phpspreadsheet",
+    "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
+    "keywords": [
+        "PHP",
+        "OpenXML",
+        "Excel",
+        "xlsx",
+        "xls",
+        "ods",
+        "gnumeric",
+        "spreadsheet"
+    ],
+    "config": {
+        "sort-packages": true,
+        "allow-plugins": {
+            "dealerdirect/phpcodesniffer-composer-installer": true
+        }
+    },
+    "homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
+    "type": "library",
+    "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"
+        }
+    ],
+    "scripts": {
+        "check": [
+            "php-cs-fixer fix --ansi --dry-run --diff",
+            "phpcs",
+            "phpunit --color=always",
+            "phpstan analyse --ansi"
+        ],
+        "fix": [
+            "php-cs-fixer fix --ansi"
+        ],
+        "versions": [
+            "phpcs --report-width=200 samples/ src/ tests/ --ignore=samples/Header.php --standard=PHPCompatibility --runtime-set testVersion 7.3- -n"
+        ]
+    },
+    "require": {
+        "php": "^7.3 || ^8.0",
+        "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.13",
+        "maennchen/zipstream-php": "^2.1",
+        "markbaker/complex": "^3.0",
+        "markbaker/matrix": "^3.0",
+        "psr/http-client": "^1.0",
+        "psr/http-factory": "^1.0",
+        "psr/simple-cache": "^1.0"
+    },
+    "require-dev": {
+        "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
+        "dompdf/dompdf": "^1.0",
+        "friendsofphp/php-cs-fixer": "^3.2",
+        "jpgraph/jpgraph": "^4.0",
+        "mpdf/mpdf": "8.0.17",
+        "phpcompatibility/php-compatibility": "^9.3",
+        "phpstan/phpstan": "^1.1",
+        "phpstan/phpstan-phpunit": "^1.0",
+        "phpunit/phpunit": "^8.5 || ^9.0",
+        "squizlabs/php_codesniffer": "^3.6",
+        "tecnickcom/tcpdf": "^6.4"
+    },
+    "suggest": {
+        "mpdf/mpdf": "Option for rendering PDF with PDF Writer",
+        "dompdf/dompdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)",
+        "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer (doesn't yet support PHP8)",
+        "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers"
+    },
+    "autoload": {
+        "psr-4": {
+            "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "PhpOffice\\PhpSpreadsheetTests\\": "tests/PhpSpreadsheetTests",
+            "PhpOffice\\PhpSpreadsheetInfra\\": "infra"
+        }
+    }
+}

File diff suppressed because it is too large
+ 6182 - 0
vendor/phpoffice/phpspreadsheet/phpstan-baseline.neon


+ 51 - 0
vendor/phpoffice/phpspreadsheet/phpstan-conditional.php

@@ -0,0 +1,51 @@
+<?php
+
+$config = [];
+
+if (PHP_VERSION_ID < 80000) {
+    // GdImage not available before PHP8
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '~^Method .* has invalid return type GdImage\.$~',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Shared/Drawing.php',
+        'count' => 1,
+    ];
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '~^Property .* has unknown class GdImage as its type\.$~',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php',
+        'count' => 1,
+    ];
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '~^Method .* has invalid return type GdImage\.$~',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php',
+        'count' => 1,
+    ];
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '~^Parameter .* of method .* has invalid type GdImage\.$~',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php',
+        'count' => 1,
+    ];
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '~^Class GdImage not found\.$~',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Writer/Xls/Worksheet.php',
+        'count' => 1,
+    ];
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '~^Parameter .* of method .* has invalid type GdImage\.$~',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Writer/Xls/Worksheet.php',
+        'count' => 1,
+    ];
+    // Erroneous analysis by Phpstan before PHP8 - 3rd parameter is nullable
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '#^Parameter \\#3 \\$namespace of method XMLWriter\\:\\:startElementNs\\(\\) expects string, null given\\.$#',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php',
+        'count' => 8,
+    ];
+    // Erroneous analysis by Phpstan before PHP8 - mb_strlen does not return false
+    $config['parameters']['ignoreErrors'][] = [
+        'message' => '#^Method PhpOffice\\\\PhpSpreadsheet\\\\Shared\\\\StringHelper\\:\\:countCharacters\\(\\) should return int but returns int(<0, max>)?\\|false\\.$#',
+        'path' => __DIR__ . '/src/PhpSpreadsheet/Shared/StringHelper.php',
+        'count' => 1,
+    ];
+}
+
+return $config;

+ 26 - 0
vendor/phpoffice/phpspreadsheet/phpstan.neon.dist

@@ -0,0 +1,26 @@
+includes:
+    - phpstan-baseline.neon
+    - phpstan-conditional.php
+    - vendor/phpstan/phpstan-phpunit/extension.neon
+    - vendor/phpstan/phpstan-phpunit/rules.neon
+
+parameters:
+    level: 8
+    paths:
+        - src/
+        - tests/
+    parallel:
+        processTimeout: 300.0
+    checkMissingIterableValueType: false
+    ignoreErrors:
+        - '~^Parameter \#1 \$im(age)? of function (imagedestroy|imageistruecolor|imagealphablending|imagesavealpha|imagecolortransparent|imagecolorsforindex|imagesavealpha|imagesx|imagesy) expects (GdImage|resource), GdImage\|resource given\.$~'
+        - '~^Parameter \#2 \$src_im(age)? of function imagecopy expects (GdImage|resource), GdImage\|resource given\.$~'
+        # Accept a bit anything for assert methods
+        - '~^Parameter \#2 .* of static method PHPUnit\\Framework\\Assert\:\:assert\w+\(\) expects .*, .* given\.$~'
+        - '~^Method PhpOffice\\PhpSpreadsheetTests\\.*\:\:test.*\(\) has parameter \$args with no type specified\.$~'
+
+        # Ignore all JpGraph issues
+        - '~^Constant (MARK_CIRCLE|MARK_CROSS|MARK_DIAMOND|MARK_DTRIANGLE|MARK_FILLEDCIRCLE|MARK_SQUARE|MARK_STAR|MARK_UTRIANGLE|MARK_X|SIDE_RIGHT) not found\.$~'
+        - '~^Instantiated class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot) not found\.$~'
+        - '~^Call to method .*\(\) on an unknown class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot)\.$~'
+        - '~^Access to property .* on an unknown class (AccBarPlot|AccLinePlot|BarPlot|ContourPlot|Graph|GroupBarPlot|GroupBarPlot|LinePlot|LinePlot|PieGraph|PiePlot|PiePlot3D|PiePlotC|RadarGraph|RadarPlot|ScatterPlot|Spline|StockPlot)\.$~'

+ 56 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ArrayEnabled.php

@@ -0,0 +1,56 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Engine\ArrayArgumentHelper;
+use PhpOffice\PhpSpreadsheet\Calculation\Engine\ArrayArgumentProcessor;
+
+trait ArrayEnabled
+{
+    /**
+     * @var ArrayArgumentHelper
+     */
+    private static $arrayArgumentHelper;
+
+    private static function initialiseHelper(array $arguments): void
+    {
+        if (self::$arrayArgumentHelper === null) {
+            self::$arrayArgumentHelper = new ArrayArgumentHelper();
+        }
+        self::$arrayArgumentHelper->initialise($arguments);
+    }
+
+    protected static function evaluateSingleArgumentArray(callable $method, array $values): array
+    {
+        $result = [];
+        foreach ($values as $value) {
+            $result[] = $method($value);
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param mixed ...$arguments
+     */
+    protected static function evaluateArrayArguments(callable $method, ...$arguments): array
+    {
+        self::initialiseHelper($arguments);
+        $arguments = self::$arrayArgumentHelper->arguments();
+
+        return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
+    }
+
+    /**
+     * @param mixed ...$arguments
+     */
+    protected static function evaluateArrayArgumentsSubset(callable $method, int $limit, ...$arguments): array
+    {
+        self::initialiseHelper(array_slice($arguments, 0, $limit));
+        $trailingArguments = array_slice($arguments, $limit);
+        $arguments = self::$arrayArgumentHelper->arguments();
+        $arguments = array_merge($arguments, $trailingArguments);
+
+        return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments);
+    }
+}

+ 181 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/BinaryComparison.php

@@ -0,0 +1,181 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation;
+
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class BinaryComparison
+{
+    /**
+     * Epsilon Precision used for comparisons in calculations.
+     */
+    private const DELTA = 0.1e-12;
+
+    /**
+     * Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters.
+     *
+     * @param null|string $str1 First string value for the comparison
+     * @param null|string $str2 Second string value for the comparison
+     */
+    private static function strcmpLowercaseFirst($str1, $str2): int
+    {
+        $inversedStr1 = StringHelper::strCaseReverse($str1 ?? '');
+        $inversedStr2 = StringHelper::strCaseReverse($str2 ?? '');
+
+        return strcmp($inversedStr1, $inversedStr2);
+    }
+
+    /**
+     * PHP8.1 deprecates passing null to strcmp.
+     *
+     * @param null|string $str1 First string value for the comparison
+     * @param null|string $str2 Second string value for the comparison
+     */
+    private static function strcmpAllowNull($str1, $str2): int
+    {
+        return strcmp($str1 ?? '', $str2 ?? '');
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    public static function compare($operand1, $operand2, string $operator): bool
+    {
+        //    Simple validate the two operands if they are string values
+        if (is_string($operand1) && $operand1 > '' && $operand1[0] == Calculation::FORMULA_STRING_QUOTE) {
+            $operand1 = Calculation::unwrapResult($operand1);
+        }
+        if (is_string($operand2) && $operand2 > '' && $operand2[0] == Calculation::FORMULA_STRING_QUOTE) {
+            $operand2 = Calculation::unwrapResult($operand2);
+        }
+
+        // Use case insensitive comparaison if not OpenOffice mode
+        if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) {
+            if (is_string($operand1)) {
+                $operand1 = StringHelper::strToUpper($operand1);
+            }
+            if (is_string($operand2)) {
+                $operand2 = StringHelper::strToUpper($operand2);
+            }
+        }
+
+        $useLowercaseFirstComparison = is_string($operand1) &&
+            is_string($operand2) &&
+            Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE;
+
+        return self::evaluateComparison($operand1, $operand2, $operator, $useLowercaseFirstComparison);
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    private static function evaluateComparison($operand1, $operand2, string $operator, bool $useLowercaseFirstComparison): bool
+    {
+        switch ($operator) {
+            //    Equality
+            case '=':
+                return self::equal($operand1, $operand2);
+            //    Greater than
+            case '>':
+                return self::greaterThan($operand1, $operand2, $useLowercaseFirstComparison);
+            //    Less than
+            case '<':
+                return self::lessThan($operand1, $operand2, $useLowercaseFirstComparison);
+            //    Greater than or equal
+            case '>=':
+                return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison);
+            //    Less than or equal
+            case '<=':
+                return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison);
+            //    Inequality
+            case '<>':
+                return self::notEqual($operand1, $operand2);
+            default:
+                throw new Exception('Unsupported binary comparison operator');
+        }
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    private static function equal($operand1, $operand2): bool
+    {
+        if (is_numeric($operand1) && is_numeric($operand2)) {
+            $result = (abs($operand1 - $operand2) < self::DELTA);
+        } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
+            $result = $operand1 == $operand2;
+        } else {
+            $result = self::strcmpAllowNull($operand1, $operand2) == 0;
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    private static function greaterThanOrEqual($operand1, $operand2, bool $useLowercaseFirstComparison): bool
+    {
+        if (is_numeric($operand1) && is_numeric($operand2)) {
+            $result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 > $operand2));
+        } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
+            $result = $operand1 >= $operand2;
+        } elseif ($useLowercaseFirstComparison) {
+            $result = self::strcmpLowercaseFirst($operand1, $operand2) >= 0;
+        } else {
+            $result = self::strcmpAllowNull($operand1, $operand2) >= 0;
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    private static function lessThanOrEqual($operand1, $operand2, bool $useLowercaseFirstComparison): bool
+    {
+        if (is_numeric($operand1) && is_numeric($operand2)) {
+            $result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 < $operand2));
+        } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) {
+            $result = $operand1 <= $operand2;
+        } elseif ($useLowercaseFirstComparison) {
+            $result = self::strcmpLowercaseFirst($operand1, $operand2) <= 0;
+        } else {
+            $result = self::strcmpAllowNull($operand1, $operand2) <= 0;
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    private static function greaterThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool
+    {
+        return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true;
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    private static function lessThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool
+    {
+        return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true;
+    }
+
+    /**
+     * @param mixed $operand1
+     * @param mixed $operand2
+     */
+    private static function notEqual($operand1, $operand2): bool
+    {
+        return self::equal($operand1, $operand2) !== true;
+    }
+}

File diff suppressed because it is too large
+ 5453 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Calculation.php


+ 20 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Category.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation;
+
+abstract class Category
+{
+    // Function categories
+    const CATEGORY_CUBE = 'Cube';
+    const CATEGORY_DATABASE = 'Database';
+    const CATEGORY_DATE_AND_TIME = 'Date and Time';
+    const CATEGORY_ENGINEERING = 'Engineering';
+    const CATEGORY_FINANCIAL = 'Financial';
+    const CATEGORY_INFORMATION = 'Information';
+    const CATEGORY_LOGICAL = 'Logical';
+    const CATEGORY_LOOKUP_AND_REFERENCE = 'Lookup and Reference';
+    const CATEGORY_MATH_AND_TRIG = 'Math and Trig';
+    const CATEGORY_STATISTICAL = 'Statistical';
+    const CATEGORY_TEXT_AND_DATA = 'Text and Data';
+    const CATEGORY_WEB = 'Web';
+}

+ 440 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database.php

@@ -0,0 +1,440 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation;
+
+/**
+ * @deprecated 1.17.0
+ */
+class Database
+{
+    /**
+     * DAVERAGE.
+     *
+     * Averages the values in a column of a list or database that match conditions you specify.
+     *
+     * Excel Function:
+     *        DAVERAGE(database,field,criteria)
+     *
+     * @Deprecated 1.17.0
+     *
+     * @see Database\DAverage::evaluate()
+     *      Use the evaluate() method in the Database\DAverage class instead
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return null|float|string
+     */
+    public static function DAVERAGE($database, $field, $criteria)
+    {
+        return Database\DAverage::evaluate($database, $field, $criteria);
+    }
+
+    /**
+     * DCOUNT.
+     *
+     * Counts the cells that contain numbers in a column of a list or database that match conditions
+     * that you specify.
+     *
+     * Excel Function:
+     *        DCOUNT(database,[field],criteria)
+     *
+     * @Deprecated 1.17.0
+     *
+     * @see Database\DCount::evaluate()
+     *      Use the evaluate() method in the Database\DCount class instead
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param null|int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return int
+     *
+     * @TODO    The field argument is optional. If field is omitted, DCOUNT counts all records in the
+     *            database that match the criteria.
+     */
+    public static function DCOUNT($database, $field, $criteria)
+    {
+        return Database\DCount::evaluate($database, $field, $criteria);
+    }
+
+    /**
+     * DCOUNTA.
+     *
+     * Counts the nonblank cells in a column of a list or database that match conditions that you specify.
+     *
+     * Excel Function:
+     *        DCOUNTA(database,[field],criteria)
+     *
+     * @Deprecated 1.17.0
+     *
+     * @see Database\DCountA::evaluate()
+     *      Use the evaluate() method in the Database\DCountA class instead
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param null|int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return int
+     */
+    public static function DCOUNTA($database, $field, $criteria)
+    {
+        return Database\DCountA::evaluate($database, $field, $criteria);
+    }
+
+    /**
+     * DGET.
+     *
+     * Extracts a single value from a column of a list or database that matches conditions that you
+     * specify.
+     *
+     * Excel Function:
+     *        DGET(database,field,criteria)
+     *
+     * @Deprecated 1.17.0
+     *
+     * @see Database\DGet::evaluate()
+     *      Use the evaluate() method in the Database\DGet class instead
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return mixed
+     */
+    public static function DGET($database, $field, $criteria)
+    {
+        return Database\DGet::evaluate($database, $field, $criteria);
+    }
+
+    /**
+     * DMAX.
+     *
+     * Returns the largest number in a column of a list or database that matches conditions you that
+     * specify.
+     *
+     * Excel Function:
+     *        DMAX(database,field,criteria)
+     *
+     * @Deprecated 1.17.0
+     *
+     * @see Database\DMax::evaluate()
+     *      Use the evaluate() method in the Database\DMax class instead
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return float
+     */
+    public static function DMAX($database, $field, $criteria)
+    {
+        return Database\DMax::evaluate($database, $field, $criteria);
+    }
+
+    /**
+     * DMIN.
+     *
+     * Returns the smallest number in a column of a list or database that matches conditions you that
+     * specify.
+     *
+     * Excel Function:
+     *        DMIN(database,field,criteria)
+     *
+     * @Deprecated 1.17.0
+     *
+     * @see Database\DMin::evaluate()
+     *      Use the evaluate() method in the Database\DMin class instead
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return float
+     */
+    public static function DMIN($database, $field, $criteria)
+    {
+        return Database\DMin::evaluate($database, $field, $criteria);
+    }
+
+    /**
+     * DPRODUCT.
+     *
+     * Multiplies the values in a column of a list or database that match conditions that you specify.
+     *
+     * Excel Function:
+     *        DPRODUCT(database,field,criteria)
+     *
+     * @Deprecated 1.17.0
+     *
+     * @see Database\DProduct::evaluate()
+     *      Use the evaluate() method in the Database\DProduct class instead
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return float|string
+     */
+    public static function DPRODUCT($database, $field, $criteria)
+    {
+        return Database\DProduct::evaluate($database, $field, $criteria);
+    }
+
+    /**
+     * DSTDEV.
+     *
+     * Estimates the standard deviation of a population based on a sample by using the numbers in a
+     * column of a list or database that match conditions that you specify.
+     *
+     * Excel Function:
+     *        DSTDEV(database,field,criteria)
+     *
+     * @Deprecated 1.17.0
+     *
+     * @see Database\DStDev::evaluate()
+     *      Use the evaluate() method in the Database\DStDev class instead
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return float|string
+     */
+    public static function DSTDEV($database, $field, $criteria)
+    {
+        return Database\DStDev::evaluate($database, $field, $criteria);
+    }
+
+    /**
+     * DSTDEVP.
+     *
+     * Calculates the standard deviation of a population based on the entire population by using the
+     * numbers in a column of a list or database that match conditions that you specify.
+     *
+     * Excel Function:
+     *        DSTDEVP(database,field,criteria)
+     *
+     * @Deprecated 1.17.0
+     *
+     * @see Database\DStDevP::evaluate()
+     *      Use the evaluate() method in the Database\DStDevP class instead
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return float|string
+     */
+    public static function DSTDEVP($database, $field, $criteria)
+    {
+        return Database\DStDevP::evaluate($database, $field, $criteria);
+    }
+
+    /**
+     * DSUM.
+     *
+     * Adds the numbers in a column of a list or database that match conditions that you specify.
+     *
+     * Excel Function:
+     *        DSUM(database,field,criteria)
+     *
+     * @Deprecated 1.17.0
+     *
+     * @see Database\DSum::evaluate()
+     *      Use the evaluate() method in the Database\DSum class instead
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return float|string
+     */
+    public static function DSUM($database, $field, $criteria)
+    {
+        return Database\DSum::evaluate($database, $field, $criteria);
+    }
+
+    /**
+     * DVAR.
+     *
+     * Estimates the variance of a population based on a sample by using the numbers in a column
+     * of a list or database that match conditions that you specify.
+     *
+     * Excel Function:
+     *        DVAR(database,field,criteria)
+     *
+     * @Deprecated 1.17.0
+     *
+     * @see Database\DVar::evaluate()
+     *      Use the evaluate() method in the Database\DVar class instead
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return float|string (string if result is an error)
+     */
+    public static function DVAR($database, $field, $criteria)
+    {
+        return Database\DVar::evaluate($database, $field, $criteria);
+    }
+
+    /**
+     * DVARP.
+     *
+     * Calculates the variance of a population based on the entire population by using the numbers
+     * in a column of a list or database that match conditions that you specify.
+     *
+     * Excel Function:
+     *        DVARP(database,field,criteria)
+     *
+     * @Deprecated 1.17.0
+     *
+     * @see Database\DVarP::evaluate()
+     *      Use the evaluate() method in the Database\DVarP class instead
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return float|string (string if result is an error)
+     */
+    public static function DVARP($database, $field, $criteria)
+    {
+        return Database\DVarP::evaluate($database, $field, $criteria);
+    }
+}

+ 45 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DAverage.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages;
+
+class DAverage extends DatabaseAbstract
+{
+    /**
+     * DAVERAGE.
+     *
+     * Averages the values in a column of a list or database that match conditions you specify.
+     *
+     * Excel Function:
+     *        DAVERAGE(database,field,criteria)
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                          A database is a list of related data in which rows of related
+     *                              information are records, and columns of data are fields. The
+     *                              first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                              column label enclosed between double quotation marks, such as
+     *                              "Age" or "Yield," or a number (without quotation marks) that
+     *                              represents the position of the column within the list: 1 for
+     *                              the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                          You can use any range for the criteria argument, as long as it
+     *                              includes at least one column label and at least one cell below
+     *                              the column label in which you specify a condition for the
+     *                              column.
+     *
+     * @return null|float|string
+     */
+    public static function evaluate($database, $field, $criteria)
+    {
+        $field = self::fieldExtract($database, $field);
+        if ($field === null) {
+            return null;
+        }
+
+        return Averages::average(
+            self::getFilteredColumn($database, $field, $criteria)
+        );
+    }
+}

+ 43 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCount.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Counts;
+
+class DCount extends DatabaseAbstract
+{
+    /**
+     * DCOUNT.
+     *
+     * Counts the cells that contain numbers in a column of a list or database that match conditions
+     * that you specify.
+     *
+     * Excel Function:
+     *        DCOUNT(database,[field],criteria)
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param null|int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return int
+     */
+    public static function evaluate($database, $field, $criteria)
+    {
+        $field = self::fieldExtract($database, $field);
+
+        return Counts::COUNT(
+            self::getFilteredColumn($database, $field, $criteria)
+        );
+    }
+}

+ 42 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DCountA.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Counts;
+
+class DCountA extends DatabaseAbstract
+{
+    /**
+     * DCOUNTA.
+     *
+     * Counts the nonblank cells in a column of a list or database that match conditions that you specify.
+     *
+     * Excel Function:
+     *        DCOUNTA(database,[field],criteria)
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return int
+     */
+    public static function evaluate($database, $field, $criteria)
+    {
+        $field = self::fieldExtract($database, $field);
+
+        return Counts::COUNTA(
+            self::getFilteredColumn($database, $field ?? 0, $criteria)
+        );
+    }
+}

+ 51 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DGet.php

@@ -0,0 +1,51 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class DGet extends DatabaseAbstract
+{
+    /**
+     * DGET.
+     *
+     * Extracts a single value from a column of a list or database that matches conditions that you
+     * specify.
+     *
+     * Excel Function:
+     *        DGET(database,field,criteria)
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return mixed
+     */
+    public static function evaluate($database, $field, $criteria)
+    {
+        $field = self::fieldExtract($database, $field);
+        if ($field === null) {
+            return null;
+        }
+
+        $columnData = self::getFilteredColumn($database, $field, $criteria);
+        if (count($columnData) > 1) {
+            return Functions::NAN();
+        }
+
+        $row = array_pop($columnData);
+
+        return array_pop($row);
+    }
+}

+ 46 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMax.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Maximum;
+
+class DMax extends DatabaseAbstract
+{
+    /**
+     * DMAX.
+     *
+     * Returns the largest number in a column of a list or database that matches conditions you that
+     * specify.
+     *
+     * Excel Function:
+     *        DMAX(database,field,criteria)
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return null|float|string
+     */
+    public static function evaluate($database, $field, $criteria)
+    {
+        $field = self::fieldExtract($database, $field);
+        if ($field === null) {
+            return null;
+        }
+
+        return Maximum::max(
+            self::getFilteredColumn($database, $field, $criteria)
+        );
+    }
+}

+ 46 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMin.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Minimum;
+
+class DMin extends DatabaseAbstract
+{
+    /**
+     * DMIN.
+     *
+     * Returns the smallest number in a column of a list or database that matches conditions you that
+     * specify.
+     *
+     * Excel Function:
+     *        DMIN(database,field,criteria)
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return null|float|string
+     */
+    public static function evaluate($database, $field, $criteria)
+    {
+        $field = self::fieldExtract($database, $field);
+        if ($field === null) {
+            return null;
+        }
+
+        return Minimum::min(
+            self::getFilteredColumn($database, $field, $criteria)
+        );
+    }
+}

+ 45 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DProduct.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
+
+use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
+
+class DProduct extends DatabaseAbstract
+{
+    /**
+     * DPRODUCT.
+     *
+     * Multiplies the values in a column of a list or database that match conditions that you specify.
+     *
+     * Excel Function:
+     *        DPRODUCT(database,field,criteria)
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return null|float|string
+     */
+    public static function evaluate($database, $field, $criteria)
+    {
+        $field = self::fieldExtract($database, $field);
+        if ($field === null) {
+            return null;
+        }
+
+        return MathTrig\Operations::product(
+            self::getFilteredColumn($database, $field, $criteria)
+        );
+    }
+}

+ 46 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDev.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Statistical\StandardDeviations;
+
+class DStDev extends DatabaseAbstract
+{
+    /**
+     * DSTDEV.
+     *
+     * Estimates the standard deviation of a population based on a sample by using the numbers in a
+     * column of a list or database that match conditions that you specify.
+     *
+     * Excel Function:
+     *        DSTDEV(database,field,criteria)
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return null|float|string
+     */
+    public static function evaluate($database, $field, $criteria)
+    {
+        $field = self::fieldExtract($database, $field);
+        if ($field === null) {
+            return null;
+        }
+
+        return StandardDeviations::STDEV(
+            self::getFilteredColumn($database, $field, $criteria)
+        );
+    }
+}

+ 46 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DStDevP.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Statistical\StandardDeviations;
+
+class DStDevP extends DatabaseAbstract
+{
+    /**
+     * DSTDEVP.
+     *
+     * Calculates the standard deviation of a population based on the entire population by using the
+     * numbers in a column of a list or database that match conditions that you specify.
+     *
+     * Excel Function:
+     *        DSTDEVP(database,field,criteria)
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return null|float|string
+     */
+    public static function evaluate($database, $field, $criteria)
+    {
+        $field = self::fieldExtract($database, $field);
+        if ($field === null) {
+            return null;
+        }
+
+        return StandardDeviations::STDEVP(
+            self::getFilteredColumn($database, $field, $criteria)
+        );
+    }
+}

+ 45 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DSum.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
+
+use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
+
+class DSum extends DatabaseAbstract
+{
+    /**
+     * DSUM.
+     *
+     * Adds the numbers in a column of a list or database that match conditions that you specify.
+     *
+     * Excel Function:
+     *        DSUM(database,field,criteria)
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return null|float|string
+     */
+    public static function evaluate($database, $field, $criteria)
+    {
+        $field = self::fieldExtract($database, $field);
+        if ($field === null) {
+            return null;
+        }
+
+        return MathTrig\Sum::sumIgnoringStrings(
+            self::getFilteredColumn($database, $field, $criteria)
+        );
+    }
+}

+ 46 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVar.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Variances;
+
+class DVar extends DatabaseAbstract
+{
+    /**
+     * DVAR.
+     *
+     * Estimates the variance of a population based on a sample by using the numbers in a column
+     * of a list or database that match conditions that you specify.
+     *
+     * Excel Function:
+     *        DVAR(database,field,criteria)
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return null|float|string (string if result is an error)
+     */
+    public static function evaluate($database, $field, $criteria)
+    {
+        $field = self::fieldExtract($database, $field);
+        if ($field === null) {
+            return null;
+        }
+
+        return Variances::VAR(
+            self::getFilteredColumn($database, $field, $criteria)
+        );
+    }
+}

+ 46 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DVarP.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Variances;
+
+class DVarP extends DatabaseAbstract
+{
+    /**
+     * DVARP.
+     *
+     * Calculates the variance of a population based on the entire population by using the numbers
+     * in a column of a list or database that match conditions that you specify.
+     *
+     * Excel Function:
+     *        DVARP(database,field,criteria)
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param int|string $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return null|float|string (string if result is an error)
+     */
+    public static function evaluate($database, $field, $criteria)
+    {
+        $field = self::fieldExtract($database, $field);
+        if ($field === null) {
+            return null;
+        }
+
+        return Variances::VARP(
+            self::getFilteredColumn($database, $field, $criteria)
+        );
+    }
+}

+ 174 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php

@@ -0,0 +1,174 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Database;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch;
+
+abstract class DatabaseAbstract
+{
+    abstract public static function evaluate($database, $field, $criteria);
+
+    /**
+     * fieldExtract.
+     *
+     * Extracts the column ID to use for the data field.
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param mixed $field Indicates which column is used in the function. Enter the
+     *                                        column label enclosed between double quotation marks, such as
+     *                                        "Age" or "Yield," or a number (without quotation marks) that
+     *                                        represents the position of the column within the list: 1 for
+     *                                        the first column, 2 for the second column, and so on.
+     */
+    protected static function fieldExtract(array $database, $field): ?int
+    {
+        $field = strtoupper(Functions::flattenSingleValue($field ?? ''));
+        if ($field === '') {
+            return null;
+        }
+
+        $fieldNames = array_map('strtoupper', array_shift($database));
+        if (is_numeric($field)) {
+            return ((int) $field) - 1;
+        }
+        $key = array_search($field, array_values($fieldNames), true);
+
+        return ($key !== false) ? (int) $key : null;
+    }
+
+    /**
+     * filter.
+     *
+     * Parses the selection criteria, extracts the database rows that match those criteria, and
+     * returns that subset of rows.
+     *
+     * @param mixed[] $database The range of cells that makes up the list or database.
+     *                                        A database is a list of related data in which rows of related
+     *                                        information are records, and columns of data are fields. The
+     *                                        first row of the list contains labels for each column.
+     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
+     *                                        You can use any range for the criteria argument, as long as it
+     *                                        includes at least one column label and at least one cell below
+     *                                        the column label in which you specify a condition for the
+     *                                        column.
+     *
+     * @return mixed[]
+     */
+    protected static function filter(array $database, array $criteria): array
+    {
+        $fieldNames = array_shift($database);
+        $criteriaNames = array_shift($criteria);
+
+        //    Convert the criteria into a set of AND/OR conditions with [:placeholders]
+        $query = self::buildQuery($criteriaNames, $criteria);
+
+        //    Loop through each row of the database
+        return self::executeQuery($database, $query, $criteriaNames, $fieldNames);
+    }
+
+    protected static function getFilteredColumn(array $database, ?int $field, array $criteria): array
+    {
+        //    reduce the database to a set of rows that match all the criteria
+        $database = self::filter($database, $criteria);
+        $defaultReturnColumnValue = ($field === null) ? 1 : null;
+
+        //    extract an array of values for the requested column
+        $columnData = [];
+        foreach ($database as $rowKey => $row) {
+            $keys = array_keys($row);
+            $key = $keys[$field] ?? null;
+            $columnKey = $key ?? 'A';
+            $columnData[$rowKey][$columnKey] = $row[$key] ?? $defaultReturnColumnValue;
+        }
+
+        return $columnData;
+    }
+
+    private static function buildQuery(array $criteriaNames, array $criteria): string
+    {
+        $baseQuery = [];
+        foreach ($criteria as $key => $criterion) {
+            foreach ($criterion as $field => $value) {
+                $criterionName = $criteriaNames[$field];
+                if ($value !== null) {
+                    $condition = self::buildCondition($value, $criterionName);
+                    $baseQuery[$key][] = $condition;
+                }
+            }
+        }
+
+        $rowQuery = array_map(
+            function ($rowValue) {
+                return (count($rowValue) > 1) ? 'AND(' . implode(',', $rowValue) . ')' : ($rowValue[0] ?? '');
+            },
+            $baseQuery
+        );
+
+        return (count($rowQuery) > 1) ? 'OR(' . implode(',', $rowQuery) . ')' : ($rowQuery[0] ?? '');
+    }
+
+    private static function buildCondition($criterion, string $criterionName): string
+    {
+        $ifCondition = Functions::ifCondition($criterion);
+
+        // Check for wildcard characters used in the condition
+        $result = preg_match('/(?<operator>[^"]*)(?<operand>".*[*?].*")/ui', $ifCondition, $matches);
+        if ($result !== 1) {
+            return "[:{$criterionName}]{$ifCondition}";
+        }
+
+        $trueFalse = ($matches['operator'] !== '<>');
+        $wildcard = WildcardMatch::wildcard($matches['operand']);
+        $condition = "WILDCARDMATCH([:{$criterionName}],{$wildcard})";
+        if ($trueFalse === false) {
+            $condition = "NOT({$condition})";
+        }
+
+        return $condition;
+    }
+
+    private static function executeQuery(array $database, string $query, array $criteria, array $fields): array
+    {
+        foreach ($database as $dataRow => $dataValues) {
+            //    Substitute actual values from the database row for our [:placeholders]
+            $conditions = $query;
+            foreach ($criteria as $criterion) {
+                $conditions = self::processCondition($criterion, $fields, $dataValues, $conditions);
+            }
+
+            //    evaluate the criteria against the row data
+            $result = Calculation::getInstance()->_calculateFormulaValue('=' . $conditions);
+
+            //    If the row failed to meet the criteria, remove it from the database
+            if ($result !== true) {
+                unset($database[$dataRow]);
+            }
+        }
+
+        return $database;
+    }
+
+    private static function processCondition(string $criterion, array $fields, array $dataValues, string $conditions)
+    {
+        $key = array_search($criterion, $fields, true);
+
+        $dataValue = 'NULL';
+        if (is_bool($dataValues[$key])) {
+            $dataValue = ($dataValues[$key]) ? 'TRUE' : 'FALSE';
+        } elseif ($dataValues[$key] !== null) {
+            $dataValue = $dataValues[$key];
+            // escape quotes if we have a string containing quotes
+            if (is_string($dataValue) && strpos($dataValue, '"') !== false) {
+                $dataValue = str_replace('"', '""', $dataValue);
+            }
+            $dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue;
+        }
+
+        return str_replace('[:' . $criterion . ']', $dataValue, $conditions);
+    }
+}

+ 915 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTime.php

@@ -0,0 +1,915 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation;
+
+use DateTimeInterface;
+
+/**
+ * @deprecated 1.18.0
+ */
+class DateTime
+{
+    /**
+     * Identify if a year is a leap year or not.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\Helpers::isLeapYear()
+     *      Use the isLeapYear method in the DateTimeExcel\Helpers class instead
+     *
+     * @param int|string $year The year to test
+     *
+     * @return bool TRUE if the year is a leap year, otherwise FALSE
+     */
+    public static function isLeapYear($year)
+    {
+        return DateTimeExcel\Helpers::isLeapYear($year);
+    }
+
+    /**
+     * getDateValue.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\Helpers::getDateValue()
+     *      Use the getDateValue method in the DateTimeExcel\Helpers class instead
+     *
+     * @param mixed $dateValue
+     *
+     * @return mixed Excel date/time serial value, or string if error
+     */
+    public static function getDateValue($dateValue)
+    {
+        try {
+            return DateTimeExcel\Helpers::getDateValue($dateValue);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+    }
+
+    /**
+     * DATETIMENOW.
+     *
+     * Returns the current date and time.
+     * The NOW function is useful when you need to display the current date and time on a worksheet or
+     * calculate a value based on the current date and time, and have that value updated each time you
+     * open the worksheet.
+     *
+     * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
+     * and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
+     *
+     * Excel Function:
+     *        NOW()
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\Current::now()
+     *      Use the now method in the DateTimeExcel\Current class instead
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     */
+    public static function DATETIMENOW()
+    {
+        return DateTimeExcel\Current::now();
+    }
+
+    /**
+     * DATENOW.
+     *
+     * Returns the current date.
+     * The NOW function is useful when you need to display the current date and time on a worksheet or
+     * calculate a value based on the current date and time, and have that value updated each time you
+     * open the worksheet.
+     *
+     * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
+     * and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
+     *
+     * Excel Function:
+     *        TODAY()
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\Current::today()
+     *      Use the today method in the DateTimeExcel\Current class instead
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     */
+    public static function DATENOW()
+    {
+        return DateTimeExcel\Current::today();
+    }
+
+    /**
+     * DATE.
+     *
+     * The DATE function returns a value that represents a particular date.
+     *
+     * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
+     * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
+     *
+     *
+     * Excel Function:
+     *        DATE(year,month,day)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\Date::fromYMD()
+     *      Use the fromYMD method in the DateTimeExcel\Date class instead
+     *
+     * PhpSpreadsheet is a lot more forgiving than MS Excel when passing non numeric values to this function.
+     * A Month name or abbreviation (English only at this point) such as 'January' or 'Jan' will still be accepted,
+     *     as will a day value with a suffix (e.g. '21st' rather than simply 21); again only English language.
+     *
+     * @param int $year The value of the year argument can include one to four digits.
+     *                                Excel interprets the year argument according to the configured
+     *                                date system: 1900 or 1904.
+     *                                If year is between 0 (zero) and 1899 (inclusive), Excel adds that
+     *                                value to 1900 to calculate the year. For example, DATE(108,1,2)
+     *                                returns January 2, 2008 (1900+108).
+     *                                If year is between 1900 and 9999 (inclusive), Excel uses that
+     *                                value as the year. For example, DATE(2008,1,2) returns January 2,
+     *                                2008.
+     *                                If year is less than 0 or is 10000 or greater, Excel returns the
+     *                                #NUM! error value.
+     * @param int $month A positive or negative integer representing the month of the year
+     *                                from 1 to 12 (January to December).
+     *                                If month is greater than 12, month adds that number of months to
+     *                                the first month in the year specified. For example, DATE(2008,14,2)
+     *                                returns the serial number representing February 2, 2009.
+     *                                If month is less than 1, month subtracts the magnitude of that
+     *                                number of months, plus 1, from the first month in the year
+     *                                specified. For example, DATE(2008,-3,2) returns the serial number
+     *                                representing September 2, 2007.
+     * @param int $day A positive or negative integer representing the day of the month
+     *                                from 1 to 31.
+     *                                If day is greater than the number of days in the month specified,
+     *                                day adds that number of days to the first day in the month. For
+     *                                example, DATE(2008,1,35) returns the serial number representing
+     *                                February 4, 2008.
+     *                                If day is less than 1, day subtracts the magnitude that number of
+     *                                days, plus one, from the first day of the month specified. For
+     *                                example, DATE(2008,1,-15) returns the serial number representing
+     *                                December 16, 2007.
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     */
+    public static function DATE($year = 0, $month = 1, $day = 1)
+    {
+        return DateTimeExcel\Date::fromYMD($year, $month, $day);
+    }
+
+    /**
+     * TIME.
+     *
+     * The TIME function returns a value that represents a particular time.
+     *
+     * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time
+     * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
+     *
+     * Excel Function:
+     *        TIME(hour,minute,second)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\Time::fromHMS()
+     *      Use the fromHMS method in the DateTimeExcel\Time class instead
+     *
+     * @param int $hour A number from 0 (zero) to 32767 representing the hour.
+     *                                    Any value greater than 23 will be divided by 24 and the remainder
+     *                                    will be treated as the hour value. For example, TIME(27,0,0) =
+     *                                    TIME(3,0,0) = .125 or 3:00 AM.
+     * @param int $minute A number from 0 to 32767 representing the minute.
+     *                                    Any value greater than 59 will be converted to hours and minutes.
+     *                                    For example, TIME(0,750,0) = TIME(12,30,0) = .520833 or 12:30 PM.
+     * @param int $second A number from 0 to 32767 representing the second.
+     *                                    Any value greater than 59 will be converted to hours, minutes,
+     *                                    and seconds. For example, TIME(0,0,2000) = TIME(0,33,22) = .023148
+     *                                    or 12:33:20 AM
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     */
+    public static function TIME($hour = 0, $minute = 0, $second = 0)
+    {
+        return DateTimeExcel\Time::fromHMS($hour, $minute, $second);
+    }
+
+    /**
+     * DATEVALUE.
+     *
+     * Returns a value that represents a particular date.
+     * Use DATEVALUE to convert a date represented by a text string to an Excel or PHP date/time stamp
+     * value.
+     *
+     * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
+     * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
+     *
+     * Excel Function:
+     *        DATEVALUE(dateValue)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\DateValue::fromString()
+     *      Use the fromString method in the DateTimeExcel\DateValue class instead
+     *
+     * @param string $dateValue Text that represents a date in a Microsoft Excel date format.
+     *                                    For example, "1/30/2008" or "30-Jan-2008" are text strings within
+     *                                    quotation marks that represent dates. Using the default date
+     *                                    system in Excel for Windows, date_text must represent a date from
+     *                                    January 1, 1900, to December 31, 9999. Using the default date
+     *                                    system in Excel for the Macintosh, date_text must represent a date
+     *                                    from January 1, 1904, to December 31, 9999. DATEVALUE returns the
+     *                                    #VALUE! error value if date_text is out of this range.
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     */
+    public static function DATEVALUE($dateValue)
+    {
+        return DateTimeExcel\DateValue::fromString($dateValue);
+    }
+
+    /**
+     * TIMEVALUE.
+     *
+     * Returns a value that represents a particular time.
+     * Use TIMEVALUE to convert a time represented by a text string to an Excel or PHP date/time stamp
+     * value.
+     *
+     * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time
+     * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
+     *
+     * Excel Function:
+     *        TIMEVALUE(timeValue)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\TimeValue::fromString()
+     *      Use the fromString method in the DateTimeExcel\TimeValue class instead
+     *
+     * @param string $timeValue A text string that represents a time in any one of the Microsoft
+     *                                    Excel time formats; for example, "6:45 PM" and "18:45" text strings
+     *                                    within quotation marks that represent time.
+     *                                    Date information in time_text is ignored.
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     */
+    public static function TIMEVALUE($timeValue)
+    {
+        return DateTimeExcel\TimeValue::fromString($timeValue);
+    }
+
+    /**
+     * DATEDIF.
+     *
+     * Excel Function:
+     *        DATEDIF(startdate, enddate, unit)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\Difference::interval()
+     *      Use the interval method in the DateTimeExcel\Difference class instead
+     *
+     * @param mixed $startDate Excel date serial value, PHP date/time stamp, PHP DateTime object
+     *                                    or a standard date string
+     * @param mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object
+     *                                    or a standard date string
+     * @param array|string $unit
+     *
+     * @return array|int|string Interval between the dates
+     */
+    public static function DATEDIF($startDate = 0, $endDate = 0, $unit = 'D')
+    {
+        return DateTimeExcel\Difference::interval($startDate, $endDate, $unit);
+    }
+
+    /**
+     * DAYS.
+     *
+     * Returns the number of days between two dates
+     *
+     * Excel Function:
+     *        DAYS(endDate, startDate)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\Days::between()
+     *      Use the between method in the DateTimeExcel\Days class instead
+     *
+     * @param array|DateTimeInterface|float|int|string $endDate Excel date serial value (float),
+     * PHP date timestamp (integer), PHP DateTime object, or a standard date string
+     * @param array|DateTimeInterface|float|int|string $startDate Excel date serial value (float),
+     * PHP date timestamp (integer), PHP DateTime object, or a standard date string
+     *
+     * @return array|int|string Number of days between start date and end date or an error
+     */
+    public static function DAYS($endDate = 0, $startDate = 0)
+    {
+        return DateTimeExcel\Days::between($endDate, $startDate);
+    }
+
+    /**
+     * DAYS360.
+     *
+     * Returns the number of days between two dates based on a 360-day year (twelve 30-day months),
+     * which is used in some accounting calculations. Use this function to help compute payments if
+     * your accounting system is based on twelve 30-day months.
+     *
+     * Excel Function:
+     *        DAYS360(startDate,endDate[,method])
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\Days360::between()
+     *      Use the between method in the DateTimeExcel\Days360 class instead
+     *
+     * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
+     *                                        PHP DateTime object, or a standard date string
+     * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
+     *                                        PHP DateTime object, or a standard date string
+     * @param array|bool $method US or European Method
+     *                                        FALSE or omitted: U.S. (NASD) method. If the starting date is
+     *                                        the last day of a month, it becomes equal to the 30th of the
+     *                                        same month. If the ending date is the last day of a month and
+     *                                        the starting date is earlier than the 30th of a month, the
+     *                                        ending date becomes equal to the 1st of the next month;
+     *                                        otherwise the ending date becomes equal to the 30th of the
+     *                                        same month.
+     *                                        TRUE: European method. Starting dates and ending dates that
+     *                                        occur on the 31st of a month become equal to the 30th of the
+     *                                        same month.
+     *
+     * @return array|int|string Number of days between start date and end date
+     */
+    public static function DAYS360($startDate = 0, $endDate = 0, $method = false)
+    {
+        return DateTimeExcel\Days360::between($startDate, $endDate, $method);
+    }
+
+    /**
+     * YEARFRAC.
+     *
+     * Calculates the fraction of the year represented by the number of whole days between two dates
+     * (the start_date and the end_date).
+     * Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or
+     * obligations to assign to a specific term.
+     *
+     * Excel Function:
+     *        YEARFRAC(startDate,endDate[,method])
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\YearFrac::fraction()
+     *      Use the fraction method in the DateTimeExcel\YearFrac class instead
+     *
+     * See https://lists.oasis-open.org/archives/office-formula/200806/msg00039.html
+     *     for description of algorithm used in Excel
+     *
+     * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     * @param array|int $method Method used for the calculation
+     *                                        0 or omitted    US (NASD) 30/360
+     *                                        1                Actual/actual
+     *                                        2                Actual/360
+     *                                        3                Actual/365
+     *                                        4                European 30/360
+     *
+     * @return array|float|string fraction of the year, or a string containing an error
+     */
+    public static function YEARFRAC($startDate = 0, $endDate = 0, $method = 0)
+    {
+        return DateTimeExcel\YearFrac::fraction($startDate, $endDate, $method);
+    }
+
+    /**
+     * NETWORKDAYS.
+     *
+     * Returns the number of whole working days between start_date and end_date. Working days
+     * exclude weekends and any dates identified in holidays.
+     * Use NETWORKDAYS to calculate employee benefits that accrue based on the number of days
+     * worked during a specific term.
+     *
+     * Excel Function:
+     *        NETWORKDAYS(startDate,endDate[,holidays[,holiday[,...]]])
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\NetworkDays::count()
+     *      Use the count method in the DateTimeExcel\NetworkDays class instead
+     *
+     * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
+     *                                            PHP DateTime object, or a standard date string
+     * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
+     *                                            PHP DateTime object, or a standard date string
+     * @param mixed $dateArgs
+     *
+     * @return array|int|string Interval between the dates
+     */
+    public static function NETWORKDAYS($startDate, $endDate, ...$dateArgs)
+    {
+        return DateTimeExcel\NetworkDays::count($startDate, $endDate, ...$dateArgs);
+    }
+
+    /**
+     * WORKDAY.
+     *
+     * Returns the date that is the indicated number of working days before or after a date (the
+     * starting date). Working days exclude weekends and any dates identified as holidays.
+     * Use WORKDAY to exclude weekends or holidays when you calculate invoice due dates, expected
+     * delivery times, or the number of days of work performed.
+     *
+     * Excel Function:
+     *        WORKDAY(startDate,endDays[,holidays[,holiday[,...]]])
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\WorkDay::date()
+     *      Use the date method in the DateTimeExcel\WorkDay class instead
+     *
+     * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
+     *                                        PHP DateTime object, or a standard date string
+     * @param int $endDays The number of nonweekend and nonholiday days before or after
+     *                                        startDate. A positive value for days yields a future date; a
+     *                                        negative value yields a past date.
+     * @param mixed $dateArgs
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     */
+    public static function WORKDAY($startDate, $endDays, ...$dateArgs)
+    {
+        return DateTimeExcel\WorkDay::date($startDate, $endDays, ...$dateArgs);
+    }
+
+    /**
+     * DAYOFMONTH.
+     *
+     * Returns the day of the month, for a specified date. The day is given as an integer
+     * ranging from 1 to 31.
+     *
+     * Excel Function:
+     *        DAY(dateValue)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\DateParts::day()
+     *      Use the day method in the DateTimeExcel\DateParts class instead
+     *
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     *
+     * @return array|int|string Day of the month
+     */
+    public static function DAYOFMONTH($dateValue = 1)
+    {
+        return DateTimeExcel\DateParts::day($dateValue);
+    }
+
+    /**
+     * WEEKDAY.
+     *
+     * Returns the day of the week for a specified date. The day is given as an integer
+     * ranging from 0 to 7 (dependent on the requested style).
+     *
+     * Excel Function:
+     *        WEEKDAY(dateValue[,style])
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\Week::day()
+     *      Use the day method in the DateTimeExcel\Week class instead
+     *
+     * @param float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     * @param int $style A number that determines the type of return value
+     *                                        1 or omitted    Numbers 1 (Sunday) through 7 (Saturday).
+     *                                        2                Numbers 1 (Monday) through 7 (Sunday).
+     *                                        3                Numbers 0 (Monday) through 6 (Sunday).
+     *
+     * @return array|int|string Day of the week value
+     */
+    public static function WEEKDAY($dateValue = 1, $style = 1)
+    {
+        return DateTimeExcel\Week::day($dateValue, $style);
+    }
+
+    /**
+     * STARTWEEK_SUNDAY.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\STARTWEEK_SUNDAY instead
+     */
+    const STARTWEEK_SUNDAY = 1;
+
+    /**
+     * STARTWEEK_MONDAY.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\STARTWEEK_MONDAY instead
+     */
+    const STARTWEEK_MONDAY = 2;
+
+    /**
+     * STARTWEEK_MONDAY_ALT.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\STARTWEEK_MONDAY_ALT instead
+     */
+    const STARTWEEK_MONDAY_ALT = 11;
+
+    /**
+     * STARTWEEK_TUESDAY.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\STARTWEEK_TUESDAY instead
+     */
+    const STARTWEEK_TUESDAY = 12;
+
+    /**
+     * STARTWEEK_WEDNESDAY.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\STARTWEEK_WEDNESDAY instead
+     */
+    const STARTWEEK_WEDNESDAY = 13;
+
+    /**
+     * STARTWEEK_THURSDAY.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\STARTWEEK_THURSDAY instead
+     */
+    const STARTWEEK_THURSDAY = 14;
+
+    /**
+     * STARTWEEK_FRIDAY.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\STARTWEEK_FRIDAY instead
+     */
+    const STARTWEEK_FRIDAY = 15;
+
+    /**
+     * STARTWEEK_SATURDAY.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\STARTWEEK_SATURDAY instead
+     */
+    const STARTWEEK_SATURDAY = 16;
+
+    /**
+     * STARTWEEK_SUNDAY_ALT.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\STARTWEEK_SUNDAY_ALT instead
+     */
+    const STARTWEEK_SUNDAY_ALT = 17;
+
+    /**
+     * DOW_SUNDAY.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\DOW_SUNDAY instead
+     */
+    const DOW_SUNDAY = 1;
+
+    /**
+     * DOW_MONDAY.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\DOW_MONDAY instead
+     */
+    const DOW_MONDAY = 2;
+
+    /**
+     * DOW_TUESDAY.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\DOW_TUESDAY instead
+     */
+    const DOW_TUESDAY = 3;
+
+    /**
+     * DOW_WEDNESDAY.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\DOW_WEDNESDAY instead
+     */
+    const DOW_WEDNESDAY = 4;
+
+    /**
+     * DOW_THURSDAY.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\DOW_THURSDAY instead
+     */
+    const DOW_THURSDAY = 5;
+
+    /**
+     * DOW_FRIDAY.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\DOW_FRIDAY instead
+     */
+    const DOW_FRIDAY = 6;
+
+    /**
+     * DOW_SATURDAY.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\DOW_SATURDAY instead
+     */
+    const DOW_SATURDAY = 7;
+
+    /**
+     * STARTWEEK_MONDAY_ISO.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\STARTWEEK_MONDAY_ISO instead
+     */
+    const STARTWEEK_MONDAY_ISO = 21;
+
+    /**
+     * METHODARR.
+     *
+     * @Deprecated 1.18.0
+     *
+     * @see Use DateTimeExcel\Constants\METHODARR instead
+     */
+    const METHODARR = [
+        self::STARTWEEK_SUNDAY => self::DOW_SUNDAY,
+        self::DOW_MONDAY,
+        self::STARTWEEK_MONDAY_ALT => self::DOW_MONDAY,
+        self::DOW_TUESDAY,
+        self::DOW_WEDNESDAY,
+        self::DOW_THURSDAY,
+        self::DOW_FRIDAY,
+        self::DOW_SATURDAY,
+        self::DOW_SUNDAY,
+        self::STARTWEEK_MONDAY_ISO => self::STARTWEEK_MONDAY_ISO,
+    ];
+
+    /**
+     * WEEKNUM.
+     *
+     * Returns the week of the year for a specified date.
+     * The WEEKNUM function considers the week containing January 1 to be the first week of the year.
+     * However, there is a European standard that defines the first week as the one with the majority
+     * of days (four or more) falling in the new year. This means that for years in which there are
+     * three days or less in the first week of January, the WEEKNUM function returns week numbers
+     * that are incorrect according to the European standard.
+     *
+     * Excel Function:
+     *        WEEKNUM(dateValue[,style])
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\Week::number(()
+     *      Use the number method in the DateTimeExcel\Week class instead
+     *
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     * @param int $method Week begins on Sunday or Monday
+     *                                        1 or omitted    Week begins on Sunday.
+     *                                        2                Week begins on Monday.
+     *                                        11               Week begins on Monday.
+     *                                        12               Week begins on Tuesday.
+     *                                        13               Week begins on Wednesday.
+     *                                        14               Week begins on Thursday.
+     *                                        15               Week begins on Friday.
+     *                                        16               Week begins on Saturday.
+     *                                        17               Week begins on Sunday.
+     *                                        21               ISO (Jan. 4 is week 1, begins on Monday).
+     *
+     * @return array|int|string Week Number
+     */
+    public static function WEEKNUM($dateValue = 1, $method = self::STARTWEEK_SUNDAY)
+    {
+        return DateTimeExcel\Week::number($dateValue, $method);
+    }
+
+    /**
+     * ISOWEEKNUM.
+     *
+     * Returns the ISO 8601 week number of the year for a specified date.
+     *
+     * Excel Function:
+     *        ISOWEEKNUM(dateValue)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\Week::isoWeekNumber()
+     *      Use the isoWeekNumber method in the DateTimeExcel\Week class instead
+     *
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     *
+     * @return array|int|string Week Number
+     */
+    public static function ISOWEEKNUM($dateValue = 1)
+    {
+        return DateTimeExcel\Week::isoWeekNumber($dateValue);
+    }
+
+    /**
+     * MONTHOFYEAR.
+     *
+     * Returns the month of a date represented by a serial number.
+     * The month is given as an integer, ranging from 1 (January) to 12 (December).
+     *
+     * Excel Function:
+     *        MONTH(dateValue)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\DateParts::month()
+     *      Use the month method in the DateTimeExcel\DateParts class instead
+     *
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     *
+     * @return array|int|string Month of the year
+     */
+    public static function MONTHOFYEAR($dateValue = 1)
+    {
+        return DateTimeExcel\DateParts::month($dateValue);
+    }
+
+    /**
+     * YEAR.
+     *
+     * Returns the year corresponding to a date.
+     * The year is returned as an integer in the range 1900-9999.
+     *
+     * Excel Function:
+     *        YEAR(dateValue)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\DateParts::year()
+     *      Use the ear method in the DateTimeExcel\DateParts class instead
+     *
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     *
+     * @return array|int|string Year
+     */
+    public static function YEAR($dateValue = 1)
+    {
+        return DateTimeExcel\DateParts::year($dateValue);
+    }
+
+    /**
+     * HOUROFDAY.
+     *
+     * Returns the hour of a time value.
+     * The hour is given as an integer, ranging from 0 (12:00 A.M.) to 23 (11:00 P.M.).
+     *
+     * Excel Function:
+     *        HOUR(timeValue)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\TimeParts::hour()
+     *      Use the hour method in the DateTimeExcel\TimeParts class instead
+     *
+     * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard time string
+     *
+     * @return array|int|string Hour
+     */
+    public static function HOUROFDAY($timeValue = 0)
+    {
+        return DateTimeExcel\TimeParts::hour($timeValue);
+    }
+
+    /**
+     * MINUTE.
+     *
+     * Returns the minutes of a time value.
+     * The minute is given as an integer, ranging from 0 to 59.
+     *
+     * Excel Function:
+     *        MINUTE(timeValue)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\TimeParts::minute()
+     *      Use the minute method in the DateTimeExcel\TimeParts class instead
+     *
+     * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard time string
+     *
+     * @return array|int|string Minute
+     */
+    public static function MINUTE($timeValue = 0)
+    {
+        return DateTimeExcel\TimeParts::minute($timeValue);
+    }
+
+    /**
+     * SECOND.
+     *
+     * Returns the seconds of a time value.
+     * The second is given as an integer in the range 0 (zero) to 59.
+     *
+     * Excel Function:
+     *        SECOND(timeValue)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\TimeParts::second()
+     *      Use the second method in the DateTimeExcel\TimeParts class instead
+     *
+     * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard time string
+     *
+     * @return array|int|string Second
+     */
+    public static function SECOND($timeValue = 0)
+    {
+        return DateTimeExcel\TimeParts::second($timeValue);
+    }
+
+    /**
+     * EDATE.
+     *
+     * Returns the serial number that represents the date that is the indicated number of months
+     * before or after a specified date (the start_date).
+     * Use EDATE to calculate maturity dates or due dates that fall on the same day of the month
+     * as the date of issue.
+     *
+     * Excel Function:
+     *        EDATE(dateValue,adjustmentMonths)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\Month::adjust()
+     *      Use the adjust method in the DateTimeExcel\Edate class instead
+     *
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                        PHP DateTime object, or a standard date string
+     * @param int $adjustmentMonths The number of months before or after start_date.
+     *                                        A positive value for months yields a future date;
+     *                                        a negative value yields a past date.
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     */
+    public static function EDATE($dateValue = 1, $adjustmentMonths = 0)
+    {
+        return DateTimeExcel\Month::adjust($dateValue, $adjustmentMonths);
+    }
+
+    /**
+     * EOMONTH.
+     *
+     * Returns the date value for the last day of the month that is the indicated number of months
+     * before or after start_date.
+     * Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month.
+     *
+     * Excel Function:
+     *        EOMONTH(dateValue,adjustmentMonths)
+     *
+     * @Deprecated 1.18.0
+     *
+     * @See DateTimeExcel\Month::lastDay()
+     *      Use the lastDay method in the DateTimeExcel\EoMonth class instead
+     *
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                        PHP DateTime object, or a standard date string
+     * @param int $adjustmentMonths The number of months before or after start_date.
+     *                                        A positive value for months yields a future date;
+     *                                        a negative value yields a past date.
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     */
+    public static function EOMONTH($dateValue = 1, $adjustmentMonths = 0)
+    {
+        return DateTimeExcel\Month::lastDay($dateValue, $adjustmentMonths);
+    }
+}

+ 38 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+class Constants
+{
+    // Constants currently used by WeekNum; will eventually be used by WEEKDAY
+    const STARTWEEK_SUNDAY = 1;
+    const STARTWEEK_MONDAY = 2;
+    const STARTWEEK_MONDAY_ALT = 11;
+    const STARTWEEK_TUESDAY = 12;
+    const STARTWEEK_WEDNESDAY = 13;
+    const STARTWEEK_THURSDAY = 14;
+    const STARTWEEK_FRIDAY = 15;
+    const STARTWEEK_SATURDAY = 16;
+    const STARTWEEK_SUNDAY_ALT = 17;
+    const DOW_SUNDAY = 1;
+    const DOW_MONDAY = 2;
+    const DOW_TUESDAY = 3;
+    const DOW_WEDNESDAY = 4;
+    const DOW_THURSDAY = 5;
+    const DOW_FRIDAY = 6;
+    const DOW_SATURDAY = 7;
+    const STARTWEEK_MONDAY_ISO = 21;
+
+    const METHODARR = [
+        self::STARTWEEK_SUNDAY => self::DOW_SUNDAY,
+        self::DOW_MONDAY,
+        self::STARTWEEK_MONDAY_ALT => self::DOW_MONDAY,
+        self::DOW_TUESDAY,
+        self::DOW_WEDNESDAY,
+        self::DOW_THURSDAY,
+        self::DOW_FRIDAY,
+        self::DOW_SATURDAY,
+        self::DOW_SUNDAY,
+        self::STARTWEEK_MONDAY_ISO => self::STARTWEEK_MONDAY_ISO,
+    ];
+}

+ 59 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use DateTimeImmutable;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class Current
+{
+    /**
+     * DATENOW.
+     *
+     * Returns the current date.
+     * The NOW function is useful when you need to display the current date and time on a worksheet or
+     * calculate a value based on the current date and time, and have that value updated each time you
+     * open the worksheet.
+     *
+     * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
+     * and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
+     *
+     * Excel Function:
+     *        TODAY()
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     */
+    public static function today()
+    {
+        $dti = new DateTimeImmutable();
+        $dateArray = Helpers::dateParse($dti->format('c'));
+
+        return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray, true) : Functions::VALUE();
+    }
+
+    /**
+     * DATETIMENOW.
+     *
+     * Returns the current date and time.
+     * The NOW function is useful when you need to display the current date and time on a worksheet or
+     * calculate a value based on the current date and time, and have that value updated each time you
+     * open the worksheet.
+     *
+     * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
+     * and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
+     *
+     * Excel Function:
+     *        NOW()
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     */
+    public static function now()
+    {
+        $dti = new DateTimeImmutable();
+        $dateArray = Helpers::dateParse($dti->format('c'));
+
+        return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray) : Functions::VALUE();
+    }
+}

+ 172 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php

@@ -0,0 +1,172 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
+
+class Date
+{
+    use ArrayEnabled;
+
+    /**
+     * DATE.
+     *
+     * The DATE function returns a value that represents a particular date.
+     *
+     * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
+     * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
+     *
+     * Excel Function:
+     *        DATE(year,month,day)
+     *
+     * PhpSpreadsheet is a lot more forgiving than MS Excel when passing non numeric values to this function.
+     * A Month name or abbreviation (English only at this point) such as 'January' or 'Jan' will still be accepted,
+     *     as will a day value with a suffix (e.g. '21st' rather than simply 21); again only English language.
+     *
+     * @param array|int $year The value of the year argument can include one to four digits.
+     *                                Excel interprets the year argument according to the configured
+     *                                date system: 1900 or 1904.
+     *                                If year is between 0 (zero) and 1899 (inclusive), Excel adds that
+     *                                value to 1900 to calculate the year. For example, DATE(108,1,2)
+     *                                returns January 2, 2008 (1900+108).
+     *                                If year is between 1900 and 9999 (inclusive), Excel uses that
+     *                                value as the year. For example, DATE(2008,1,2) returns January 2,
+     *                                2008.
+     *                                If year is less than 0 or is 10000 or greater, Excel returns the
+     *                                #NUM! error value.
+     * @param array|int $month A positive or negative integer representing the month of the year
+     *                                from 1 to 12 (January to December).
+     *                                If month is greater than 12, month adds that number of months to
+     *                                the first month in the year specified. For example, DATE(2008,14,2)
+     *                                returns the serial number representing February 2, 2009.
+     *                                If month is less than 1, month subtracts the magnitude of that
+     *                                number of months, plus 1, from the first month in the year
+     *                                specified. For example, DATE(2008,-3,2) returns the serial number
+     *                                representing September 2, 2007.
+     * @param array|int $day A positive or negative integer representing the day of the month
+     *                                from 1 to 31.
+     *                                If day is greater than the number of days in the month specified,
+     *                                day adds that number of days to the first day in the month. For
+     *                                example, DATE(2008,1,35) returns the serial number representing
+     *                                February 4, 2008.
+     *                                If day is less than 1, day subtracts the magnitude that number of
+     *                                days, plus one, from the first day of the month specified. For
+     *                                example, DATE(2008,1,-15) returns the serial number representing
+     *                                December 16, 2007.
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     *         If an array of numbers is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function fromYMD($year, $month, $day)
+    {
+        if (is_array($year) || is_array($month) || is_array($day)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $year, $month, $day);
+        }
+
+        $baseYear = SharedDateHelper::getExcelCalendar();
+
+        try {
+            $year = self::getYear($year, $baseYear);
+            $month = self::getMonth($month);
+            $day = self::getDay($day);
+            self::adjustYearMonth($year, $month, $baseYear);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Execute function
+        $excelDateValue = SharedDateHelper::formattedPHPToExcel($year, $month, $day);
+
+        return Helpers::returnIn3FormatsFloat($excelDateValue);
+    }
+
+    /**
+     * Convert year from multiple formats to int.
+     *
+     * @param mixed $year
+     */
+    private static function getYear($year, int $baseYear): int
+    {
+        $year = ($year !== null) ? StringHelper::testStringAsNumeric((string) $year) : 0;
+        if (!is_numeric($year)) {
+            throw new Exception(Functions::VALUE());
+        }
+        $year = (int) $year;
+
+        if ($year < ($baseYear - 1900)) {
+            throw new Exception(Functions::NAN());
+        }
+        if ((($baseYear - 1900) !== 0) && ($year < $baseYear) && ($year >= 1900)) {
+            throw new Exception(Functions::NAN());
+        }
+
+        if (($year < $baseYear) && ($year >= ($baseYear - 1900))) {
+            $year += 1900;
+        }
+
+        return (int) $year;
+    }
+
+    /**
+     * Convert month from multiple formats to int.
+     *
+     * @param mixed $month
+     */
+    private static function getMonth($month): int
+    {
+        if (($month !== null) && (!is_numeric($month))) {
+            $month = SharedDateHelper::monthStringToNumber($month);
+        }
+
+        $month = ($month !== null) ? StringHelper::testStringAsNumeric((string) $month) : 0;
+        if (!is_numeric($month)) {
+            throw new Exception(Functions::VALUE());
+        }
+
+        return (int) $month;
+    }
+
+    /**
+     * Convert day from multiple formats to int.
+     *
+     * @param mixed $day
+     */
+    private static function getDay($day): int
+    {
+        if (($day !== null) && (!is_numeric($day))) {
+            $day = SharedDateHelper::dayStringToNumber($day);
+        }
+
+        $day = ($day !== null) ? StringHelper::testStringAsNumeric((string) $day) : 0;
+        if (!is_numeric($day)) {
+            throw new Exception(Functions::VALUE());
+        }
+
+        return (int) $day;
+    }
+
+    private static function adjustYearMonth(int &$year, int &$month, int $baseYear): void
+    {
+        if ($month < 1) {
+            //    Handle year/month adjustment if month < 1
+            --$month;
+            $year += ceil($month / 12) - 1;
+            $month = 13 - abs($month % 12);
+        } elseif ($month > 12) {
+            //    Handle year/month adjustment if month > 12
+            $year += floor($month / 12);
+            $month = ($month % 12);
+        }
+
+        // Re-validate the year parameter after adjustments
+        if (($year < $baseYear) || ($year >= 10000)) {
+            throw new Exception(Functions::NAN());
+        }
+    }
+}

+ 151 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php

@@ -0,0 +1,151 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
+
+class DateParts
+{
+    use ArrayEnabled;
+
+    /**
+     * DAYOFMONTH.
+     *
+     * Returns the day of the month, for a specified date. The day is given as an integer
+     * ranging from 1 to 31.
+     *
+     * Excel Function:
+     *        DAY(dateValue)
+     *
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     *
+     * @return array|int|string Day of the month
+     *         If an array of numbers is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function day($dateValue)
+    {
+        if (is_array($dateValue)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
+        }
+
+        $weirdResult = self::weirdCondition($dateValue);
+        if ($weirdResult >= 0) {
+            return $weirdResult;
+        }
+
+        try {
+            $dateValue = Helpers::getDateValue($dateValue);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Execute function
+        $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
+
+        return (int) $PHPDateObject->format('j');
+    }
+
+    /**
+     * MONTHOFYEAR.
+     *
+     * Returns the month of a date represented by a serial number.
+     * The month is given as an integer, ranging from 1 (January) to 12 (December).
+     *
+     * Excel Function:
+     *        MONTH(dateValue)
+     *
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     *
+     * @return array|int|string Month of the year
+     *         If an array of numbers is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function month($dateValue)
+    {
+        if (is_array($dateValue)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
+        }
+
+        try {
+            $dateValue = Helpers::getDateValue($dateValue);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+        if ($dateValue < 1 && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900) {
+            return 1;
+        }
+
+        // Execute function
+        $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
+
+        return (int) $PHPDateObject->format('n');
+    }
+
+    /**
+     * YEAR.
+     *
+     * Returns the year corresponding to a date.
+     * The year is returned as an integer in the range 1900-9999.
+     *
+     * Excel Function:
+     *        YEAR(dateValue)
+     *
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     *
+     * @return array|int|string Year
+     *         If an array of numbers is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function year($dateValue)
+    {
+        if (is_array($dateValue)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
+        }
+
+        try {
+            $dateValue = Helpers::getDateValue($dateValue);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        if ($dateValue < 1 && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900) {
+            return 1900;
+        }
+        // Execute function
+        $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
+
+        return (int) $PHPDateObject->format('Y');
+    }
+
+    /**
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     */
+    private static function weirdCondition($dateValue): int
+    {
+        // Excel does not treat 0 consistently for DAY vs. (MONTH or YEAR)
+        if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900 && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) {
+            if (is_bool($dateValue)) {
+                return (int) $dateValue;
+            }
+            if ($dateValue === null) {
+                return 0;
+            }
+            if (is_numeric($dateValue) && $dateValue < 1 && $dateValue >= 0) {
+                return 0;
+            }
+        }
+
+        return -1;
+    }
+}

+ 157 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php

@@ -0,0 +1,157 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use DateTimeImmutable;
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
+
+class DateValue
+{
+    use ArrayEnabled;
+
+    /**
+     * DATEVALUE.
+     *
+     * Returns a value that represents a particular date.
+     * Use DATEVALUE to convert a date represented by a text string to an Excel or PHP date/time stamp
+     * value.
+     *
+     * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date
+     * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
+     *
+     * Excel Function:
+     *        DATEVALUE(dateValue)
+     *
+     * @param array|string $dateValue Text that represents a date in a Microsoft Excel date format.
+     *                                    For example, "1/30/2008" or "30-Jan-2008" are text strings within
+     *                                    quotation marks that represent dates. Using the default date
+     *                                    system in Excel for Windows, date_text must represent a date from
+     *                                    January 1, 1900, to December 31, 9999. Using the default date
+     *                                    system in Excel for the Macintosh, date_text must represent a date
+     *                                    from January 1, 1904, to December 31, 9999. DATEVALUE returns the
+     *                                    #VALUE! error value if date_text is out of this range.
+     *                         Or can be an array of date values
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     *         If an array of numbers is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function fromString($dateValue)
+    {
+        if (is_array($dateValue)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
+        }
+
+        $dti = new DateTimeImmutable();
+        $baseYear = SharedDateHelper::getExcelCalendar();
+        $dateValue = trim($dateValue ?? '', '"');
+        //    Strip any ordinals because they're allowed in Excel (English only)
+        $dateValue = preg_replace('/(\d)(st|nd|rd|th)([ -\/])/Ui', '$1$3', $dateValue) ?? '';
+        //    Convert separators (/ . or space) to hyphens (should also handle dot used for ordinals in some countries, e.g. Denmark, Germany)
+        $dateValue = str_replace(['/', '.', '-', '  '], ' ', $dateValue);
+
+        $yearFound = false;
+        $t1 = explode(' ', $dateValue);
+        $t = '';
+        foreach ($t1 as &$t) {
+            if ((is_numeric($t)) && ($t > 31)) {
+                if ($yearFound) {
+                    return Functions::VALUE();
+                }
+                if ($t < 100) {
+                    $t += 1900;
+                }
+                $yearFound = true;
+            }
+        }
+        if (count($t1) === 1) {
+            //    We've been fed a time value without any date
+            return ((strpos((string) $t, ':') === false)) ? Functions::Value() : 0.0;
+        }
+        unset($t);
+
+        $dateValue = self::t1ToString($t1, $dti, $yearFound);
+
+        $PHPDateArray = self::setUpArray($dateValue, $dti);
+
+        return self::finalResults($PHPDateArray, $dti, $baseYear);
+    }
+
+    private static function t1ToString(array $t1, DateTimeImmutable $dti, bool $yearFound): string
+    {
+        if (count($t1) == 2) {
+            //    We only have two parts of the date: either day/month or month/year
+            if ($yearFound) {
+                array_unshift($t1, 1);
+            } else {
+                if (is_numeric($t1[1]) && $t1[1] > 29) {
+                    $t1[1] += 1900;
+                    array_unshift($t1, 1);
+                } else {
+                    $t1[] = $dti->format('Y');
+                }
+            }
+        }
+        $dateValue = implode(' ', $t1);
+
+        return $dateValue;
+    }
+
+    /**
+     * Parse date.
+     */
+    private static function setUpArray(string $dateValue, DateTimeImmutable $dti): array
+    {
+        $PHPDateArray = Helpers::dateParse($dateValue);
+        if (!Helpers::dateParseSucceeded($PHPDateArray)) {
+            // If original count was 1, we've already returned.
+            // If it was 2, we added another.
+            // Therefore, neither of the first 2 stroks below can fail.
+            $testVal1 = strtok($dateValue, '- ');
+            $testVal2 = strtok('- ');
+            $testVal3 = strtok('- ') ?: $dti->format('Y');
+            Helpers::adjustYear((string) $testVal1, (string) $testVal2, $testVal3);
+            $PHPDateArray = Helpers::dateParse($testVal1 . '-' . $testVal2 . '-' . $testVal3);
+            if (!Helpers::dateParseSucceeded($PHPDateArray)) {
+                $PHPDateArray = Helpers::dateParse($testVal2 . '-' . $testVal1 . '-' . $testVal3);
+            }
+        }
+
+        return $PHPDateArray;
+    }
+
+    /**
+     * Final results.
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     */
+    private static function finalResults(array $PHPDateArray, DateTimeImmutable $dti, int $baseYear)
+    {
+        $retValue = Functions::Value();
+        if (Helpers::dateParseSucceeded($PHPDateArray)) {
+            // Execute function
+            Helpers::replaceIfEmpty($PHPDateArray['year'], $dti->format('Y'));
+            if ($PHPDateArray['year'] < $baseYear) {
+                return Functions::VALUE();
+            }
+            Helpers::replaceIfEmpty($PHPDateArray['month'], $dti->format('m'));
+            Helpers::replaceIfEmpty($PHPDateArray['day'], $dti->format('d'));
+            $PHPDateArray['hour'] = 0;
+            $PHPDateArray['minute'] = 0;
+            $PHPDateArray['second'] = 0;
+            $month = (int) $PHPDateArray['month'];
+            $day = (int) $PHPDateArray['day'];
+            $year = (int) $PHPDateArray['year'];
+            if (!checkdate($month, $day, $year)) {
+                return ($year === 1900 && $month === 2 && $day === 29) ? Helpers::returnIn3FormatsFloat(60.0) : Functions::VALUE();
+            }
+            $retValue = Helpers::returnIn3FormatsArray($PHPDateArray, true);
+        }
+
+        return $retValue;
+    }
+}

+ 62 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use DateTimeInterface;
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
+
+class Days
+{
+    use ArrayEnabled;
+
+    /**
+     * DAYS.
+     *
+     * Returns the number of days between two dates
+     *
+     * Excel Function:
+     *        DAYS(endDate, startDate)
+     *
+     * @param array|DateTimeInterface|float|int|string $endDate Excel date serial value (float),
+     *           PHP date timestamp (integer), PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     * @param array|DateTimeInterface|float|int|string $startDate Excel date serial value (float),
+     *           PHP date timestamp (integer), PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     *
+     * @return array|int|string Number of days between start date and end date or an error
+     *         If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
+     *            will also be an array with matching dimensions
+     */
+    public static function between($endDate, $startDate)
+    {
+        if (is_array($endDate) || is_array($startDate)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $endDate, $startDate);
+        }
+
+        try {
+            $startDate = Helpers::getDateValue($startDate);
+            $endDate = Helpers::getDateValue($endDate);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Execute function
+        $PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate);
+        $PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate);
+
+        $days = Functions::VALUE();
+        $diff = $PHPStartDateObject->diff($PHPEndDateObject);
+        if ($diff !== false && !is_bool($diff->days)) {
+            $days = $diff->days;
+            if ($diff->invert) {
+                $days = -$days;
+            }
+        }
+
+        return $days;
+    }
+}

+ 118 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php

@@ -0,0 +1,118 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
+
+class Days360
+{
+    use ArrayEnabled;
+
+    /**
+     * DAYS360.
+     *
+     * Returns the number of days between two dates based on a 360-day year (twelve 30-day months),
+     * which is used in some accounting calculations. Use this function to help compute payments if
+     * your accounting system is based on twelve 30-day months.
+     *
+     * Excel Function:
+     *        DAYS360(startDate,endDate[,method])
+     *
+     * @param array|mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
+     *                                        PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     * @param array|mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
+     *                                        PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     * @param array|mixed $method US or European Method as a bool
+     *                                        FALSE or omitted: U.S. (NASD) method. If the starting date is
+     *                                        the last day of a month, it becomes equal to the 30th of the
+     *                                        same month. If the ending date is the last day of a month and
+     *                                        the starting date is earlier than the 30th of a month, the
+     *                                        ending date becomes equal to the 1st of the next month;
+     *                                        otherwise the ending date becomes equal to the 30th of the
+     *                                        same month.
+     *                                        TRUE: European method. Starting dates and ending dates that
+     *                                        occur on the 31st of a month become equal to the 30th of the
+     *                                        same month.
+     *                         Or can be an array of methods
+     *
+     * @return array|int|string Number of days between start date and end date
+     *         If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
+     *            will also be an array with matching dimensions
+     */
+    public static function between($startDate = 0, $endDate = 0, $method = false)
+    {
+        if (is_array($startDate) || is_array($endDate) || is_array($method)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $startDate, $endDate, $method);
+        }
+
+        try {
+            $startDate = Helpers::getDateValue($startDate);
+            $endDate = Helpers::getDateValue($endDate);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        if (!is_bool($method)) {
+            return Functions::VALUE();
+        }
+
+        // Execute function
+        $PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate);
+        $startDay = $PHPStartDateObject->format('j');
+        $startMonth = $PHPStartDateObject->format('n');
+        $startYear = $PHPStartDateObject->format('Y');
+
+        $PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate);
+        $endDay = $PHPEndDateObject->format('j');
+        $endMonth = $PHPEndDateObject->format('n');
+        $endYear = $PHPEndDateObject->format('Y');
+
+        return self::dateDiff360((int) $startDay, (int) $startMonth, (int) $startYear, (int) $endDay, (int) $endMonth, (int) $endYear, !$method);
+    }
+
+    /**
+     * Return the number of days between two dates based on a 360 day calendar.
+     */
+    private static function dateDiff360(int $startDay, int $startMonth, int $startYear, int $endDay, int $endMonth, int $endYear, bool $methodUS): int
+    {
+        $startDay = self::getStartDay($startDay, $startMonth, $startYear, $methodUS);
+        $endDay = self::getEndDay($endDay, $endMonth, $endYear, $startDay, $methodUS);
+
+        return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360;
+    }
+
+    private static function getStartDay(int $startDay, int $startMonth, int $startYear, bool $methodUS): int
+    {
+        if ($startDay == 31) {
+            --$startDay;
+        } elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !Helpers::isLeapYear($startYear))))) {
+            $startDay = 30;
+        }
+
+        return $startDay;
+    }
+
+    private static function getEndDay(int $endDay, int &$endMonth, int &$endYear, int $startDay, bool $methodUS): int
+    {
+        if ($endDay == 31) {
+            if ($methodUS && $startDay != 30) {
+                $endDay = 1;
+                if ($endMonth == 12) {
+                    ++$endYear;
+                    $endMonth = 1;
+                } else {
+                    ++$endMonth;
+                }
+            } else {
+                $endDay = 30;
+            }
+        }
+
+        return $endDay;
+    }
+}

+ 158 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php

@@ -0,0 +1,158 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use DateInterval;
+use DateTime;
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
+
+class Difference
+{
+    use ArrayEnabled;
+
+    /**
+     * DATEDIF.
+     *
+     * @param mixed $startDate Excel date serial value, PHP date/time stamp, PHP DateTime object
+     *                                    or a standard date string
+     *                         Or can be an array of date values
+     * @param mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object
+     *                                    or a standard date string
+     *                         Or can be an array of date values
+     * @param array|string $unit
+     *                         Or can be an array of unit values
+     *
+     * @return array|int|string Interval between the dates
+     *         If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
+     *            will also be an array with matching dimensions
+     */
+    public static function interval($startDate, $endDate, $unit = 'D')
+    {
+        if (is_array($startDate) || is_array($endDate) || is_array($unit)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $startDate, $endDate, $unit);
+        }
+
+        try {
+            $startDate = Helpers::getDateValue($startDate);
+            $endDate = Helpers::getDateValue($endDate);
+            $difference = self::initialDiff($startDate, $endDate);
+            $unit = strtoupper($unit);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Execute function
+        $PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate);
+        $startDays = (int) $PHPStartDateObject->format('j');
+        $startMonths = (int) $PHPStartDateObject->format('n');
+        $startYears = (int) $PHPStartDateObject->format('Y');
+
+        $PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate);
+        $endDays = (int) $PHPEndDateObject->format('j');
+        $endMonths = (int) $PHPEndDateObject->format('n');
+        $endYears = (int) $PHPEndDateObject->format('Y');
+
+        $PHPDiffDateObject = $PHPEndDateObject->diff($PHPStartDateObject);
+
+        $retVal = false;
+        $retVal = self::replaceRetValue($retVal, $unit, 'D') ?? self::datedifD($difference);
+        $retVal = self::replaceRetValue($retVal, $unit, 'M') ?? self::datedifM($PHPDiffDateObject);
+        $retVal = self::replaceRetValue($retVal, $unit, 'MD') ?? self::datedifMD($startDays, $endDays, $PHPEndDateObject, $PHPDiffDateObject);
+        $retVal = self::replaceRetValue($retVal, $unit, 'Y') ?? self::datedifY($PHPDiffDateObject);
+        $retVal = self::replaceRetValue($retVal, $unit, 'YD') ?? self::datedifYD($difference, $startYears, $endYears, $PHPStartDateObject, $PHPEndDateObject);
+        $retVal = self::replaceRetValue($retVal, $unit, 'YM') ?? self::datedifYM($PHPDiffDateObject);
+
+        return is_bool($retVal) ? Functions::VALUE() : $retVal;
+    }
+
+    private static function initialDiff(float $startDate, float $endDate): float
+    {
+        // Validate parameters
+        if ($startDate > $endDate) {
+            throw new Exception(Functions::NAN());
+        }
+
+        return $endDate - $startDate;
+    }
+
+    /**
+     * Decide whether it's time to set retVal.
+     *
+     * @param bool|int $retVal
+     *
+     * @return null|bool|int
+     */
+    private static function replaceRetValue($retVal, string $unit, string $compare)
+    {
+        if ($retVal !== false || $unit !== $compare) {
+            return $retVal;
+        }
+
+        return null;
+    }
+
+    private static function datedifD(float $difference): int
+    {
+        return (int) $difference;
+    }
+
+    private static function datedifM(DateInterval $PHPDiffDateObject): int
+    {
+        return 12 * (int) $PHPDiffDateObject->format('%y') + (int) $PHPDiffDateObject->format('%m');
+    }
+
+    private static function datedifMD(int $startDays, int $endDays, DateTime $PHPEndDateObject, DateInterval $PHPDiffDateObject): int
+    {
+        if ($endDays < $startDays) {
+            $retVal = $endDays;
+            $PHPEndDateObject->modify('-' . $endDays . ' days');
+            $adjustDays = (int) $PHPEndDateObject->format('j');
+            $retVal += ($adjustDays - $startDays);
+        } else {
+            $retVal = (int) $PHPDiffDateObject->format('%d');
+        }
+
+        return $retVal;
+    }
+
+    private static function datedifY(DateInterval $PHPDiffDateObject): int
+    {
+        return (int) $PHPDiffDateObject->format('%y');
+    }
+
+    private static function datedifYD(float $difference, int $startYears, int $endYears, DateTime $PHPStartDateObject, DateTime $PHPEndDateObject): int
+    {
+        $retVal = (int) $difference;
+        if ($endYears > $startYears) {
+            $isLeapStartYear = $PHPStartDateObject->format('L');
+            $wasLeapEndYear = $PHPEndDateObject->format('L');
+
+            // Adjust end year to be as close as possible as start year
+            while ($PHPEndDateObject >= $PHPStartDateObject) {
+                $PHPEndDateObject->modify('-1 year');
+                $endYears = $PHPEndDateObject->format('Y');
+            }
+            $PHPEndDateObject->modify('+1 year');
+
+            // Get the result
+            $retVal = $PHPEndDateObject->diff($PHPStartDateObject)->days;
+
+            // Adjust for leap years cases
+            $isLeapEndYear = $PHPEndDateObject->format('L');
+            $limit = new DateTime($PHPEndDateObject->format('Y-02-29'));
+            if (!$isLeapStartYear && !$wasLeapEndYear && $isLeapEndYear && $PHPEndDateObject >= $limit) {
+                --$retVal;
+            }
+        }
+
+        return (int) $retVal;
+    }
+
+    private static function datedifYM(DateInterval $PHPDiffDateObject): int
+    {
+        return (int) $PHPDiffDateObject->format('%m');
+    }
+}

+ 306 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php

@@ -0,0 +1,306 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use DateTime;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
+
+class Helpers
+{
+    /**
+     * Identify if a year is a leap year or not.
+     *
+     * @param int|string $year The year to test
+     *
+     * @return bool TRUE if the year is a leap year, otherwise FALSE
+     */
+    public static function isLeapYear($year): bool
+    {
+        return (($year % 4) === 0) && (($year % 100) !== 0) || (($year % 400) === 0);
+    }
+
+    /**
+     * getDateValue.
+     *
+     * @param mixed $dateValue
+     *
+     * @return float Excel date/time serial value
+     */
+    public static function getDateValue($dateValue, bool $allowBool = true): float
+    {
+        if (is_object($dateValue)) {
+            $retval = SharedDateHelper::PHPToExcel($dateValue);
+            if (is_bool($retval)) {
+                throw new Exception(Functions::VALUE());
+            }
+
+            return $retval;
+        }
+
+        self::nullFalseTrueToNumber($dateValue, $allowBool);
+        if (!is_numeric($dateValue)) {
+            $saveReturnDateType = Functions::getReturnDateType();
+            Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
+            $dateValue = DateValue::fromString($dateValue);
+            Functions::setReturnDateType($saveReturnDateType);
+            if (!is_numeric($dateValue)) {
+                throw new Exception(Functions::VALUE());
+            }
+        }
+        if ($dateValue < 0 && Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) {
+            throw new Exception(Functions::NAN());
+        }
+
+        return (float) $dateValue;
+    }
+
+    /**
+     * getTimeValue.
+     *
+     * @param string $timeValue
+     *
+     * @return mixed Excel date/time serial value, or string if error
+     */
+    public static function getTimeValue($timeValue)
+    {
+        $saveReturnDateType = Functions::getReturnDateType();
+        Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
+        $timeValue = TimeValue::fromString($timeValue);
+        Functions::setReturnDateType($saveReturnDateType);
+
+        return $timeValue;
+    }
+
+    /**
+     * Adjust date by given months.
+     *
+     * @param mixed $dateValue
+     */
+    public static function adjustDateByMonths($dateValue = 0, float $adjustmentMonths = 0): DateTime
+    {
+        // Execute function
+        $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
+        $oMonth = (int) $PHPDateObject->format('m');
+        $oYear = (int) $PHPDateObject->format('Y');
+
+        $adjustmentMonthsString = (string) $adjustmentMonths;
+        if ($adjustmentMonths > 0) {
+            $adjustmentMonthsString = '+' . $adjustmentMonths;
+        }
+        if ($adjustmentMonths != 0) {
+            $PHPDateObject->modify($adjustmentMonthsString . ' months');
+        }
+        $nMonth = (int) $PHPDateObject->format('m');
+        $nYear = (int) $PHPDateObject->format('Y');
+
+        $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12);
+        if ($monthDiff != $adjustmentMonths) {
+            $adjustDays = (int) $PHPDateObject->format('d');
+            $adjustDaysString = '-' . $adjustDays . ' days';
+            $PHPDateObject->modify($adjustDaysString);
+        }
+
+        return $PHPDateObject;
+    }
+
+    /**
+     * Help reduce perceived complexity of some tests.
+     *
+     * @param mixed $value
+     * @param mixed $altValue
+     */
+    public static function replaceIfEmpty(&$value, $altValue): void
+    {
+        $value = $value ?: $altValue;
+    }
+
+    /**
+     * Adjust year in ambiguous situations.
+     */
+    public static function adjustYear(string $testVal1, string $testVal2, string &$testVal3): void
+    {
+        if (!is_numeric($testVal1) || $testVal1 < 31) {
+            if (!is_numeric($testVal2) || $testVal2 < 12) {
+                if (is_numeric($testVal3) && $testVal3 < 12) {
+                    $testVal3 += 2000;
+                }
+            }
+        }
+    }
+
+    /**
+     * Return result in one of three formats.
+     *
+     * @return mixed
+     */
+    public static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false)
+    {
+        $retType = Functions::getReturnDateType();
+        if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) {
+            return new DateTime(
+                $dateArray['year']
+                . '-' . $dateArray['month']
+                . '-' . $dateArray['day']
+                . ' ' . $dateArray['hour']
+                . ':' . $dateArray['minute']
+                . ':' . $dateArray['second']
+            );
+        }
+        $excelDateValue =
+            SharedDateHelper::formattedPHPToExcel(
+                $dateArray['year'],
+                $dateArray['month'],
+                $dateArray['day'],
+                $dateArray['hour'],
+                $dateArray['minute'],
+                $dateArray['second']
+            );
+        if ($retType === Functions::RETURNDATE_EXCEL) {
+            return $noFrac ? floor($excelDateValue) : (float) $excelDateValue;
+        }
+        // RETURNDATE_UNIX_TIMESTAMP)
+
+        return (int) SharedDateHelper::excelToTimestamp($excelDateValue);
+    }
+
+    /**
+     * Return result in one of three formats.
+     *
+     * @return mixed
+     */
+    public static function returnIn3FormatsFloat(float $excelDateValue)
+    {
+        $retType = Functions::getReturnDateType();
+        if ($retType === Functions::RETURNDATE_EXCEL) {
+            return $excelDateValue;
+        }
+        if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) {
+            return (int) SharedDateHelper::excelToTimestamp($excelDateValue);
+        }
+        // RETURNDATE_PHP_DATETIME_OBJECT
+
+        return SharedDateHelper::excelToDateTimeObject($excelDateValue);
+    }
+
+    /**
+     * Return result in one of three formats.
+     *
+     * @return mixed
+     */
+    public static function returnIn3FormatsObject(DateTime $PHPDateObject)
+    {
+        $retType = Functions::getReturnDateType();
+        if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) {
+            return $PHPDateObject;
+        }
+        if ($retType === Functions::RETURNDATE_EXCEL) {
+            return (float) SharedDateHelper::PHPToExcel($PHPDateObject);
+        }
+        // RETURNDATE_UNIX_TIMESTAMP
+        $stamp = SharedDateHelper::PHPToExcel($PHPDateObject);
+        $stamp = is_bool($stamp) ? ((int) $stamp) : $stamp;
+
+        return (int) SharedDateHelper::excelToTimestamp($stamp);
+    }
+
+    private static function baseDate(): int
+    {
+        if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
+            return 0;
+        }
+        if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904) {
+            return 0;
+        }
+
+        return 1;
+    }
+
+    /**
+     * Many functions accept null/false/true argument treated as 0/0/1.
+     *
+     * @param mixed $number
+     */
+    public static function nullFalseTrueToNumber(&$number, bool $allowBool = true): void
+    {
+        $number = Functions::flattenSingleValue($number);
+        $nullVal = self::baseDate();
+        if ($number === null) {
+            $number = $nullVal;
+        } elseif ($allowBool && is_bool($number)) {
+            $number = $nullVal + (int) $number;
+        }
+    }
+
+    /**
+     * Many functions accept null argument treated as 0.
+     *
+     * @param mixed $number
+     *
+     * @return float|int
+     */
+    public static function validateNumericNull($number)
+    {
+        $number = Functions::flattenSingleValue($number);
+        if ($number === null) {
+            return 0;
+        }
+        if (is_int($number)) {
+            return $number;
+        }
+        if (is_numeric($number)) {
+            return (float) $number;
+        }
+
+        throw new Exception(Functions::VALUE());
+    }
+
+    /**
+     * Many functions accept null/false/true argument treated as 0/0/1.
+     *
+     * @param mixed $number
+     *
+     * @return float
+     */
+    public static function validateNotNegative($number)
+    {
+        if (!is_numeric($number)) {
+            throw new Exception(Functions::VALUE());
+        }
+        if ($number >= 0) {
+            return (float) $number;
+        }
+
+        throw new Exception(Functions::NAN());
+    }
+
+    public static function silly1900(DateTime $PHPDateObject, string $mod = '-1 day'): void
+    {
+        $isoDate = $PHPDateObject->format('c');
+        if ($isoDate < '1900-03-01') {
+            $PHPDateObject->modify($mod);
+        }
+    }
+
+    public static function dateParse(string $string): array
+    {
+        return self::forceArray(date_parse($string));
+    }
+
+    public static function dateParseSucceeded(array $dateArray): bool
+    {
+        return $dateArray['error_count'] === 0;
+    }
+
+    /**
+     * Despite documentation, date_parse probably never returns false.
+     * Just in case, this routine helps guarantee it.
+     *
+     * @param array|false $dateArray
+     */
+    private static function forceArray($dateArray): array
+    {
+        return is_array($dateArray) ? $dateArray : ['error_count' => 1];
+    }
+}

+ 101 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+
+class Month
+{
+    use ArrayEnabled;
+
+    /**
+     * EDATE.
+     *
+     * Returns the serial number that represents the date that is the indicated number of months
+     * before or after a specified date (the start_date).
+     * Use EDATE to calculate maturity dates or due dates that fall on the same day of the month
+     * as the date of issue.
+     *
+     * Excel Function:
+     *        EDATE(dateValue,adjustmentMonths)
+     *
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                        PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     * @param array|int $adjustmentMonths The number of months before or after start_date.
+     *                                        A positive value for months yields a future date;
+     *                                        a negative value yields a past date.
+     *                         Or can be an array of adjustment values
+     *
+     * @return array|mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     *         If an array of values is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function adjust($dateValue, $adjustmentMonths)
+    {
+        if (is_array($dateValue) || is_array($adjustmentMonths)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $adjustmentMonths);
+        }
+
+        try {
+            $dateValue = Helpers::getDateValue($dateValue, false);
+            $adjustmentMonths = Helpers::validateNumericNull($adjustmentMonths);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+        $adjustmentMonths = floor($adjustmentMonths);
+
+        // Execute function
+        $PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths);
+
+        return Helpers::returnIn3FormatsObject($PHPDateObject);
+    }
+
+    /**
+     * EOMONTH.
+     *
+     * Returns the date value for the last day of the month that is the indicated number of months
+     * before or after start_date.
+     * Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month.
+     *
+     * Excel Function:
+     *        EOMONTH(dateValue,adjustmentMonths)
+     *
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                        PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     * @param array|int $adjustmentMonths The number of months before or after start_date.
+     *                                        A positive value for months yields a future date;
+     *                                        a negative value yields a past date.
+     *                         Or can be an array of adjustment values
+     *
+     * @return array|mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     *         If an array of values is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function lastDay($dateValue, $adjustmentMonths)
+    {
+        if (is_array($dateValue) || is_array($adjustmentMonths)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $adjustmentMonths);
+        }
+
+        try {
+            $dateValue = Helpers::getDateValue($dateValue, false);
+            $adjustmentMonths = Helpers::validateNumericNull($adjustmentMonths);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+        $adjustmentMonths = floor($adjustmentMonths);
+
+        // Execute function
+        $PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths + 1);
+        $adjustDays = (int) $PHPDateObject->format('d');
+        $adjustDaysString = '-' . $adjustDays . ' days';
+        $PHPDateObject->modify($adjustDaysString);
+
+        return Helpers::returnIn3FormatsObject($PHPDateObject);
+    }
+}

+ 119 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php

@@ -0,0 +1,119 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class NetworkDays
+{
+    use ArrayEnabled;
+
+    /**
+     * NETWORKDAYS.
+     *
+     * Returns the number of whole working days between start_date and end_date. Working days
+     * exclude weekends and any dates identified in holidays.
+     * Use NETWORKDAYS to calculate employee benefits that accrue based on the number of days
+     * worked during a specific term.
+     *
+     * Excel Function:
+     *        NETWORKDAYS(startDate,endDate[,holidays[,holiday[,...]]])
+     *
+     * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
+     *                                            PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
+     *                                            PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     * @param mixed $dateArgs An array of dates (such as holidays) to exclude from the calculation
+     *
+     * @return array|int|string Interval between the dates
+     *         If an array of values is passed for the $startDate or $endDate arguments, then the returned result
+     *            will also be an array with matching dimensions
+     */
+    public static function count($startDate, $endDate, ...$dateArgs)
+    {
+        if (is_array($startDate) || is_array($endDate)) {
+            return self::evaluateArrayArgumentsSubset(
+                [self::class, __FUNCTION__],
+                2,
+                $startDate,
+                $endDate,
+                ...$dateArgs
+            );
+        }
+
+        try {
+            //    Retrieve the mandatory start and end date that are referenced in the function definition
+            $sDate = Helpers::getDateValue($startDate);
+            $eDate = Helpers::getDateValue($endDate);
+            $startDate = min($sDate, $eDate);
+            $endDate = max($sDate, $eDate);
+            //    Get the optional days
+            $dateArgs = Functions::flattenArray($dateArgs);
+            //    Test any extra holiday parameters
+            $holidayArray = [];
+            foreach ($dateArgs as $holidayDate) {
+                $holidayArray[] = Helpers::getDateValue($holidayDate);
+            }
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Execute function
+        $startDow = self::calcStartDow($startDate);
+        $endDow = self::calcEndDow($endDate);
+        $wholeWeekDays = (int) floor(($endDate - $startDate) / 7) * 5;
+        $partWeekDays = self::calcPartWeekDays($startDow, $endDow);
+
+        //    Test any extra holiday parameters
+        $holidayCountedArray = [];
+        foreach ($holidayArray as $holidayDate) {
+            if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
+                if ((Week::day($holidayDate, 2) < 6) && (!in_array($holidayDate, $holidayCountedArray))) {
+                    --$partWeekDays;
+                    $holidayCountedArray[] = $holidayDate;
+                }
+            }
+        }
+
+        return self::applySign($wholeWeekDays + $partWeekDays, $sDate, $eDate);
+    }
+
+    private static function calcStartDow(float $startDate): int
+    {
+        $startDow = 6 - (int) Week::day($startDate, 2);
+        if ($startDow < 0) {
+            $startDow = 5;
+        }
+
+        return $startDow;
+    }
+
+    private static function calcEndDow(float $endDate): int
+    {
+        $endDow = (int) Week::day($endDate, 2);
+        if ($endDow >= 6) {
+            $endDow = 0;
+        }
+
+        return $endDow;
+    }
+
+    private static function calcPartWeekDays(int $startDow, int $endDow): int
+    {
+        $partWeekDays = $endDow + $startDow;
+        if ($partWeekDays > 5) {
+            $partWeekDays -= 5;
+        }
+
+        return $partWeekDays;
+    }
+
+    private static function applySign(int $result, float $sDate, float $eDate): int
+    {
+        return ($sDate > $eDate) ? -$result : $result;
+    }
+}

+ 129 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php

@@ -0,0 +1,129 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use DateTime;
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
+
+class Time
+{
+    use ArrayEnabled;
+
+    /**
+     * TIME.
+     *
+     * The TIME function returns a value that represents a particular time.
+     *
+     * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time
+     * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
+     *
+     * Excel Function:
+     *        TIME(hour,minute,second)
+     *
+     * @param array|int $hour A number from 0 (zero) to 32767 representing the hour.
+     *                                    Any value greater than 23 will be divided by 24 and the remainder
+     *                                    will be treated as the hour value. For example, TIME(27,0,0) =
+     *                                    TIME(3,0,0) = .125 or 3:00 AM.
+     * @param array|int $minute A number from 0 to 32767 representing the minute.
+     *                                    Any value greater than 59 will be converted to hours and minutes.
+     *                                    For example, TIME(0,750,0) = TIME(12,30,0) = .520833 or 12:30 PM.
+     * @param array|int $second A number from 0 to 32767 representing the second.
+     *                                    Any value greater than 59 will be converted to hours, minutes,
+     *                                    and seconds. For example, TIME(0,0,2000) = TIME(0,33,22) = .023148
+     *                                    or 12:33:20 AM
+     *         If an array of numbers is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     *
+     * @return array|mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     *         If an array of numbers is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function fromHMS($hour, $minute, $second)
+    {
+        if (is_array($hour) || is_array($minute) || is_array($second)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $hour, $minute, $second);
+        }
+
+        try {
+            $hour = self::toIntWithNullBool($hour);
+            $minute = self::toIntWithNullBool($minute);
+            $second = self::toIntWithNullBool($second);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        self::adjustSecond($second, $minute);
+        self::adjustMinute($minute, $hour);
+
+        if ($hour > 23) {
+            $hour = $hour % 24;
+        } elseif ($hour < 0) {
+            return Functions::NAN();
+        }
+
+        // Execute function
+        $retType = Functions::getReturnDateType();
+        if ($retType === Functions::RETURNDATE_EXCEL) {
+            $calendar = SharedDateHelper::getExcelCalendar();
+            $date = (int) ($calendar !== SharedDateHelper::CALENDAR_WINDOWS_1900);
+
+            return (float) SharedDateHelper::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second);
+        }
+        if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) {
+            return (int) SharedDateHelper::excelToTimestamp(SharedDateHelper::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; //    -2147472000 + 3600
+        }
+        // RETURNDATE_PHP_DATETIME_OBJECT
+        // Hour has already been normalized (0-23) above
+        $phpDateObject = new DateTime('1900-01-01 ' . $hour . ':' . $minute . ':' . $second);
+
+        return $phpDateObject;
+    }
+
+    private static function adjustSecond(int &$second, int &$minute): void
+    {
+        if ($second < 0) {
+            $minute += floor($second / 60);
+            $second = 60 - abs($second % 60);
+            if ($second == 60) {
+                $second = 0;
+            }
+        } elseif ($second >= 60) {
+            $minute += floor($second / 60);
+            $second = $second % 60;
+        }
+    }
+
+    private static function adjustMinute(int &$minute, int &$hour): void
+    {
+        if ($minute < 0) {
+            $hour += floor($minute / 60);
+            $minute = 60 - abs($minute % 60);
+            if ($minute == 60) {
+                $minute = 0;
+            }
+        } elseif ($minute >= 60) {
+            $hour += floor($minute / 60);
+            $minute = $minute % 60;
+        }
+    }
+
+    /**
+     * @param mixed $value expect int
+     */
+    private static function toIntWithNullBool($value): int
+    {
+        $value = $value ?? 0;
+        if (is_bool($value)) {
+            $value = (int) $value;
+        }
+        if (!is_numeric($value)) {
+            throw new Exception(Functions::VALUE());
+        }
+
+        return (int) $value;
+    }
+}

+ 132 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php

@@ -0,0 +1,132 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
+
+class TimeParts
+{
+    use ArrayEnabled;
+
+    /**
+     * HOUROFDAY.
+     *
+     * Returns the hour of a time value.
+     * The hour is given as an integer, ranging from 0 (12:00 A.M.) to 23 (11:00 P.M.).
+     *
+     * Excel Function:
+     *        HOUR(timeValue)
+     *
+     * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard time string
+     *                         Or can be an array of date/time values
+     *
+     * @return array|int|string Hour
+     *         If an array of numbers is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function hour($timeValue)
+    {
+        if (is_array($timeValue)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
+        }
+
+        try {
+            Helpers::nullFalseTrueToNumber($timeValue);
+            if (!is_numeric($timeValue)) {
+                $timeValue = Helpers::getTimeValue($timeValue);
+            }
+            Helpers::validateNotNegative($timeValue);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Execute function
+        $timeValue = fmod($timeValue, 1);
+        $timeValue = SharedDateHelper::excelToDateTimeObject($timeValue);
+
+        return (int) $timeValue->format('H');
+    }
+
+    /**
+     * MINUTE.
+     *
+     * Returns the minutes of a time value.
+     * The minute is given as an integer, ranging from 0 to 59.
+     *
+     * Excel Function:
+     *        MINUTE(timeValue)
+     *
+     * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard time string
+     *                         Or can be an array of date/time values
+     *
+     * @return array|int|string Minute
+     *         If an array of numbers is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function minute($timeValue)
+    {
+        if (is_array($timeValue)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
+        }
+
+        try {
+            Helpers::nullFalseTrueToNumber($timeValue);
+            if (!is_numeric($timeValue)) {
+                $timeValue = Helpers::getTimeValue($timeValue);
+            }
+            Helpers::validateNotNegative($timeValue);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Execute function
+        $timeValue = fmod($timeValue, 1);
+        $timeValue = SharedDateHelper::excelToDateTimeObject($timeValue);
+
+        return (int) $timeValue->format('i');
+    }
+
+    /**
+     * SECOND.
+     *
+     * Returns the seconds of a time value.
+     * The minute is given as an integer, ranging from 0 to 59.
+     *
+     * Excel Function:
+     *        SECOND(timeValue)
+     *
+     * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard time string
+     *                         Or can be an array of date/time values
+     *
+     * @return array|int|string Second
+     *         If an array of numbers is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function second($timeValue)
+    {
+        if (is_array($timeValue)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
+        }
+
+        try {
+            Helpers::nullFalseTrueToNumber($timeValue);
+            if (!is_numeric($timeValue)) {
+                $timeValue = Helpers::getTimeValue($timeValue);
+            }
+            Helpers::validateNotNegative($timeValue);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Execute function
+        $timeValue = fmod($timeValue, 1);
+        $timeValue = SharedDateHelper::excelToDateTimeObject($timeValue);
+
+        return (int) $timeValue->format('s');
+    }
+}

+ 77 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php

@@ -0,0 +1,77 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use Datetime;
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
+
+class TimeValue
+{
+    use ArrayEnabled;
+
+    /**
+     * TIMEVALUE.
+     *
+     * Returns a value that represents a particular time.
+     * Use TIMEVALUE to convert a time represented by a text string to an Excel or PHP date/time stamp
+     * value.
+     *
+     * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the time
+     * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way.
+     *
+     * Excel Function:
+     *        TIMEVALUE(timeValue)
+     *
+     * @param array|string $timeValue A text string that represents a time in any one of the Microsoft
+     *                                    Excel time formats; for example, "6:45 PM" and "18:45" text strings
+     *                                    within quotation marks that represent time.
+     *                                    Date information in time_text is ignored.
+     *                         Or can be an array of date/time values
+     *
+     * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     *         If an array of numbers is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function fromString($timeValue)
+    {
+        if (is_array($timeValue)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue);
+        }
+
+        $timeValue = trim($timeValue ?? '', '"');
+        $timeValue = str_replace(['/', '.'], '-', $timeValue);
+
+        $arraySplit = preg_split('/[\/:\-\s]/', $timeValue) ?: [];
+        if ((count($arraySplit) == 2 || count($arraySplit) == 3) && $arraySplit[0] > 24) {
+            $arraySplit[0] = ($arraySplit[0] % 24);
+            $timeValue = implode(':', $arraySplit);
+        }
+
+        $PHPDateArray = Helpers::dateParse($timeValue);
+        $retValue = Functions::VALUE();
+        if (Helpers::dateParseSucceeded($PHPDateArray)) {
+            /** @var int */
+            $hour = $PHPDateArray['hour'];
+            /** @var int */
+            $minute = $PHPDateArray['minute'];
+            /** @var int */
+            $second = $PHPDateArray['second'];
+            // OpenOffice-specific code removed - it works just like Excel
+            $excelDateValue = SharedDateHelper::formattedPHPToExcel(1900, 1, 1, $hour, $minute, $second) - 1;
+
+            $retType = Functions::getReturnDateType();
+            if ($retType === Functions::RETURNDATE_EXCEL) {
+                $retValue = (float) $excelDateValue;
+            } elseif ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) {
+                $retValue = (int) $phpDateValue = SharedDateHelper::excelToTimestamp($excelDateValue + 25569) - 3600;
+            } else {
+                $retValue = new DateTime('1900-01-01 ' . $PHPDateArray['hour'] . ':' . $PHPDateArray['minute'] . ':' . $PHPDateArray['second']);
+            }
+        }
+
+        return $retValue;
+    }
+}

+ 278 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php

@@ -0,0 +1,278 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use DateTime;
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
+
+class Week
+{
+    use ArrayEnabled;
+
+    /**
+     * WEEKNUM.
+     *
+     * Returns the week of the year for a specified date.
+     * The WEEKNUM function considers the week containing January 1 to be the first week of the year.
+     * However, there is a European standard that defines the first week as the one with the majority
+     * of days (four or more) falling in the new year. This means that for years in which there are
+     * three days or less in the first week of January, the WEEKNUM function returns week numbers
+     * that are incorrect according to the European standard.
+     *
+     * Excel Function:
+     *        WEEKNUM(dateValue[,style])
+     *
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     * @param array|int $method Week begins on Sunday or Monday
+     *                                        1 or omitted    Week begins on Sunday.
+     *                                        2                Week begins on Monday.
+     *                                        11               Week begins on Monday.
+     *                                        12               Week begins on Tuesday.
+     *                                        13               Week begins on Wednesday.
+     *                                        14               Week begins on Thursday.
+     *                                        15               Week begins on Friday.
+     *                                        16               Week begins on Saturday.
+     *                                        17               Week begins on Sunday.
+     *                                        21               ISO (Jan. 4 is week 1, begins on Monday).
+     *                         Or can be an array of methods
+     *
+     * @return array|int|string Week Number
+     *         If an array of values is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function number($dateValue, $method = Constants::STARTWEEK_SUNDAY)
+    {
+        if (is_array($dateValue) || is_array($method)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $method);
+        }
+
+        $origDateValueNull = empty($dateValue);
+
+        try {
+            $method = self::validateMethod($method);
+            if ($dateValue === null) { // boolean not allowed
+                $dateValue = (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904 || $method === Constants::DOW_SUNDAY) ? 0 : 1;
+            }
+            $dateValue = self::validateDateValue($dateValue);
+            if (!$dateValue && self::buggyWeekNum1900($method)) {
+                // This seems to be an additional Excel bug.
+                return 0;
+            }
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Execute function
+        $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
+        if ($method == Constants::STARTWEEK_MONDAY_ISO) {
+            Helpers::silly1900($PHPDateObject);
+
+            return (int) $PHPDateObject->format('W');
+        }
+        if (self::buggyWeekNum1904($method, $origDateValueNull, $PHPDateObject)) {
+            return 0;
+        }
+        Helpers::silly1900($PHPDateObject, '+ 5 years'); // 1905 calendar matches
+        $dayOfYear = (int) $PHPDateObject->format('z');
+        $PHPDateObject->modify('-' . $dayOfYear . ' days');
+        $firstDayOfFirstWeek = (int) $PHPDateObject->format('w');
+        $daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7;
+        $daysInFirstWeek += 7 * !$daysInFirstWeek;
+        $endFirstWeek = $daysInFirstWeek - 1;
+        $weekOfYear = floor(($dayOfYear - $endFirstWeek + 13) / 7);
+
+        return (int) $weekOfYear;
+    }
+
+    /**
+     * ISOWEEKNUM.
+     *
+     * Returns the ISO 8601 week number of the year for a specified date.
+     *
+     * Excel Function:
+     *        ISOWEEKNUM(dateValue)
+     *
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     *
+     * @return array|int|string Week Number
+     *         If an array of numbers is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function isoWeekNumber($dateValue)
+    {
+        if (is_array($dateValue)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue);
+        }
+
+        if (self::apparentBug($dateValue)) {
+            return 52;
+        }
+
+        try {
+            $dateValue = Helpers::getDateValue($dateValue);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Execute function
+        $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
+        Helpers::silly1900($PHPDateObject);
+
+        return (int) $PHPDateObject->format('W');
+    }
+
+    /**
+     * WEEKDAY.
+     *
+     * Returns the day of the week for a specified date. The day is given as an integer
+     * ranging from 0 to 7 (dependent on the requested style).
+     *
+     * Excel Function:
+     *        WEEKDAY(dateValue[,style])
+     *
+     * @param null|array|float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     * @param mixed $style A number that determines the type of return value
+     *                                        1 or omitted    Numbers 1 (Sunday) through 7 (Saturday).
+     *                                        2                Numbers 1 (Monday) through 7 (Sunday).
+     *                                        3                Numbers 0 (Monday) through 6 (Sunday).
+     *                         Or can be an array of styles
+     *
+     * @return array|int|string Day of the week value
+     *         If an array of values is passed as the argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function day($dateValue, $style = 1)
+    {
+        if (is_array($dateValue) || is_array($style)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $style);
+        }
+
+        try {
+            $dateValue = Helpers::getDateValue($dateValue);
+            $style = self::validateStyle($style);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Execute function
+        $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue);
+        Helpers::silly1900($PHPDateObject);
+        $DoW = (int) $PHPDateObject->format('w');
+
+        switch ($style) {
+            case 1:
+                ++$DoW;
+
+                break;
+            case 2:
+                $DoW = self::dow0Becomes7($DoW);
+
+                break;
+            case 3:
+                $DoW = self::dow0Becomes7($DoW) - 1;
+
+                break;
+        }
+
+        return $DoW;
+    }
+
+    /**
+     * @param mixed $style expect int
+     */
+    private static function validateStyle($style): int
+    {
+        if (!is_numeric($style)) {
+            throw new Exception(Functions::VALUE());
+        }
+        $style = (int) $style;
+        if (($style < 1) || ($style > 3)) {
+            throw new Exception(Functions::NAN());
+        }
+
+        return $style;
+    }
+
+    private static function dow0Becomes7(int $DoW): int
+    {
+        return ($DoW === 0) ? 7 : $DoW;
+    }
+
+    /**
+     * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     */
+    private static function apparentBug($dateValue): bool
+    {
+        if (SharedDateHelper::getExcelCalendar() !== SharedDateHelper::CALENDAR_MAC_1904) {
+            if (is_bool($dateValue)) {
+                return true;
+            }
+            if (is_numeric($dateValue) && !((int) $dateValue)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Validate dateValue parameter.
+     *
+     * @param mixed $dateValue
+     */
+    private static function validateDateValue($dateValue): float
+    {
+        if (is_bool($dateValue)) {
+            throw new Exception(Functions::VALUE());
+        }
+
+        return Helpers::getDateValue($dateValue);
+    }
+
+    /**
+     * Validate method parameter.
+     *
+     * @param mixed $method
+     */
+    private static function validateMethod($method): int
+    {
+        if ($method === null) {
+            $method = Constants::STARTWEEK_SUNDAY;
+        }
+
+        if (!is_numeric($method)) {
+            throw new Exception(Functions::VALUE());
+        }
+
+        $method = (int) $method;
+        if (!array_key_exists($method, Constants::METHODARR)) {
+            throw new Exception(Functions::NAN());
+        }
+        $method = Constants::METHODARR[$method];
+
+        return $method;
+    }
+
+    private static function buggyWeekNum1900(int $method): bool
+    {
+        return $method === Constants::DOW_SUNDAY && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900;
+    }
+
+    private static function buggyWeekNum1904(int $method, bool $origNull, DateTime $dateObject): bool
+    {
+        // This appears to be another Excel bug.
+
+        return $method === Constants::DOW_SUNDAY && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904 &&
+            !$origNull && $dateObject->format('Y-m-d') === '1904-01-01';
+    }
+}

+ 201 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php

@@ -0,0 +1,201 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class WorkDay
+{
+    use ArrayEnabled;
+
+    /**
+     * WORKDAY.
+     *
+     * Returns the date that is the indicated number of working days before or after a date (the
+     * starting date). Working days exclude weekends and any dates identified as holidays.
+     * Use WORKDAY to exclude weekends or holidays when you calculate invoice due dates, expected
+     * delivery times, or the number of days of work performed.
+     *
+     * Excel Function:
+     *        WORKDAY(startDate,endDays[,holidays[,holiday[,...]]])
+     *
+     * @param array|mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
+     *                                        PHP DateTime object, or a standard date string
+     *                         Or can be an array of date values
+     * @param array|int $endDays The number of nonweekend and nonholiday days before or after
+     *                                        startDate. A positive value for days yields a future date; a
+     *                                        negative value yields a past date.
+     *                         Or can be an array of int values
+     * @param null|mixed $dateArgs An array of dates (such as holidays) to exclude from the calculation
+     *
+     * @return array|mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+     *                        depending on the value of the ReturnDateType flag
+     *         If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
+     *            will also be an array with matching dimensions
+     */
+    public static function date($startDate, $endDays, ...$dateArgs)
+    {
+        if (is_array($startDate) || is_array($endDays)) {
+            return self::evaluateArrayArgumentsSubset(
+                [self::class, __FUNCTION__],
+                2,
+                $startDate,
+                $endDays,
+                ...$dateArgs
+            );
+        }
+
+        //    Retrieve the mandatory start date and days that are referenced in the function definition
+        try {
+            $startDate = Helpers::getDateValue($startDate);
+            $endDays = Helpers::validateNumericNull($endDays);
+            $holidayArray = array_map([Helpers::class, 'getDateValue'], Functions::flattenArray($dateArgs));
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $startDate = (float) floor($startDate);
+        $endDays = (int) floor($endDays);
+        //    If endDays is 0, we always return startDate
+        if ($endDays == 0) {
+            return $startDate;
+        }
+        if ($endDays < 0) {
+            return self::decrementing($startDate, $endDays, $holidayArray);
+        }
+
+        return self::incrementing($startDate, $endDays, $holidayArray);
+    }
+
+    /**
+     * Use incrementing logic to determine Workday.
+     *
+     * @return mixed
+     */
+    private static function incrementing(float $startDate, int $endDays, array $holidayArray)
+    {
+        //    Adjust the start date if it falls over a weekend
+        $startDoW = self::getWeekDay($startDate, 3);
+        if ($startDoW >= 5) {
+            $startDate += 7 - $startDoW;
+            --$endDays;
+        }
+
+        //    Add endDays
+        $endDate = (float) $startDate + ((int) ($endDays / 5) * 7);
+        $endDays = $endDays % 5;
+        while ($endDays > 0) {
+            ++$endDate;
+            //    Adjust the calculated end date if it falls over a weekend
+            $endDow = self::getWeekDay($endDate, 3);
+            if ($endDow >= 5) {
+                $endDate += 7 - $endDow;
+            }
+            --$endDays;
+        }
+
+        //    Test any extra holiday parameters
+        if (!empty($holidayArray)) {
+            $endDate = self::incrementingArray($startDate, $endDate, $holidayArray);
+        }
+
+        return Helpers::returnIn3FormatsFloat($endDate);
+    }
+
+    private static function incrementingArray(float $startDate, float $endDate, array $holidayArray): float
+    {
+        $holidayCountedArray = $holidayDates = [];
+        foreach ($holidayArray as $holidayDate) {
+            if (self::getWeekDay($holidayDate, 3) < 5) {
+                $holidayDates[] = $holidayDate;
+            }
+        }
+        sort($holidayDates, SORT_NUMERIC);
+        foreach ($holidayDates as $holidayDate) {
+            if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
+                if (!in_array($holidayDate, $holidayCountedArray)) {
+                    ++$endDate;
+                    $holidayCountedArray[] = $holidayDate;
+                }
+            }
+            //    Adjust the calculated end date if it falls over a weekend
+            $endDoW = self::getWeekDay($endDate, 3);
+            if ($endDoW >= 5) {
+                $endDate += 7 - $endDoW;
+            }
+        }
+
+        return $endDate;
+    }
+
+    /**
+     * Use decrementing logic to determine Workday.
+     *
+     * @return mixed
+     */
+    private static function decrementing(float $startDate, int $endDays, array $holidayArray)
+    {
+        //    Adjust the start date if it falls over a weekend
+        $startDoW = self::getWeekDay($startDate, 3);
+        if ($startDoW >= 5) {
+            $startDate += -$startDoW + 4;
+            ++$endDays;
+        }
+
+        //    Add endDays
+        $endDate = (float) $startDate + ((int) ($endDays / 5) * 7);
+        $endDays = $endDays % 5;
+        while ($endDays < 0) {
+            --$endDate;
+            //    Adjust the calculated end date if it falls over a weekend
+            $endDow = self::getWeekDay($endDate, 3);
+            if ($endDow >= 5) {
+                $endDate += 4 - $endDow;
+            }
+            ++$endDays;
+        }
+
+        //    Test any extra holiday parameters
+        if (!empty($holidayArray)) {
+            $endDate = self::decrementingArray($startDate, $endDate, $holidayArray);
+        }
+
+        return Helpers::returnIn3FormatsFloat($endDate);
+    }
+
+    private static function decrementingArray(float $startDate, float $endDate, array $holidayArray): float
+    {
+        $holidayCountedArray = $holidayDates = [];
+        foreach ($holidayArray as $holidayDate) {
+            if (self::getWeekDay($holidayDate, 3) < 5) {
+                $holidayDates[] = $holidayDate;
+            }
+        }
+        rsort($holidayDates, SORT_NUMERIC);
+        foreach ($holidayDates as $holidayDate) {
+            if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) {
+                if (!in_array($holidayDate, $holidayCountedArray)) {
+                    --$endDate;
+                    $holidayCountedArray[] = $holidayDate;
+                }
+            }
+            //    Adjust the calculated end date if it falls over a weekend
+            $endDoW = self::getWeekDay($endDate, 3);
+            /** int $endDoW */
+            if ($endDoW >= 5) {
+                $endDate += -$endDoW + 4;
+            }
+        }
+
+        return $endDate;
+    }
+
+    private static function getWeekDay(float $date, int $wd): int
+    {
+        $result = Functions::scalar(Week::day($date, $wd));
+
+        return is_int($result) ? $result : -1;
+    }
+}

+ 132 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php

@@ -0,0 +1,132 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDateHelper;
+
+class YearFrac
+{
+    use ArrayEnabled;
+
+    /**
+     * YEARFRAC.
+     *
+     * Calculates the fraction of the year represented by the number of whole days between two dates
+     * (the start_date and the end_date).
+     * Use the YEARFRAC worksheet function to identify the proportion of a whole year's benefits or
+     * obligations to assign to a specific term.
+     *
+     * Excel Function:
+     *        YEARFRAC(startDate,endDate[,method])
+     * See https://lists.oasis-open.org/archives/office-formula/200806/msg00039.html
+     *     for description of algorithm used in Excel
+     *
+     * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     *                         Or can be an array of values
+     * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer),
+     *                                    PHP DateTime object, or a standard date string
+     *                         Or can be an array of methods
+     * @param array|int $method Method used for the calculation
+     *                                        0 or omitted    US (NASD) 30/360
+     *                                        1                Actual/actual
+     *                                        2                Actual/360
+     *                                        3                Actual/365
+     *                                        4                European 30/360
+     *                         Or can be an array of methods
+     *
+     * @return array|float|string fraction of the year, or a string containing an error
+     *         If an array of values is passed for the $startDate or $endDays,arguments, then the returned result
+     *            will also be an array with matching dimensions
+     */
+    public static function fraction($startDate, $endDate, $method = 0)
+    {
+        if (is_array($startDate) || is_array($endDate) || is_array($method)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $startDate, $endDate, $method);
+        }
+
+        try {
+            $method = (int) Helpers::validateNumericNull($method);
+            $sDate = Helpers::getDateValue($startDate);
+            $eDate = Helpers::getDateValue($endDate);
+            $sDate = self::excelBug($sDate, $startDate, $endDate, $method);
+            $eDate = self::excelBug($eDate, $endDate, $startDate, $method);
+            $startDate = min($sDate, $eDate);
+            $endDate = max($sDate, $eDate);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        switch ($method) {
+            case 0:
+                return Functions::scalar(Days360::between($startDate, $endDate)) / 360;
+            case 1:
+                return self::method1($startDate, $endDate);
+            case 2:
+                return Functions::scalar(Difference::interval($startDate, $endDate)) / 360;
+            case 3:
+                return Functions::scalar(Difference::interval($startDate, $endDate)) / 365;
+            case 4:
+                return Functions::scalar(Days360::between($startDate, $endDate, true)) / 360;
+        }
+
+        return Functions::NAN();
+    }
+
+    /**
+     * Excel 1900 calendar treats date argument of null as 1900-01-00. Really.
+     *
+     * @param mixed $startDate
+     * @param mixed $endDate
+     */
+    private static function excelBug(float $sDate, $startDate, $endDate, int $method): float
+    {
+        if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE && SharedDateHelper::getExcelCalendar() !== SharedDateHelper::CALENDAR_MAC_1904) {
+            if ($endDate === null && $startDate !== null) {
+                if (DateParts::month($sDate) == 12 && DateParts::day($sDate) === 31 && $method === 0) {
+                    $sDate += 2;
+                } else {
+                    ++$sDate;
+                }
+            }
+        }
+
+        return $sDate;
+    }
+
+    private static function method1(float $startDate, float $endDate): float
+    {
+        $days = Functions::scalar(Difference::interval($startDate, $endDate));
+        $startYear = (int) DateParts::year($startDate);
+        $endYear = (int) DateParts::year($endDate);
+        $years = $endYear - $startYear + 1;
+        $startMonth = (int) DateParts::month($startDate);
+        $startDay = (int) DateParts::day($startDate);
+        $endMonth = (int) DateParts::month($endDate);
+        $endDay = (int) DateParts::day($endDate);
+        $startMonthDay = 100 * $startMonth + $startDay;
+        $endMonthDay = 100 * $endMonth + $endDay;
+        if ($years == 1) {
+            $tmpCalcAnnualBasis = 365 + (int) Helpers::isLeapYear($endYear);
+        } elseif ($years == 2 && $startMonthDay >= $endMonthDay) {
+            if (Helpers::isLeapYear($startYear)) {
+                $tmpCalcAnnualBasis = 365 + (int) ($startMonthDay <= 229);
+            } elseif (Helpers::isLeapYear($endYear)) {
+                $tmpCalcAnnualBasis = 365 + (int) ($endMonthDay >= 229);
+            } else {
+                $tmpCalcAnnualBasis = 365;
+            }
+        } else {
+            $tmpCalcAnnualBasis = 0;
+            for ($year = $startYear; $year <= $endYear; ++$year) {
+                $tmpCalcAnnualBasis += 365 + (int) Helpers::isLeapYear($year);
+            }
+            $tmpCalcAnnualBasis /= $years;
+        }
+
+        return $days / $tmpCalcAnnualBasis;
+    }
+}

+ 202 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php

@@ -0,0 +1,202 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+
+class ArrayArgumentHelper
+{
+    /**
+     * @var array
+     */
+    protected $arguments;
+
+    /**
+     * @var int
+     */
+    protected $argumentCount;
+
+    /**
+     * @var array
+     */
+    protected $rows;
+
+    /**
+     * @var array
+     */
+    protected $columns;
+
+    public function initialise(array $arguments): void
+    {
+        $this->rows = $this->rows($arguments);
+        $this->columns = $this->columns($arguments);
+
+        $this->argumentCount = count($arguments);
+        $this->arguments = $this->flattenSingleCellArrays($arguments, $this->rows, $this->columns);
+
+        $this->rows = $this->rows($arguments);
+        $this->columns = $this->columns($arguments);
+
+        if ($this->arrayArguments() > 2) {
+            throw new Exception('Formulae with more than two array arguments are not supported');
+        }
+    }
+
+    public function arguments(): array
+    {
+        return $this->arguments;
+    }
+
+    public function hasArrayArgument(): bool
+    {
+        return $this->arrayArguments() > 0;
+    }
+
+    public function getFirstArrayArgumentNumber(): int
+    {
+        $rowArrays = $this->filterArray($this->rows);
+        $columnArrays = $this->filterArray($this->columns);
+
+        for ($index = 0; $index < $this->argumentCount; ++$index) {
+            if (isset($rowArrays[$index]) || isset($columnArrays[$index])) {
+                return ++$index;
+            }
+        }
+
+        return 0;
+    }
+
+    public function getSingleRowVector(): ?int
+    {
+        $rowVectors = $this->getRowVectors();
+
+        return count($rowVectors) === 1 ? array_pop($rowVectors) : null;
+    }
+
+    private function getRowVectors(): array
+    {
+        $rowVectors = [];
+        for ($index = 0; $index < $this->argumentCount; ++$index) {
+            if ($this->rows[$index] === 1 && $this->columns[$index] > 1) {
+                $rowVectors[] = $index;
+            }
+        }
+
+        return $rowVectors;
+    }
+
+    public function getSingleColumnVector(): ?int
+    {
+        $columnVectors = $this->getColumnVectors();
+
+        return count($columnVectors) === 1 ? array_pop($columnVectors) : null;
+    }
+
+    private function getColumnVectors(): array
+    {
+        $columnVectors = [];
+        for ($index = 0; $index < $this->argumentCount; ++$index) {
+            if ($this->rows[$index] > 1 && $this->columns[$index] === 1) {
+                $columnVectors[] = $index;
+            }
+        }
+
+        return $columnVectors;
+    }
+
+    public function getMatrixPair(): array
+    {
+        for ($i = 0; $i < ($this->argumentCount - 1); ++$i) {
+            for ($j = $i + 1; $j < $this->argumentCount; ++$j) {
+                if (isset($this->rows[$i], $this->rows[$j])) {
+                    return [$i, $j];
+                }
+            }
+        }
+
+        return [];
+    }
+
+    public function isVector(int $argument): bool
+    {
+        return $this->rows[$argument] === 1 || $this->columns[$argument] === 1;
+    }
+
+    public function isRowVector(int $argument): bool
+    {
+        return $this->rows[$argument] === 1;
+    }
+
+    public function isColumnVector(int $argument): bool
+    {
+        return $this->columns[$argument] === 1;
+    }
+
+    public function rowCount(int $argument): int
+    {
+        return $this->rows[$argument];
+    }
+
+    public function columnCount(int $argument): int
+    {
+        return $this->columns[$argument];
+    }
+
+    private function rows(array $arguments): array
+    {
+        return array_map(
+            function ($argument) {
+                return is_countable($argument) ? count($argument) : 1;
+            },
+            $arguments
+        );
+    }
+
+    private function columns(array $arguments): array
+    {
+        return array_map(
+            function ($argument) {
+                return is_array($argument) && is_array($argument[array_keys($argument)[0]])
+                    ? count($argument[array_keys($argument)[0]])
+                    : 1;
+            },
+            $arguments
+        );
+    }
+
+    public function arrayArguments(): int
+    {
+        $count = 0;
+        foreach (array_keys($this->arguments) as $argument) {
+            if ($this->rows[$argument] > 1 || $this->columns[$argument] > 1) {
+                ++$count;
+            }
+        }
+
+        return $count;
+    }
+
+    private function flattenSingleCellArrays(array $arguments, array $rows, array $columns): array
+    {
+        foreach ($arguments as $index => $argument) {
+            if ($rows[$index] === 1 && $columns[$index] === 1) {
+                while (is_array($argument)) {
+                    $argument = array_pop($argument);
+                }
+                $arguments[$index] = $argument;
+            }
+        }
+
+        return $arguments;
+    }
+
+    private function filterArray(array $array): array
+    {
+        return array_filter(
+            $array,
+            function ($value) {
+                return $value > 1;
+            }
+        );
+    }
+}

+ 174 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php

@@ -0,0 +1,174 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class ArrayArgumentProcessor
+{
+    /**
+     * @var ArrayArgumentHelper
+     */
+    private static $arrayArgumentHelper;
+
+    /**
+     * @param mixed ...$arguments
+     */
+    public static function processArguments(
+        ArrayArgumentHelper $arrayArgumentHelper,
+        callable $method,
+        ...$arguments
+    ): array {
+        self::$arrayArgumentHelper = $arrayArgumentHelper;
+
+        if (self::$arrayArgumentHelper->hasArrayArgument() === false) {
+            return [$method(...$arguments)];
+        }
+
+        if (self::$arrayArgumentHelper->arrayArguments() === 1) {
+            $nthArgument = self::$arrayArgumentHelper->getFirstArrayArgumentNumber();
+
+            return self::evaluateNthArgumentAsArray($method, $nthArgument, ...$arguments);
+        }
+
+        $singleRowVectorIndex = self::$arrayArgumentHelper->getSingleRowVector();
+        $singleColumnVectorIndex = self::$arrayArgumentHelper->getSingleColumnVector();
+        if ($singleRowVectorIndex !== null && $singleColumnVectorIndex !== null) {
+            // Basic logic for a single row vector and a single column vector
+            return self::evaluateVectorPair($method, $singleRowVectorIndex, $singleColumnVectorIndex, ...$arguments);
+        }
+
+        $matrixPair = self::$arrayArgumentHelper->getMatrixPair();
+        if ($matrixPair !== []) {
+            if (
+                (self::$arrayArgumentHelper->isVector($matrixPair[0]) === true &&
+                    self::$arrayArgumentHelper->isVector($matrixPair[1]) === false) ||
+                (self::$arrayArgumentHelper->isVector($matrixPair[0]) === false &&
+                    self::$arrayArgumentHelper->isVector($matrixPair[1]) === true)
+            ) {
+                // Logic for a matrix and a vector (row or column)
+                return self::evaluateVectorMatrixPair($method, $matrixPair, ...$arguments);
+            }
+            // Logic for matrix/matrix, column vector/column vector or row vector/row vector
+            return self::evaluateMatrixPair($method, $matrixPair, ...$arguments);
+        }
+
+        // Still need to work out the logic for more than two array arguments,
+        // For the moment, we're throwing an Exception when we initialise the ArrayArgumentHelper
+        return ['#VALUE!'];
+    }
+
+    /**
+     * @param mixed ...$arguments
+     */
+    private static function evaluateVectorMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array
+    {
+        $matrix2 = array_pop($matrixIndexes);
+        /** @var array $matrixValues2 */
+        $matrixValues2 = $arguments[$matrix2];
+        $matrix1 = array_pop($matrixIndexes);
+        /** @var array $matrixValues1 */
+        $matrixValues1 = $arguments[$matrix1];
+
+        $rows = min(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
+        $columns = min(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));
+
+        if ($rows === 1) {
+            $rows = max(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2]));
+        }
+        if ($columns === 1) {
+            $columns = max(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2]));
+        }
+
+        $result = [];
+        for ($rowIndex = 0; $rowIndex < $rows; ++$rowIndex) {
+            for ($columnIndex = 0; $columnIndex < $columns; ++$columnIndex) {
+                $rowIndex1 = self::$arrayArgumentHelper->isRowVector($matrix1) ? 0 : $rowIndex;
+                $columnIndex1 = self::$arrayArgumentHelper->isColumnVector($matrix1) ? 0 : $columnIndex;
+                $value1 = $matrixValues1[$rowIndex1][$columnIndex1];
+                $rowIndex2 = self::$arrayArgumentHelper->isRowVector($matrix2) ? 0 : $rowIndex;
+                $columnIndex2 = self::$arrayArgumentHelper->isColumnVector($matrix2) ? 0 : $columnIndex;
+                $value2 = $matrixValues2[$rowIndex2][$columnIndex2];
+                $arguments[$matrix1] = $value1;
+                $arguments[$matrix2] = $value2;
+
+                $result[$rowIndex][$columnIndex] = $method(...$arguments);
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param mixed ...$arguments
+     */
+    private static function evaluateMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array
+    {
+        $matrix2 = array_pop($matrixIndexes);
+        /** @var array $matrixValues2 */
+        $matrixValues2 = $arguments[$matrix2];
+        $matrix1 = array_pop($matrixIndexes);
+        /** @var array $matrixValues1 */
+        $matrixValues1 = $arguments[$matrix1];
+
+        $result = [];
+        foreach ($matrixValues1 as $rowIndex => $row) {
+            foreach ($row as $columnIndex => $value1) {
+                if (isset($matrixValues2[$rowIndex][$columnIndex]) === false) {
+                    continue;
+                }
+
+                $value2 = $matrixValues2[$rowIndex][$columnIndex];
+                $arguments[$matrix1] = $value1;
+                $arguments[$matrix2] = $value2;
+
+                $result[$rowIndex][$columnIndex] = $method(...$arguments);
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param mixed ...$arguments
+     */
+    private static function evaluateVectorPair(callable $method, int $rowIndex, int $columnIndex, ...$arguments): array
+    {
+        $rowVector = Functions::flattenArray($arguments[$rowIndex]);
+        $columnVector = Functions::flattenArray($arguments[$columnIndex]);
+
+        $result = [];
+        foreach ($columnVector as $column) {
+            $rowResults = [];
+            foreach ($rowVector as $row) {
+                $arguments[$rowIndex] = $row;
+                $arguments[$columnIndex] = $column;
+
+                $rowResults[] = $method(...$arguments);
+            }
+            $result[] = $rowResults;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Note, offset is from 1 (for the first argument) rather than from 0.
+     *
+     * @param mixed ...$arguments
+     */
+    private static function evaluateNthArgumentAsArray(callable $method, int $nthArgument, ...$arguments): array
+    {
+        $values = array_slice($arguments, $nthArgument - 1, 1);
+        /** @var array $values */
+        $values = array_pop($values);
+
+        $result = [];
+        foreach ($values as $value) {
+            $arguments[$nthArgument - 1] = $value;
+            $result[] = $method(...$arguments);
+        }
+
+        return $result;
+    }
+}

+ 73 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
+
+class CyclicReferenceStack
+{
+    /**
+     * The call stack for calculated cells.
+     *
+     * @var mixed[]
+     */
+    private $stack = [];
+
+    /**
+     * Return the number of entries on the stack.
+     *
+     * @return int
+     */
+    public function count()
+    {
+        return count($this->stack);
+    }
+
+    /**
+     * Push a new entry onto the stack.
+     *
+     * @param mixed $value
+     */
+    public function push($value): void
+    {
+        $this->stack[$value] = $value;
+    }
+
+    /**
+     * Pop the last entry from the stack.
+     *
+     * @return mixed
+     */
+    public function pop()
+    {
+        return array_pop($this->stack);
+    }
+
+    /**
+     * Test to see if a specified entry exists on the stack.
+     *
+     * @param mixed $value The value to test
+     *
+     * @return bool
+     */
+    public function onStack($value)
+    {
+        return isset($this->stack[$value]);
+    }
+
+    /**
+     * Clear the stack.
+     */
+    public function clear(): void
+    {
+        $this->stack = [];
+    }
+
+    /**
+     * Return an array of all entries on the stack.
+     *
+     * @return mixed[]
+     */
+    public function showStack()
+    {
+        return $this->stack;
+    }
+}

+ 140 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php

@@ -0,0 +1,140 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engine;
+
+class Logger
+{
+    /**
+     * Flag to determine whether a debug log should be generated by the calculation engine
+     *        If true, then a debug log will be generated
+     *        If false, then a debug log will not be generated.
+     *
+     * @var bool
+     */
+    private $writeDebugLog = false;
+
+    /**
+     * Flag to determine whether a debug log should be echoed by the calculation engine
+     *        If true, then a debug log will be echoed
+     *        If false, then a debug log will not be echoed
+     * A debug log can only be echoed if it is generated.
+     *
+     * @var bool
+     */
+    private $echoDebugLog = false;
+
+    /**
+     * The debug log generated by the calculation engine.
+     *
+     * @var string[]
+     */
+    private $debugLog = [];
+
+    /**
+     * The calculation engine cell reference stack.
+     *
+     * @var CyclicReferenceStack
+     */
+    private $cellStack;
+
+    /**
+     * Instantiate a Calculation engine logger.
+     */
+    public function __construct(CyclicReferenceStack $stack)
+    {
+        $this->cellStack = $stack;
+    }
+
+    /**
+     * Enable/Disable Calculation engine logging.
+     *
+     * @param bool $writeDebugLog
+     */
+    public function setWriteDebugLog($writeDebugLog): void
+    {
+        $this->writeDebugLog = $writeDebugLog;
+    }
+
+    /**
+     * Return whether calculation engine logging is enabled or disabled.
+     *
+     * @return bool
+     */
+    public function getWriteDebugLog()
+    {
+        return $this->writeDebugLog;
+    }
+
+    /**
+     * Enable/Disable echoing of debug log information.
+     *
+     * @param bool $echoDebugLog
+     */
+    public function setEchoDebugLog($echoDebugLog): void
+    {
+        $this->echoDebugLog = $echoDebugLog;
+    }
+
+    /**
+     * Return whether echoing of debug log information is enabled or disabled.
+     *
+     * @return bool
+     */
+    public function getEchoDebugLog()
+    {
+        return $this->echoDebugLog;
+    }
+
+    /**
+     * Write an entry to the calculation engine debug log.
+     */
+    public function writeDebugLog(...$args): void
+    {
+        //    Only write the debug log if logging is enabled
+        if ($this->writeDebugLog) {
+            $message = implode('', $args);
+            $cellReference = implode(' -> ', $this->cellStack->showStack());
+            if ($this->echoDebugLog) {
+                echo $cellReference,
+                    ($this->cellStack->count() > 0 ? ' => ' : ''),
+                    $message,
+                    PHP_EOL;
+            }
+            $this->debugLog[] = $cellReference .
+                ($this->cellStack->count() > 0 ? ' => ' : '') .
+                $message;
+        }
+    }
+
+    /**
+     * Write a series of entries to the calculation engine debug log.
+     *
+     * @param string[] $args
+     */
+    public function mergeDebugLog(array $args): void
+    {
+        if ($this->writeDebugLog) {
+            foreach ($args as $entry) {
+                $this->writeDebugLog($entry);
+            }
+        }
+    }
+
+    /**
+     * Clear the calculation engine debug log.
+     */
+    public function clearLog(): void
+    {
+        $this->debugLog = [];
+    }
+
+    /**
+     * Return the calculation engine debug log.
+     *
+     * @return string[]
+     */
+    public function getLog()
+    {
+        return $this->debugLog;
+    }
+}

File diff suppressed because it is too large
+ 1446 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php


+ 145 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselI.php

@@ -0,0 +1,145 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class BesselI
+{
+    use ArrayEnabled;
+
+    /**
+     * BESSELI.
+     *
+     *    Returns the modified Bessel function In(x), which is equivalent to the Bessel function evaluated
+     *        for purely imaginary arguments
+     *
+     *    Excel Function:
+     *        BESSELI(x,ord)
+     *
+     * NOTE: The MS Excel implementation of the BESSELI function is still not accurate.
+     *       This code provides a more accurate calculation
+     *
+     * @param mixed $x A float value at which to evaluate the function.
+     *                                If x is nonnumeric, BESSELI returns the #VALUE! error value.
+     *                      Or can be an array of values
+     * @param mixed $ord The integer order of the Bessel function.
+     *                                If ord is not an integer, it is truncated.
+     *                                If $ord is nonnumeric, BESSELI returns the #VALUE! error value.
+     *                                If $ord < 0, BESSELI returns the #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|float|string Result, or a string containing an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function BESSELI($x, $ord)
+    {
+        if (is_array($x) || is_array($ord)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
+        }
+
+        try {
+            $x = EngineeringValidations::validateFloat($x);
+            $ord = EngineeringValidations::validateInt($ord);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        if ($ord < 0) {
+            return Functions::NAN();
+        }
+
+        $fResult = self::calculate($x, $ord);
+
+        return (is_nan($fResult)) ? Functions::NAN() : $fResult;
+    }
+
+    private static function calculate(float $x, int $ord): float
+    {
+        // special cases
+        switch ($ord) {
+            case 0:
+                return self::besselI0($x);
+            case 1:
+                return self::besselI1($x);
+        }
+
+        return self::besselI2($x, $ord);
+    }
+
+    private static function besselI0(float $x): float
+    {
+        $ax = abs($x);
+
+        if ($ax < 3.75) {
+            $y = $x / 3.75;
+            $y = $y * $y;
+
+            return 1.0 + $y * (3.5156229 + $y * (3.0899424 + $y * (1.2067492
+                                + $y * (0.2659732 + $y * (0.360768e-1 + $y * 0.45813e-2)))));
+        }
+
+        $y = 3.75 / $ax;
+
+        return (exp($ax) / sqrt($ax)) * (0.39894228 + $y * (0.1328592e-1 + $y * (0.225319e-2 + $y * (-0.157565e-2
+                            + $y * (0.916281e-2 + $y * (-0.2057706e-1 + $y * (0.2635537e-1 +
+                                        $y * (-0.1647633e-1 + $y * 0.392377e-2))))))));
+    }
+
+    private static function besselI1(float $x): float
+    {
+        $ax = abs($x);
+
+        if ($ax < 3.75) {
+            $y = $x / 3.75;
+            $y = $y * $y;
+            $ans = $ax * (0.5 + $y * (0.87890594 + $y * (0.51498869 + $y * (0.15084934 + $y * (0.2658733e-1 +
+                                    $y * (0.301532e-2 + $y * 0.32411e-3))))));
+
+            return ($x < 0.0) ? -$ans : $ans;
+        }
+
+        $y = 3.75 / $ax;
+        $ans = 0.2282967e-1 + $y * (-0.2895312e-1 + $y * (0.1787654e-1 - $y * 0.420059e-2));
+        $ans = 0.39894228 + $y * (-0.3988024e-1 + $y * (-0.362018e-2 + $y * (0.163801e-2 +
+                        $y * (-0.1031555e-1 + $y * $ans))));
+        $ans *= exp($ax) / sqrt($ax);
+
+        return ($x < 0.0) ? -$ans : $ans;
+    }
+
+    private static function besselI2(float $x, int $ord): float
+    {
+        if ($x === 0.0) {
+            return 0.0;
+        }
+
+        $tox = 2.0 / abs($x);
+        $bip = 0;
+        $ans = 0.0;
+        $bi = 1.0;
+
+        for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) {
+            $bim = $bip + $j * $tox * $bi;
+            $bip = $bi;
+            $bi = $bim;
+
+            if (abs($bi) > 1.0e+12) {
+                $ans *= 1.0e-12;
+                $bi *= 1.0e-12;
+                $bip *= 1.0e-12;
+            }
+
+            if ($j === $ord) {
+                $ans = $bip;
+            }
+        }
+
+        $ans *= self::besselI0($x) / $bi;
+
+        return ($x < 0.0 && (($ord % 2) === 1)) ? -$ans : $ans;
+    }
+}

+ 180 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselJ.php

@@ -0,0 +1,180 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class BesselJ
+{
+    use ArrayEnabled;
+
+    /**
+     * BESSELJ.
+     *
+     *    Returns the Bessel function
+     *
+     *    Excel Function:
+     *        BESSELJ(x,ord)
+     *
+     * NOTE: The MS Excel implementation of the BESSELJ function is still not accurate, particularly for higher order
+     *       values with x < -8 and x > 8. This code provides a more accurate calculation
+     *
+     * @param mixed $x A float value at which to evaluate the function.
+     *                                If x is nonnumeric, BESSELJ returns the #VALUE! error value.
+     *                      Or can be an array of values
+     * @param mixed $ord The integer order of the Bessel function.
+     *                       If ord is not an integer, it is truncated.
+     *                                If $ord is nonnumeric, BESSELJ returns the #VALUE! error value.
+     *                                If $ord < 0, BESSELJ returns the #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|float|string Result, or a string containing an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function BESSELJ($x, $ord)
+    {
+        if (is_array($x) || is_array($ord)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
+        }
+
+        try {
+            $x = EngineeringValidations::validateFloat($x);
+            $ord = EngineeringValidations::validateInt($ord);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        if ($ord < 0) {
+            return Functions::NAN();
+        }
+
+        $fResult = self::calculate($x, $ord);
+
+        return (is_nan($fResult)) ? Functions::NAN() : $fResult;
+    }
+
+    private static function calculate(float $x, int $ord): float
+    {
+        // special cases
+        switch ($ord) {
+            case 0:
+                return self::besselJ0($x);
+            case 1:
+                return self::besselJ1($x);
+        }
+
+        return self::besselJ2($x, $ord);
+    }
+
+    private static function besselJ0(float $x): float
+    {
+        $ax = abs($x);
+
+        if ($ax < 8.0) {
+            $y = $x * $x;
+            $ans1 = 57568490574.0 + $y * (-13362590354.0 + $y * (651619640.7 + $y * (-11214424.18 + $y *
+                            (77392.33017 + $y * (-184.9052456)))));
+            $ans2 = 57568490411.0 + $y * (1029532985.0 + $y * (9494680.718 + $y * (59272.64853 + $y *
+                            (267.8532712 + $y * 1.0))));
+
+            return $ans1 / $ans2;
+        }
+
+        $z = 8.0 / $ax;
+        $y = $z * $z;
+        $xx = $ax - 0.785398164;
+        $ans1 = 1.0 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6)));
+        $ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y *
+                    (0.7621095161e-6 - $y * 0.934935152e-7)));
+
+        return sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2);
+    }
+
+    private static function besselJ1(float $x): float
+    {
+        $ax = abs($x);
+
+        if ($ax < 8.0) {
+            $y = $x * $x;
+            $ans1 = $x * (72362614232.0 + $y * (-7895059235.0 + $y * (242396853.1 + $y *
+                            (-2972611.439 + $y * (15704.48260 + $y * (-30.16036606))))));
+            $ans2 = 144725228442.0 + $y * (2300535178.0 + $y * (18583304.74 + $y * (99447.43394 + $y *
+                            (376.9991397 + $y * 1.0))));
+
+            return $ans1 / $ans2;
+        }
+
+        $z = 8.0 / $ax;
+        $y = $z * $z;
+        $xx = $ax - 2.356194491;
+
+        $ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6))));
+        $ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y *
+                    (-0.88228987e-6 + $y * 0.105787412e-6)));
+        $ans = sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2);
+
+        return ($x < 0.0) ? -$ans : $ans;
+    }
+
+    private static function besselJ2(float $x, int $ord): float
+    {
+        $ax = abs($x);
+        if ($ax === 0.0) {
+            return 0.0;
+        }
+
+        if ($ax > $ord) {
+            return self::besselj2a($ax, $ord, $x);
+        }
+
+        return self::besselj2b($ax, $ord, $x);
+    }
+
+    private static function besselj2a(float $ax, int $ord, float $x)
+    {
+        $tox = 2.0 / $ax;
+        $bjm = self::besselJ0($ax);
+        $bj = self::besselJ1($ax);
+        for ($j = 1; $j < $ord; ++$j) {
+            $bjp = $j * $tox * $bj - $bjm;
+            $bjm = $bj;
+            $bj = $bjp;
+        }
+        $ans = $bj;
+
+        return ($x < 0.0 && ($ord % 2) == 1) ? -$ans : $ans;
+    }
+
+    private static function besselj2b(float $ax, int $ord, float $x)
+    {
+        $tox = 2.0 / $ax;
+        $jsum = false;
+        $bjp = $ans = $sum = 0.0;
+        $bj = 1.0;
+        for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) {
+            $bjm = $j * $tox * $bj - $bjp;
+            $bjp = $bj;
+            $bj = $bjm;
+            if (abs($bj) > 1.0e+10) {
+                $bj *= 1.0e-10;
+                $bjp *= 1.0e-10;
+                $ans *= 1.0e-10;
+                $sum *= 1.0e-10;
+            }
+            if ($jsum === true) {
+                $sum += $bj;
+            }
+            $jsum = !$jsum;
+            if ($j === $ord) {
+                $ans = $bjp;
+            }
+        }
+        $sum = 2.0 * $sum - $bj;
+        $ans /= $sum;
+
+        return ($x < 0.0 && ($ord % 2) === 1) ? -$ans : $ans;
+    }
+}

+ 119 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselK.php

@@ -0,0 +1,119 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class BesselK
+{
+    use ArrayEnabled;
+
+    /**
+     * BESSELK.
+     *
+     *    Returns the modified Bessel function Kn(x), which is equivalent to the Bessel functions evaluated
+     *        for purely imaginary arguments.
+     *
+     *    Excel Function:
+     *        BESSELK(x,ord)
+     *
+     * @param mixed $x A float value at which to evaluate the function.
+     *                                If x is nonnumeric, BESSELK returns the #VALUE! error value.
+     *                      Or can be an array of values
+     * @param mixed $ord The integer order of the Bessel function.
+     *                       If ord is not an integer, it is truncated.
+     *                                If $ord is nonnumeric, BESSELK returns the #VALUE! error value.
+     *                       If $ord < 0, BESSELKI returns the #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|float|string Result, or a string containing an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function BESSELK($x, $ord)
+    {
+        if (is_array($x) || is_array($ord)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
+        }
+
+        try {
+            $x = EngineeringValidations::validateFloat($x);
+            $ord = EngineeringValidations::validateInt($ord);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        if (($ord < 0) || ($x <= 0.0)) {
+            return Functions::NAN();
+        }
+
+        $fBk = self::calculate($x, $ord);
+
+        return (is_nan($fBk)) ? Functions::NAN() : $fBk;
+    }
+
+    private static function calculate(float $x, int $ord): float
+    {
+        // special cases
+        switch ($ord) {
+            case 0:
+                return self::besselK0($x);
+            case 1:
+                return self::besselK1($x);
+        }
+
+        return self::besselK2($x, $ord);
+    }
+
+    private static function besselK0(float $x): float
+    {
+        if ($x <= 2) {
+            $fNum2 = $x * 0.5;
+            $y = ($fNum2 * $fNum2);
+
+            return -log($fNum2) * BesselI::BESSELI($x, 0) +
+                (-0.57721566 + $y * (0.42278420 + $y * (0.23069756 + $y * (0.3488590e-1 + $y * (0.262698e-2 + $y *
+                                    (0.10750e-3 + $y * 0.74e-5))))));
+        }
+
+        $y = 2 / $x;
+
+        return exp(-$x) / sqrt($x) *
+            (1.25331414 + $y * (-0.7832358e-1 + $y * (0.2189568e-1 + $y * (-0.1062446e-1 + $y *
+                            (0.587872e-2 + $y * (-0.251540e-2 + $y * 0.53208e-3))))));
+    }
+
+    private static function besselK1(float $x): float
+    {
+        if ($x <= 2) {
+            $fNum2 = $x * 0.5;
+            $y = ($fNum2 * $fNum2);
+
+            return log($fNum2) * BesselI::BESSELI($x, 1) +
+                (1 + $y * (0.15443144 + $y * (-0.67278579 + $y * (-0.18156897 + $y * (-0.1919402e-1 + $y *
+                                    (-0.110404e-2 + $y * (-0.4686e-4))))))) / $x;
+        }
+
+        $y = 2 / $x;
+
+        return exp(-$x) / sqrt($x) *
+            (1.25331414 + $y * (0.23498619 + $y * (-0.3655620e-1 + $y * (0.1504268e-1 + $y * (-0.780353e-2 + $y *
+                                (0.325614e-2 + $y * (-0.68245e-3)))))));
+    }
+
+    private static function besselK2(float $x, int $ord)
+    {
+        $fTox = 2 / $x;
+        $fBkm = self::besselK0($x);
+        $fBk = self::besselK1($x);
+        for ($n = 1; $n < $ord; ++$n) {
+            $fBkp = $fBkm + $n * $fTox * $fBk;
+            $fBkm = $fBk;
+            $fBk = $fBkp;
+        }
+
+        return $fBk;
+    }
+}

+ 126 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BesselY.php

@@ -0,0 +1,126 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class BesselY
+{
+    use ArrayEnabled;
+
+    /**
+     * BESSELY.
+     *
+     * Returns the Bessel function, which is also called the Weber function or the Neumann function.
+     *
+     *    Excel Function:
+     *        BESSELY(x,ord)
+     *
+     * @param mixed $x A float value at which to evaluate the function.
+     *                   If x is nonnumeric, BESSELY returns the #VALUE! error value.
+     *                      Or can be an array of values
+     * @param mixed $ord The integer order of the Bessel function.
+     *                       If ord is not an integer, it is truncated.
+     *                       If $ord is nonnumeric, BESSELY returns the #VALUE! error value.
+     *                       If $ord < 0, BESSELY returns the #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|float|string Result, or a string containing an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function BESSELY($x, $ord)
+    {
+        if (is_array($x) || is_array($ord)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord);
+        }
+
+        try {
+            $x = EngineeringValidations::validateFloat($x);
+            $ord = EngineeringValidations::validateInt($ord);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        if (($ord < 0) || ($x <= 0.0)) {
+            return Functions::NAN();
+        }
+
+        $fBy = self::calculate($x, $ord);
+
+        return (is_nan($fBy)) ? Functions::NAN() : $fBy;
+    }
+
+    private static function calculate(float $x, int $ord): float
+    {
+        // special cases
+        switch ($ord) {
+            case 0:
+                return self::besselY0($x);
+            case 1:
+                return self::besselY1($x);
+        }
+
+        return self::besselY2($x, $ord);
+    }
+
+    private static function besselY0(float $x): float
+    {
+        if ($x < 8.0) {
+            $y = ($x * $x);
+            $ans1 = -2957821389.0 + $y * (7062834065.0 + $y * (-512359803.6 + $y * (10879881.29 + $y *
+                            (-86327.92757 + $y * 228.4622733))));
+            $ans2 = 40076544269.0 + $y * (745249964.8 + $y * (7189466.438 + $y *
+                        (47447.26470 + $y * (226.1030244 + $y))));
+
+            return $ans1 / $ans2 + 0.636619772 * BesselJ::BESSELJ($x, 0) * log($x);
+        }
+
+        $z = 8.0 / $x;
+        $y = ($z * $z);
+        $xx = $x - 0.785398164;
+        $ans1 = 1 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6)));
+        $ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * (0.7621095161e-6 + $y *
+                        (-0.934945152e-7))));
+
+        return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2);
+    }
+
+    private static function besselY1(float $x): float
+    {
+        if ($x < 8.0) {
+            $y = ($x * $x);
+            $ans1 = $x * (-0.4900604943e13 + $y * (0.1275274390e13 + $y * (-0.5153438139e11 + $y *
+                            (0.7349264551e9 + $y * (-0.4237922726e7 + $y * 0.8511937935e4)))));
+            $ans2 = 0.2499580570e14 + $y * (0.4244419664e12 + $y * (0.3733650367e10 + $y * (0.2245904002e8 + $y *
+                            (0.1020426050e6 + $y * (0.3549632885e3 + $y)))));
+
+            return ($ans1 / $ans2) + 0.636619772 * (BesselJ::BESSELJ($x, 1) * log($x) - 1 / $x);
+        }
+
+        $z = 8.0 / $x;
+        $y = $z * $z;
+        $xx = $x - 2.356194491;
+        $ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6))));
+        $ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y *
+                    (-0.88228987e-6 + $y * 0.105787412e-6)));
+
+        return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2);
+    }
+
+    private static function besselY2(float $x, int $ord): float
+    {
+        $fTox = 2.0 / $x;
+        $fBym = self::besselY0($x);
+        $fBy = self::besselY1($x);
+        for ($n = 1; $n < $ord; ++$n) {
+            $fByp = $n * $fTox * $fBy - $fBym;
+            $fBym = $fBy;
+            $fBy = $fByp;
+        }
+
+        return $fBy;
+    }
+}

+ 273 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/BitWise.php

@@ -0,0 +1,273 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class BitWise
+{
+    use ArrayEnabled;
+
+    const SPLIT_DIVISOR = 2 ** 24;
+
+    /**
+     * Split a number into upper and lower portions for full 32-bit support.
+     *
+     * @param float|int $number
+     */
+    private static function splitNumber($number): array
+    {
+        return [floor($number / self::SPLIT_DIVISOR), fmod($number, self::SPLIT_DIVISOR)];
+    }
+
+    /**
+     * BITAND.
+     *
+     * Returns the bitwise AND of two integer values.
+     *
+     * Excel Function:
+     *        BITAND(number1, number2)
+     *
+     * @param array|int $number1
+     *                      Or can be an array of values
+     * @param array|int $number2
+     *                      Or can be an array of values
+     *
+     * @return array|int|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function BITAND($number1, $number2)
+    {
+        if (is_array($number1) || is_array($number2)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2);
+        }
+
+        try {
+            $number1 = self::validateBitwiseArgument($number1);
+            $number2 = self::validateBitwiseArgument($number2);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+        $split1 = self::splitNumber($number1);
+        $split2 = self::splitNumber($number2);
+
+        return  self::SPLIT_DIVISOR * ($split1[0] & $split2[0]) + ($split1[1] & $split2[1]);
+    }
+
+    /**
+     * BITOR.
+     *
+     * Returns the bitwise OR of two integer values.
+     *
+     * Excel Function:
+     *        BITOR(number1, number2)
+     *
+     * @param array|int $number1
+     *                      Or can be an array of values
+     * @param array|int $number2
+     *                      Or can be an array of values
+     *
+     * @return array|int|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function BITOR($number1, $number2)
+    {
+        if (is_array($number1) || is_array($number2)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2);
+        }
+
+        try {
+            $number1 = self::validateBitwiseArgument($number1);
+            $number2 = self::validateBitwiseArgument($number2);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $split1 = self::splitNumber($number1);
+        $split2 = self::splitNumber($number2);
+
+        return  self::SPLIT_DIVISOR * ($split1[0] | $split2[0]) + ($split1[1] | $split2[1]);
+    }
+
+    /**
+     * BITXOR.
+     *
+     * Returns the bitwise XOR of two integer values.
+     *
+     * Excel Function:
+     *        BITXOR(number1, number2)
+     *
+     * @param array|int $number1
+     *                      Or can be an array of values
+     * @param array|int $number2
+     *                      Or can be an array of values
+     *
+     * @return array|int|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function BITXOR($number1, $number2)
+    {
+        if (is_array($number1) || is_array($number2)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2);
+        }
+
+        try {
+            $number1 = self::validateBitwiseArgument($number1);
+            $number2 = self::validateBitwiseArgument($number2);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $split1 = self::splitNumber($number1);
+        $split2 = self::splitNumber($number2);
+
+        return  self::SPLIT_DIVISOR * ($split1[0] ^ $split2[0]) + ($split1[1] ^ $split2[1]);
+    }
+
+    /**
+     * BITLSHIFT.
+     *
+     * Returns the number value shifted left by shift_amount bits.
+     *
+     * Excel Function:
+     *        BITLSHIFT(number, shift_amount)
+     *
+     * @param array|int $number
+     *                      Or can be an array of values
+     * @param array|int $shiftAmount
+     *                      Or can be an array of values
+     *
+     * @return array|float|int|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function BITLSHIFT($number, $shiftAmount)
+    {
+        if (is_array($number) || is_array($shiftAmount)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $shiftAmount);
+        }
+
+        try {
+            $number = self::validateBitwiseArgument($number);
+            $shiftAmount = self::validateShiftAmount($shiftAmount);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $result = floor($number * (2 ** $shiftAmount));
+        if ($result > 2 ** 48 - 1) {
+            return Functions::NAN();
+        }
+
+        return $result;
+    }
+
+    /**
+     * BITRSHIFT.
+     *
+     * Returns the number value shifted right by shift_amount bits.
+     *
+     * Excel Function:
+     *        BITRSHIFT(number, shift_amount)
+     *
+     * @param array|int $number
+     *                      Or can be an array of values
+     * @param array|int $shiftAmount
+     *                      Or can be an array of values
+     *
+     * @return array|float|int|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function BITRSHIFT($number, $shiftAmount)
+    {
+        if (is_array($number) || is_array($shiftAmount)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $shiftAmount);
+        }
+
+        try {
+            $number = self::validateBitwiseArgument($number);
+            $shiftAmount = self::validateShiftAmount($shiftAmount);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $result = floor($number / (2 ** $shiftAmount));
+        if ($result > 2 ** 48 - 1) { // possible because shiftAmount can be negative
+            return Functions::NAN();
+        }
+
+        return $result;
+    }
+
+    /**
+     * Validate arguments passed to the bitwise functions.
+     *
+     * @param mixed $value
+     *
+     * @return float|int
+     */
+    private static function validateBitwiseArgument($value)
+    {
+        $value = self::nullFalseTrueToNumber($value);
+
+        if (is_numeric($value)) {
+            if ($value == floor($value)) {
+                if (($value > 2 ** 48 - 1) || ($value < 0)) {
+                    throw new Exception(Functions::NAN());
+                }
+
+                return floor($value);
+            }
+
+            throw new Exception(Functions::NAN());
+        }
+
+        throw new Exception(Functions::VALUE());
+    }
+
+    /**
+     * Validate arguments passed to the bitwise functions.
+     *
+     * @param mixed $value
+     *
+     * @return int
+     */
+    private static function validateShiftAmount($value)
+    {
+        $value = self::nullFalseTrueToNumber($value);
+
+        if (is_numeric($value)) {
+            if (abs($value) > 53) {
+                throw new Exception(Functions::NAN());
+            }
+
+            return (int) $value;
+        }
+
+        throw new Exception(Functions::VALUE());
+    }
+
+    /**
+     * Many functions accept null/false/true argument treated as 0/0/1.
+     *
+     * @param mixed $number
+     *
+     * @return mixed
+     */
+    private static function nullFalseTrueToNumber(&$number)
+    {
+        if ($number === null) {
+            $number = 0;
+        } elseif (is_bool($number)) {
+            $number = (int) $number;
+        }
+
+        return $number;
+    }
+}

+ 82 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Compare.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+
+class Compare
+{
+    use ArrayEnabled;
+
+    /**
+     * DELTA.
+     *
+     *    Excel Function:
+     *        DELTA(a[,b])
+     *
+     *    Tests whether two values are equal. Returns 1 if number1 = number2; returns 0 otherwise.
+     *    Use this function to filter a set of values. For example, by summing several DELTA
+     *        functions you calculate the count of equal pairs. This function is also known as the
+     *        Kronecker Delta function.
+     *
+     * @param array|float $a the first number
+     *                      Or can be an array of values
+     * @param array|float $b The second number. If omitted, b is assumed to be zero.
+     *                      Or can be an array of values
+     *
+     * @return array|int|string (string in the event of an error)
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function DELTA($a, $b = 0.0)
+    {
+        if (is_array($a) || is_array($b)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $a, $b);
+        }
+
+        try {
+            $a = EngineeringValidations::validateFloat($a);
+            $b = EngineeringValidations::validateFloat($b);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        return (int) (abs($a - $b) < 1.0e-15);
+    }
+
+    /**
+     * GESTEP.
+     *
+     *    Excel Function:
+     *        GESTEP(number[,step])
+     *
+     *    Returns 1 if number >= step; returns 0 (zero) otherwise
+     *    Use this function to filter a set of values. For example, by summing several GESTEP
+     *        functions you calculate the count of values that exceed a threshold.
+     *
+     * @param array|float $number the value to test against step
+     *                      Or can be an array of values
+     * @param array|float $step The threshold value. If you omit a value for step, GESTEP uses zero.
+     *                      Or can be an array of values
+     *
+     * @return array|int|string (string in the event of an error)
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function GESTEP($number, $step = 0.0)
+    {
+        if (is_array($number) || is_array($step)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $step);
+        }
+
+        try {
+            $number = EngineeringValidations::validateFloat($number);
+            $step = EngineeringValidations::validateFloat($step);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        return (int) ($number >= $step);
+    }
+}

+ 121 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php

@@ -0,0 +1,121 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use Complex\Complex as ComplexObject;
+use Complex\Exception as ComplexException;
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class Complex
+{
+    use ArrayEnabled;
+
+    /**
+     * COMPLEX.
+     *
+     * Converts real and imaginary coefficients into a complex number of the form x +/- yi or x +/- yj.
+     *
+     * Excel Function:
+     *        COMPLEX(realNumber,imaginary[,suffix])
+     *
+     * @param mixed $realNumber the real float coefficient of the complex number
+     *                      Or can be an array of values
+     * @param mixed $imaginary the imaginary float coefficient of the complex number
+     *                      Or can be an array of values
+     * @param mixed $suffix The character suffix for the imaginary component of the complex number.
+     *                          If omitted, the suffix is assumed to be "i".
+     *                      Or can be an array of values
+     *
+     * @return array|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function COMPLEX($realNumber = 0.0, $imaginary = 0.0, $suffix = 'i')
+    {
+        if (is_array($realNumber) || is_array($imaginary) || is_array($suffix)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $realNumber, $imaginary, $suffix);
+        }
+
+        $realNumber = $realNumber ?? 0.0;
+        $imaginary = $imaginary ?? 0.0;
+        $suffix = $suffix ?? 'i';
+
+        try {
+            $realNumber = EngineeringValidations::validateFloat($realNumber);
+            $imaginary = EngineeringValidations::validateFloat($imaginary);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        if (($suffix == 'i') || ($suffix == 'j') || ($suffix == '')) {
+            $complex = new ComplexObject($realNumber, $imaginary, $suffix);
+
+            return (string) $complex;
+        }
+
+        return Functions::VALUE();
+    }
+
+    /**
+     * IMAGINARY.
+     *
+     * Returns the imaginary coefficient of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMAGINARY(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the imaginary
+     *                                         coefficient
+     *                      Or can be an array of values
+     *
+     * @return array|float|string (string if an error)
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMAGINARY($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return $complex->getImaginary();
+    }
+
+    /**
+     * IMREAL.
+     *
+     * Returns the real coefficient of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMREAL(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the real coefficient
+     *                      Or can be an array of values
+     *
+     * @return array|float|string (string if an error)
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMREAL($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return $complex->getReal();
+    }
+}

+ 611 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php

@@ -0,0 +1,611 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use Complex\Complex as ComplexObject;
+use Complex\Exception as ComplexException;
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class ComplexFunctions
+{
+    use ArrayEnabled;
+
+    /**
+     * IMABS.
+     *
+     * Returns the absolute value (modulus) of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMABS(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the absolute value
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMABS($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return $complex->abs();
+    }
+
+    /**
+     * IMARGUMENT.
+     *
+     * Returns the argument theta of a complex number, i.e. the angle in radians from the real
+     * axis to the representation of the number in polar coordinates.
+     *
+     * Excel Function:
+     *        IMARGUMENT(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the argument theta
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMARGUMENT($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
+            return Functions::DIV0();
+        }
+
+        return $complex->argument();
+    }
+
+    /**
+     * IMCONJUGATE.
+     *
+     * Returns the complex conjugate of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMCONJUGATE(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the conjugate
+     *                      Or can be an array of values
+     *
+     * @return array|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMCONJUGATE($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->conjugate();
+    }
+
+    /**
+     * IMCOS.
+     *
+     * Returns the cosine of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMCOS(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the cosine
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMCOS($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->cos();
+    }
+
+    /**
+     * IMCOSH.
+     *
+     * Returns the hyperbolic cosine of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMCOSH(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the hyperbolic cosine
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMCOSH($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->cosh();
+    }
+
+    /**
+     * IMCOT.
+     *
+     * Returns the cotangent of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMCOT(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the cotangent
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMCOT($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->cot();
+    }
+
+    /**
+     * IMCSC.
+     *
+     * Returns the cosecant of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMCSC(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the cosecant
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMCSC($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->csc();
+    }
+
+    /**
+     * IMCSCH.
+     *
+     * Returns the hyperbolic cosecant of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMCSCH(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the hyperbolic cosecant
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMCSCH($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->csch();
+    }
+
+    /**
+     * IMSIN.
+     *
+     * Returns the sine of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMSIN(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the sine
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMSIN($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->sin();
+    }
+
+    /**
+     * IMSINH.
+     *
+     * Returns the hyperbolic sine of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMSINH(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the hyperbolic sine
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMSINH($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->sinh();
+    }
+
+    /**
+     * IMSEC.
+     *
+     * Returns the secant of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMSEC(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the secant
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMSEC($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->sec();
+    }
+
+    /**
+     * IMSECH.
+     *
+     * Returns the hyperbolic secant of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMSECH(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the hyperbolic secant
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMSECH($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->sech();
+    }
+
+    /**
+     * IMTAN.
+     *
+     * Returns the tangent of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMTAN(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the tangent
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMTAN($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->tan();
+    }
+
+    /**
+     * IMSQRT.
+     *
+     * Returns the square root of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMSQRT(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the square root
+     *                      Or can be an array of values
+     *
+     * @return array|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMSQRT($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        $theta = self::IMARGUMENT($complexNumber);
+        if ($theta === Functions::DIV0()) {
+            return '0';
+        }
+
+        return (string) $complex->sqrt();
+    }
+
+    /**
+     * IMLN.
+     *
+     * Returns the natural logarithm of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMLN(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the natural logarithm
+     *                      Or can be an array of values
+     *
+     * @return array|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMLN($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->ln();
+    }
+
+    /**
+     * IMLOG10.
+     *
+     * Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMLOG10(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the common logarithm
+     *                      Or can be an array of values
+     *
+     * @return array|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMLOG10($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->log10();
+    }
+
+    /**
+     * IMLOG2.
+     *
+     * Returns the base-2 logarithm of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMLOG2(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the base-2 logarithm
+     *                      Or can be an array of values
+     *
+     * @return array|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMLOG2($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->log2();
+    }
+
+    /**
+     * IMEXP.
+     *
+     * Returns the exponential of a complex number in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMEXP(complexNumber)
+     *
+     * @param array|string $complexNumber the complex number for which you want the exponential
+     *                      Or can be an array of values
+     *
+     * @return array|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMEXP($complexNumber)
+    {
+        if (is_array($complexNumber)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return (string) $complex->exp();
+    }
+
+    /**
+     * IMPOWER.
+     *
+     * Returns a complex number in x + yi or x + yj text format raised to a power.
+     *
+     * Excel Function:
+     *        IMPOWER(complexNumber,realNumber)
+     *
+     * @param array|string $complexNumber the complex number you want to raise to a power
+     *                      Or can be an array of values
+     * @param array|float|int|string $realNumber the power to which you want to raise the complex number
+     *                      Or can be an array of values
+     *
+     * @return array|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMPOWER($complexNumber, $realNumber)
+    {
+        if (is_array($complexNumber) || is_array($realNumber)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexNumber, $realNumber);
+        }
+
+        try {
+            $complex = new ComplexObject($complexNumber);
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        if (!is_numeric($realNumber)) {
+            return Functions::VALUE();
+        }
+
+        return (string) $complex->pow((float) $realNumber);
+    }
+}

+ 133 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php

@@ -0,0 +1,133 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use Complex\Complex as ComplexObject;
+use Complex\Exception as ComplexException;
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class ComplexOperations
+{
+    use ArrayEnabled;
+
+    /**
+     * IMDIV.
+     *
+     * Returns the quotient of two complex numbers in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMDIV(complexDividend,complexDivisor)
+     *
+     * @param array|string $complexDividend the complex numerator or dividend
+     *                      Or can be an array of values
+     * @param array|string $complexDivisor the complex denominator or divisor
+     *                      Or can be an array of values
+     *
+     * @return array|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMDIV($complexDividend, $complexDivisor)
+    {
+        if (is_array($complexDividend) || is_array($complexDivisor)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexDividend, $complexDivisor);
+        }
+
+        try {
+            return (string) (new ComplexObject($complexDividend))->divideby(new ComplexObject($complexDivisor));
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+    }
+
+    /**
+     * IMSUB.
+     *
+     * Returns the difference of two complex numbers in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMSUB(complexNumber1,complexNumber2)
+     *
+     * @param array|string $complexNumber1 the complex number from which to subtract complexNumber2
+     *                      Or can be an array of values
+     * @param array|string $complexNumber2 the complex number to subtract from complexNumber1
+     *                      Or can be an array of values
+     *
+     * @return array|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function IMSUB($complexNumber1, $complexNumber2)
+    {
+        if (is_array($complexNumber1) || is_array($complexNumber2)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexNumber1, $complexNumber2);
+        }
+
+        try {
+            return (string) (new ComplexObject($complexNumber1))->subtract(new ComplexObject($complexNumber2));
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+    }
+
+    /**
+     * IMSUM.
+     *
+     * Returns the sum of two or more complex numbers in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMSUM(complexNumber[,complexNumber[,...]])
+     *
+     * @param string ...$complexNumbers Series of complex numbers to add
+     *
+     * @return string
+     */
+    public static function IMSUM(...$complexNumbers)
+    {
+        // Return value
+        $returnValue = new ComplexObject(0.0);
+        $aArgs = Functions::flattenArray($complexNumbers);
+
+        try {
+            // Loop through the arguments
+            foreach ($aArgs as $complex) {
+                $returnValue = $returnValue->add(new ComplexObject($complex));
+            }
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return (string) $returnValue;
+    }
+
+    /**
+     * IMPRODUCT.
+     *
+     * Returns the product of two or more complex numbers in x + yi or x + yj text format.
+     *
+     * Excel Function:
+     *        IMPRODUCT(complexNumber[,complexNumber[,...]])
+     *
+     * @param string ...$complexNumbers Series of complex numbers to multiply
+     *
+     * @return string
+     */
+    public static function IMPRODUCT(...$complexNumbers)
+    {
+        // Return value
+        $returnValue = new ComplexObject(1.0);
+        $aArgs = Functions::flattenArray($complexNumbers);
+
+        try {
+            // Loop through the arguments
+            foreach ($aArgs as $complex) {
+                $returnValue = $returnValue->multiply(new ComplexObject($complex));
+            }
+        } catch (ComplexException $e) {
+            return Functions::NAN();
+        }
+
+        return (string) $returnValue;
+    }
+}

+ 11 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Constants.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+class Constants
+{
+    /**
+     * EULER.
+     */
+    public const EULER = 2.71828182845904523536;
+}

+ 68 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php

@@ -0,0 +1,68 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+abstract class ConvertBase
+{
+    use ArrayEnabled;
+
+    protected static function validateValue($value): string
+    {
+        if (is_bool($value)) {
+            if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) {
+                throw new Exception(Functions::VALUE());
+            }
+            $value = (int) $value;
+        }
+
+        if (is_numeric($value)) {
+            if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
+                $value = floor((float) $value);
+            }
+        }
+
+        return strtoupper((string) $value);
+    }
+
+    protected static function validatePlaces($places = null): ?int
+    {
+        if ($places === null) {
+            return $places;
+        }
+
+        if (is_numeric($places)) {
+            if ($places < 0 || $places > 10) {
+                throw new Exception(Functions::NAN());
+            }
+
+            return (int) $places;
+        }
+
+        throw new Exception(Functions::VALUE());
+    }
+
+    /**
+     * Formats a number base string value with leading zeroes.
+     *
+     * @param string $value The "number" to pad
+     * @param ?int $places The length that we want to pad this value
+     *
+     * @return string The padded "number"
+     */
+    protected static function nbrConversionFormat(string $value, ?int $places): string
+    {
+        if ($places !== null) {
+            if (strlen($value) <= $places) {
+                return substr(str_pad($value, $places, '0', STR_PAD_LEFT), -10);
+            }
+
+            return Functions::NAN();
+        }
+
+        return substr($value, -10);
+    }
+}

+ 163 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php

@@ -0,0 +1,163 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class ConvertBinary extends ConvertBase
+{
+    /**
+     * toDecimal.
+     *
+     * Return a binary value as decimal.
+     *
+     * Excel Function:
+     *        BIN2DEC(x)
+     *
+     * @param array|string $value The binary number (as a string) that you want to convert. The number
+     *                                cannot contain more than 10 characters (10 bits). The most significant
+     *                                bit of number is the sign bit. The remaining 9 bits are magnitude bits.
+     *                                Negative numbers are represented using two's-complement notation.
+     *                                If number is not a valid binary number, or if number contains more than
+     *                                10 characters (10 bits), BIN2DEC returns the #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|string Result, or an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function toDecimal($value)
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        try {
+            $value = self::validateValue($value);
+            $value = self::validateBinary($value);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        if (strlen($value) == 10) {
+            //    Two's Complement
+            $value = substr($value, -9);
+
+            return '-' . (512 - bindec($value));
+        }
+
+        return (string) bindec($value);
+    }
+
+    /**
+     * toHex.
+     *
+     * Return a binary value as hex.
+     *
+     * Excel Function:
+     *        BIN2HEX(x[,places])
+     *
+     * @param array|string $value The binary number (as a string) that you want to convert. The number
+     *                                cannot contain more than 10 characters (10 bits). The most significant
+     *                                bit of number is the sign bit. The remaining 9 bits are magnitude bits.
+     *                                Negative numbers are represented using two's-complement notation.
+     *                                If number is not a valid binary number, or if number contains more than
+     *                                10 characters (10 bits), BIN2HEX returns the #NUM! error value.
+     *                      Or can be an array of values
+     * @param array|int $places The number of characters to use. If places is omitted, BIN2HEX uses the
+     *                                minimum number of characters necessary. Places is useful for padding the
+     *                                return value with leading 0s (zeros).
+     *                                If places is not an integer, it is truncated.
+     *                                If places is nonnumeric, BIN2HEX returns the #VALUE! error value.
+     *                                If places is negative, BIN2HEX returns the #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|string Result, or an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function toHex($value, $places = null)
+    {
+        if (is_array($value) || is_array($places)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
+        }
+
+        try {
+            $value = self::validateValue($value);
+            $value = self::validateBinary($value);
+            $places = self::validatePlaces($places);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        if (strlen($value) == 10) {
+            $high2 = substr($value, 0, 2);
+            $low8 = substr($value, 2);
+            $xarr = ['00' => '00000000', '01' => '00000001', '10' => 'FFFFFFFE', '11' => 'FFFFFFFF'];
+
+            return $xarr[$high2] . strtoupper(substr('0' . dechex((int) bindec($low8)), -2));
+        }
+        $hexVal = (string) strtoupper(dechex((int) bindec($value)));
+
+        return self::nbrConversionFormat($hexVal, $places);
+    }
+
+    /**
+     * toOctal.
+     *
+     * Return a binary value as octal.
+     *
+     * Excel Function:
+     *        BIN2OCT(x[,places])
+     *
+     * @param array|string $value The binary number (as a string) that you want to convert. The number
+     *                                cannot contain more than 10 characters (10 bits). The most significant
+     *                                bit of number is the sign bit. The remaining 9 bits are magnitude bits.
+     *                                Negative numbers are represented using two's-complement notation.
+     *                                If number is not a valid binary number, or if number contains more than
+     *                                10 characters (10 bits), BIN2OCT returns the #NUM! error value.
+     *                      Or can be an array of values
+     * @param array|int $places The number of characters to use. If places is omitted, BIN2OCT uses the
+     *                                minimum number of characters necessary. Places is useful for padding the
+     *                                return value with leading 0s (zeros).
+     *                                If places is not an integer, it is truncated.
+     *                                If places is nonnumeric, BIN2OCT returns the #VALUE! error value.
+     *                                If places is negative, BIN2OCT returns the #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|string Result, or an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function toOctal($value, $places = null)
+    {
+        if (is_array($value) || is_array($places)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
+        }
+
+        try {
+            $value = self::validateValue($value);
+            $value = self::validateBinary($value);
+            $places = self::validatePlaces($places);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        if (strlen($value) == 10 && substr($value, 0, 1) === '1') { //    Two's Complement
+            return str_repeat('7', 6) . strtoupper(decoct((int) bindec("11$value")));
+        }
+        $octVal = (string) decoct((int) bindec($value));
+
+        return self::nbrConversionFormat($octVal, $places);
+    }
+
+    protected static function validateBinary(string $value): string
+    {
+        if ((strlen($value) > preg_match_all('/[01]/', $value)) || (strlen($value) > 10)) {
+            throw new Exception(Functions::NAN());
+        }
+
+        return $value;
+    }
+}

+ 213 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php

@@ -0,0 +1,213 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class ConvertDecimal extends ConvertBase
+{
+    const LARGEST_OCTAL_IN_DECIMAL = 536870911;
+    const SMALLEST_OCTAL_IN_DECIMAL = -536870912;
+    const LARGEST_BINARY_IN_DECIMAL = 511;
+    const SMALLEST_BINARY_IN_DECIMAL = -512;
+    const LARGEST_HEX_IN_DECIMAL = 549755813887;
+    const SMALLEST_HEX_IN_DECIMAL = -549755813888;
+
+    /**
+     * toBinary.
+     *
+     * Return a decimal value as binary.
+     *
+     * Excel Function:
+     *        DEC2BIN(x[,places])
+     *
+     * @param array|string $value The decimal integer you want to convert. If number is negative,
+     *                          valid place values are ignored and DEC2BIN returns a 10-character
+     *                          (10-bit) binary number in which the most significant bit is the sign
+     *                          bit. The remaining 9 bits are magnitude bits. Negative numbers are
+     *                          represented using two's-complement notation.
+     *                      If number < -512 or if number > 511, DEC2BIN returns the #NUM! error
+     *                          value.
+     *                      If number is nonnumeric, DEC2BIN returns the #VALUE! error value.
+     *                      If DEC2BIN requires more than places characters, it returns the #NUM!
+     *                          error value.
+     *                      Or can be an array of values
+     * @param array|int $places The number of characters to use. If places is omitted, DEC2BIN uses
+     *                          the minimum number of characters necessary. Places is useful for
+     *                          padding the return value with leading 0s (zeros).
+     *                      If places is not an integer, it is truncated.
+     *                      If places is nonnumeric, DEC2BIN returns the #VALUE! error value.
+     *                      If places is zero or negative, DEC2BIN returns the #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|string Result, or an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function toBinary($value, $places = null)
+    {
+        if (is_array($value) || is_array($places)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
+        }
+
+        try {
+            $value = self::validateValue($value);
+            $value = self::validateDecimal($value);
+            $places = self::validatePlaces($places);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $value = (int) floor((float) $value);
+        if ($value > self::LARGEST_BINARY_IN_DECIMAL || $value < self::SMALLEST_BINARY_IN_DECIMAL) {
+            return Functions::NAN();
+        }
+
+        $r = decbin($value);
+        // Two's Complement
+        $r = substr($r, -10);
+
+        return self::nbrConversionFormat($r, $places);
+    }
+
+    /**
+     * toHex.
+     *
+     * Return a decimal value as hex.
+     *
+     * Excel Function:
+     *        DEC2HEX(x[,places])
+     *
+     * @param array|string $value The decimal integer you want to convert. If number is negative,
+     *                          places is ignored and DEC2HEX returns a 10-character (40-bit)
+     *                          hexadecimal number in which the most significant bit is the sign
+     *                          bit. The remaining 39 bits are magnitude bits. Negative numbers
+     *                          are represented using two's-complement notation.
+     *                      If number < -549,755,813,888 or if number > 549,755,813,887,
+     *                          DEC2HEX returns the #NUM! error value.
+     *                      If number is nonnumeric, DEC2HEX returns the #VALUE! error value.
+     *                      If DEC2HEX requires more than places characters, it returns the
+     *                          #NUM! error value.
+     *                      Or can be an array of values
+     * @param array|int $places The number of characters to use. If places is omitted, DEC2HEX uses
+     *                          the minimum number of characters necessary. Places is useful for
+     *                          padding the return value with leading 0s (zeros).
+     *                      If places is not an integer, it is truncated.
+     *                      If places is nonnumeric, DEC2HEX returns the #VALUE! error value.
+     *                      If places is zero or negative, DEC2HEX returns the #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|string Result, or an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function toHex($value, $places = null)
+    {
+        if (is_array($value) || is_array($places)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
+        }
+
+        try {
+            $value = self::validateValue($value);
+            $value = self::validateDecimal($value);
+            $places = self::validatePlaces($places);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $value = floor((float) $value);
+        if ($value > self::LARGEST_HEX_IN_DECIMAL || $value < self::SMALLEST_HEX_IN_DECIMAL) {
+            return Functions::NAN();
+        }
+        $r = strtoupper(dechex((int) $value));
+        $r = self::hex32bit($value, $r);
+
+        return self::nbrConversionFormat($r, $places);
+    }
+
+    public static function hex32bit(float $value, string $hexstr, bool $force = false): string
+    {
+        if (PHP_INT_SIZE === 4 || $force) {
+            if ($value >= 2 ** 32) {
+                $quotient = (int) ($value / (2 ** 32));
+
+                return strtoupper(substr('0' . dechex($quotient), -2) . $hexstr);
+            }
+            if ($value < -(2 ** 32)) {
+                $quotient = 256 - (int) ceil((-$value) / (2 ** 32));
+
+                return strtoupper(substr('0' . dechex($quotient), -2) . substr("00000000$hexstr", -8));
+            }
+            if ($value < 0) {
+                return "FF$hexstr";
+            }
+        }
+
+        return $hexstr;
+    }
+
+    /**
+     * toOctal.
+     *
+     * Return an decimal value as octal.
+     *
+     * Excel Function:
+     *        DEC2OCT(x[,places])
+     *
+     * @param array|string $value The decimal integer you want to convert. If number is negative,
+     *                          places is ignored and DEC2OCT returns a 10-character (30-bit)
+     *                          octal number in which the most significant bit is the sign bit.
+     *                          The remaining 29 bits are magnitude bits. Negative numbers are
+     *                          represented using two's-complement notation.
+     *                      If number < -536,870,912 or if number > 536,870,911, DEC2OCT
+     *                          returns the #NUM! error value.
+     *                      If number is nonnumeric, DEC2OCT returns the #VALUE! error value.
+     *                      If DEC2OCT requires more than places characters, it returns the
+     *                          #NUM! error value.
+     *                      Or can be an array of values
+     * @param array|int $places The number of characters to use. If places is omitted, DEC2OCT uses
+     *                          the minimum number of characters necessary. Places is useful for
+     *                          padding the return value with leading 0s (zeros).
+     *                      If places is not an integer, it is truncated.
+     *                      If places is nonnumeric, DEC2OCT returns the #VALUE! error value.
+     *                      If places is zero or negative, DEC2OCT returns the #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|string Result, or an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function toOctal($value, $places = null)
+    {
+        if (is_array($value) || is_array($places)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
+        }
+
+        try {
+            $value = self::validateValue($value);
+            $value = self::validateDecimal($value);
+            $places = self::validatePlaces($places);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $value = (int) floor((float) $value);
+        if ($value > self::LARGEST_OCTAL_IN_DECIMAL || $value < self::SMALLEST_OCTAL_IN_DECIMAL) {
+            return Functions::NAN();
+        }
+        $r = decoct($value);
+        $r = substr($r, -10);
+
+        return self::nbrConversionFormat($r, $places);
+    }
+
+    protected static function validateDecimal(string $value): string
+    {
+        if (strlen($value) > preg_match_all('/[-0123456789.]/', $value)) {
+            throw new Exception(Functions::VALUE());
+        }
+
+        return $value;
+    }
+}

+ 175 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php

@@ -0,0 +1,175 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class ConvertHex extends ConvertBase
+{
+    /**
+     * toBinary.
+     *
+     * Return a hex value as binary.
+     *
+     * Excel Function:
+     *        HEX2BIN(x[,places])
+     *
+     * @param array|string $value The hexadecimal number you want to convert.
+     *                      Number cannot contain more than 10 characters.
+     *                      The most significant bit of number is the sign bit (40th bit from the right).
+     *                      The remaining 9 bits are magnitude bits.
+     *                      Negative numbers are represented using two's-complement notation.
+     *                      If number is negative, HEX2BIN ignores places and returns a 10-character binary number.
+     *                      If number is negative, it cannot be less than FFFFFFFE00,
+     *                          and if number is positive, it cannot be greater than 1FF.
+     *                      If number is not a valid hexadecimal number, HEX2BIN returns the #NUM! error value.
+     *                      If HEX2BIN requires more than places characters, it returns the #NUM! error value.
+     *                      Or can be an array of values
+     * @param array|int $places The number of characters to use. If places is omitted,
+     *                          HEX2BIN uses the minimum number of characters necessary. Places
+     *                          is useful for padding the return value with leading 0s (zeros).
+     *                      If places is not an integer, it is truncated.
+     *                      If places is nonnumeric, HEX2BIN returns the #VALUE! error value.
+     *                      If places is negative, HEX2BIN returns the #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|string Result, or an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function toBinary($value, $places = null)
+    {
+        if (is_array($value) || is_array($places)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
+        }
+
+        try {
+            $value = self::validateValue($value);
+            $value = self::validateHex($value);
+            $places = self::validatePlaces($places);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $dec = self::toDecimal($value);
+
+        return ConvertDecimal::toBinary($dec, $places);
+    }
+
+    /**
+     * toDecimal.
+     *
+     * Return a hex value as decimal.
+     *
+     * Excel Function:
+     *        HEX2DEC(x)
+     *
+     * @param array|string $value The hexadecimal number you want to convert. This number cannot
+     *                          contain more than 10 characters (40 bits). The most significant
+     *                          bit of number is the sign bit. The remaining 39 bits are magnitude
+     *                          bits. Negative numbers are represented using two's-complement
+     *                          notation.
+     *                      If number is not a valid hexadecimal number, HEX2DEC returns the
+     *                          #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|string Result, or an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function toDecimal($value)
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        try {
+            $value = self::validateValue($value);
+            $value = self::validateHex($value);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        if (strlen($value) > 10) {
+            return Functions::NAN();
+        }
+
+        $binX = '';
+        foreach (str_split($value) as $char) {
+            $binX .= str_pad(base_convert($char, 16, 2), 4, '0', STR_PAD_LEFT);
+        }
+        if (strlen($binX) == 40 && $binX[0] == '1') {
+            for ($i = 0; $i < 40; ++$i) {
+                $binX[$i] = ($binX[$i] == '1' ? '0' : '1');
+            }
+
+            return (string) ((bindec($binX) + 1) * -1);
+        }
+
+        return (string) bindec($binX);
+    }
+
+    /**
+     * toOctal.
+     *
+     * Return a hex value as octal.
+     *
+     * Excel Function:
+     *        HEX2OCT(x[,places])
+     *
+     * @param array|string $value The hexadecimal number you want to convert. Number cannot
+     *                                    contain more than 10 characters. The most significant bit of
+     *                                    number is the sign bit. The remaining 39 bits are magnitude
+     *                                    bits. Negative numbers are represented using two's-complement
+     *                                    notation.
+     *                                    If number is negative, HEX2OCT ignores places and returns a
+     *                                    10-character octal number.
+     *                                    If number is negative, it cannot be less than FFE0000000, and
+     *                                    if number is positive, it cannot be greater than 1FFFFFFF.
+     *                                    If number is not a valid hexadecimal number, HEX2OCT returns
+     *                                    the #NUM! error value.
+     *                                    If HEX2OCT requires more than places characters, it returns
+     *                                    the #NUM! error value.
+     *                      Or can be an array of values
+     * @param array|int $places The number of characters to use. If places is omitted, HEX2OCT
+     *                                    uses the minimum number of characters necessary. Places is
+     *                                    useful for padding the return value with leading 0s (zeros).
+     *                                    If places is not an integer, it is truncated.
+     *                                    If places is nonnumeric, HEX2OCT returns the #VALUE! error
+     *                                    value.
+     *                                    If places is negative, HEX2OCT returns the #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|string Result, or an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function toOctal($value, $places = null)
+    {
+        if (is_array($value) || is_array($places)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
+        }
+
+        try {
+            $value = self::validateValue($value);
+            $value = self::validateHex($value);
+            $places = self::validatePlaces($places);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $decimal = self::toDecimal($value);
+
+        return ConvertDecimal::toOctal($decimal, $places);
+    }
+
+    protected static function validateHex(string $value): string
+    {
+        if (strlen($value) > preg_match_all('/[0123456789ABCDEF]/', $value)) {
+            throw new Exception(Functions::NAN());
+        }
+
+        return $value;
+    }
+}

+ 174 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php

@@ -0,0 +1,174 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class ConvertOctal extends ConvertBase
+{
+    /**
+     * toBinary.
+     *
+     * Return an octal value as binary.
+     *
+     * Excel Function:
+     *        OCT2BIN(x[,places])
+     *
+     * @param array|string $value The octal number you want to convert. Number may not
+     *                          contain more than 10 characters. The most significant
+     *                          bit of number is the sign bit. The remaining 29 bits
+     *                          are magnitude bits. Negative numbers are represented
+     *                          using two's-complement notation.
+     *                      If number is negative, OCT2BIN ignores places and returns
+     *                          a 10-character binary number.
+     *                      If number is negative, it cannot be less than 7777777000,
+     *                          and if number is positive, it cannot be greater than 777.
+     *                      If number is not a valid octal number, OCT2BIN returns
+     *                          the #NUM! error value.
+     *                      If OCT2BIN requires more than places characters, it
+     *                          returns the #NUM! error value.
+     *                      Or can be an array of values
+     * @param array|int $places The number of characters to use. If places is omitted,
+     *                          OCT2BIN uses the minimum number of characters necessary.
+     *                          Places is useful for padding the return value with
+     *                          leading 0s (zeros).
+     *                      If places is not an integer, it is truncated.
+     *                      If places is nonnumeric, OCT2BIN returns the #VALUE!
+     *                          error value.
+     *                      If places is negative, OCT2BIN returns the #NUM! error
+     *                          value.
+     *                      Or can be an array of values
+     *
+     * @return array|string Result, or an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function toBinary($value, $places = null)
+    {
+        if (is_array($value) || is_array($places)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
+        }
+
+        try {
+            $value = self::validateValue($value);
+            $value = self::validateOctal($value);
+            $places = self::validatePlaces($places);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        return ConvertDecimal::toBinary(self::toDecimal($value), $places);
+    }
+
+    /**
+     * toDecimal.
+     *
+     * Return an octal value as decimal.
+     *
+     * Excel Function:
+     *        OCT2DEC(x)
+     *
+     * @param array|string $value The octal number you want to convert. Number may not contain
+     *                          more than 10 octal characters (30 bits). The most significant
+     *                          bit of number is the sign bit. The remaining 29 bits are
+     *                          magnitude bits. Negative numbers are represented using
+     *                          two's-complement notation.
+     *                      If number is not a valid octal number, OCT2DEC returns the
+     *                          #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|string Result, or an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function toDecimal($value)
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        try {
+            $value = self::validateValue($value);
+            $value = self::validateOctal($value);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $binX = '';
+        foreach (str_split($value) as $char) {
+            $binX .= str_pad(decbin((int) $char), 3, '0', STR_PAD_LEFT);
+        }
+        if (strlen($binX) == 30 && $binX[0] == '1') {
+            for ($i = 0; $i < 30; ++$i) {
+                $binX[$i] = ($binX[$i] == '1' ? '0' : '1');
+            }
+
+            return (string) ((bindec($binX) + 1) * -1);
+        }
+
+        return (string) bindec($binX);
+    }
+
+    /**
+     * toHex.
+     *
+     * Return an octal value as hex.
+     *
+     * Excel Function:
+     *        OCT2HEX(x[,places])
+     *
+     * @param array|string $value The octal number you want to convert. Number may not contain
+     *                          more than 10 octal characters (30 bits). The most significant
+     *                          bit of number is the sign bit. The remaining 29 bits are
+     *                          magnitude bits. Negative numbers are represented using
+     *                          two's-complement notation.
+     *                      If number is negative, OCT2HEX ignores places and returns a
+     *                          10-character hexadecimal number.
+     *                      If number is not a valid octal number, OCT2HEX returns the
+     *                          #NUM! error value.
+     *                      If OCT2HEX requires more than places characters, it returns
+     *                          the #NUM! error value.
+     *                      Or can be an array of values
+     * @param array|int $places The number of characters to use. If places is omitted, OCT2HEX
+     *                          uses the minimum number of characters necessary. Places is useful
+     *                          for padding the return value with leading 0s (zeros).
+     *                      If places is not an integer, it is truncated.
+     *                      If places is nonnumeric, OCT2HEX returns the #VALUE! error value.
+     *                      If places is negative, OCT2HEX returns the #NUM! error value.
+     *                      Or can be an array of values
+     *
+     * @return array|string Result, or an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function toHex($value, $places = null)
+    {
+        if (is_array($value) || is_array($places)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places);
+        }
+
+        try {
+            $value = self::validateValue($value);
+            $value = self::validateOctal($value);
+            $places = self::validatePlaces($places);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $hexVal = strtoupper(dechex((int) self::toDecimal($value)));
+        $hexVal = (PHP_INT_SIZE === 4 && strlen($value) === 10 && $value[0] >= '4') ? "FF{$hexVal}" : $hexVal;
+
+        return self::nbrConversionFormat($hexVal, $places);
+    }
+
+    protected static function validateOctal(string $value): string
+    {
+        $numDigits = (int) preg_match_all('/[01234567]/', $value);
+        if (strlen($value) > $numDigits || $numDigits > 10) {
+            throw new Exception(Functions::NAN());
+        }
+
+        return $value;
+    }
+}

+ 693 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php

@@ -0,0 +1,693 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class ConvertUOM
+{
+    use ArrayEnabled;
+
+    public const CATEGORY_WEIGHT_AND_MASS = 'Weight and Mass';
+    public const CATEGORY_DISTANCE = 'Distance';
+    public const CATEGORY_TIME = 'Time';
+    public const CATEGORY_PRESSURE = 'Pressure';
+    public const CATEGORY_FORCE = 'Force';
+    public const CATEGORY_ENERGY = 'Energy';
+    public const CATEGORY_POWER = 'Power';
+    public const CATEGORY_MAGNETISM = 'Magnetism';
+    public const CATEGORY_TEMPERATURE = 'Temperature';
+    public const CATEGORY_VOLUME = 'Volume and Liquid Measure';
+    public const CATEGORY_AREA = 'Area';
+    public const CATEGORY_INFORMATION = 'Information';
+    public const CATEGORY_SPEED = 'Speed';
+
+    /**
+     * Details of the Units of measure that can be used in CONVERTUOM().
+     *
+     * @var mixed[]
+     */
+    private static $conversionUnits = [
+        // Weight and Mass
+        'g' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Gram', 'AllowPrefix' => true],
+        'sg' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Slug', 'AllowPrefix' => false],
+        'lbm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Pound mass (avoirdupois)', 'AllowPrefix' => false],
+        'u' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U (atomic mass unit)', 'AllowPrefix' => true],
+        'ozm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Ounce mass (avoirdupois)', 'AllowPrefix' => false],
+        'grain' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Grain', 'AllowPrefix' => false],
+        'cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U.S. (short) hundredweight', 'AllowPrefix' => false],
+        'shweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U.S. (short) hundredweight', 'AllowPrefix' => false],
+        'uk_cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false],
+        'lcwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false],
+        'hweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false],
+        'stone' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Stone', 'AllowPrefix' => false],
+        'ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Ton', 'AllowPrefix' => false],
+        'uk_ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false],
+        'LTON' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false],
+        'brton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false],
+        // Distance
+        'm' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Meter', 'AllowPrefix' => true],
+        'mi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Statute mile', 'AllowPrefix' => false],
+        'Nmi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Nautical mile', 'AllowPrefix' => false],
+        'in' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Inch', 'AllowPrefix' => false],
+        'ft' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Foot', 'AllowPrefix' => false],
+        'yd' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Yard', 'AllowPrefix' => false],
+        'ang' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Angstrom', 'AllowPrefix' => true],
+        'ell' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Ell', 'AllowPrefix' => false],
+        'ly' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Light Year', 'AllowPrefix' => false],
+        'parsec' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Parsec', 'AllowPrefix' => false],
+        'pc' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Parsec', 'AllowPrefix' => false],
+        'Pica' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false],
+        'Picapt' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false],
+        'pica' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/6 in)', 'AllowPrefix' => false],
+        'survey_mi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'U.S survey mile (statute mile)', 'AllowPrefix' => false],
+        // Time
+        'yr' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Year', 'AllowPrefix' => false],
+        'day' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Day', 'AllowPrefix' => false],
+        'd' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Day', 'AllowPrefix' => false],
+        'hr' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Hour', 'AllowPrefix' => false],
+        'mn' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Minute', 'AllowPrefix' => false],
+        'min' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Minute', 'AllowPrefix' => false],
+        'sec' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Second', 'AllowPrefix' => true],
+        's' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Second', 'AllowPrefix' => true],
+        // Pressure
+        'Pa' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Pascal', 'AllowPrefix' => true],
+        'p' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Pascal', 'AllowPrefix' => true],
+        'atm' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true],
+        'at' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true],
+        'mmHg' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'mm of Mercury', 'AllowPrefix' => true],
+        'psi' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'PSI', 'AllowPrefix' => true],
+        'Torr' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Torr', 'AllowPrefix' => true],
+        // Force
+        'N' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Newton', 'AllowPrefix' => true],
+        'dyn' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Dyne', 'AllowPrefix' => true],
+        'dy' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Dyne', 'AllowPrefix' => true],
+        'lbf' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Pound force', 'AllowPrefix' => false],
+        'pond' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Pond', 'AllowPrefix' => true],
+        // Energy
+        'J' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Joule', 'AllowPrefix' => true],
+        'e' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Erg', 'AllowPrefix' => true],
+        'c' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Thermodynamic calorie', 'AllowPrefix' => true],
+        'cal' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'IT calorie', 'AllowPrefix' => true],
+        'eV' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Electron volt', 'AllowPrefix' => true],
+        'ev' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Electron volt', 'AllowPrefix' => true],
+        'HPh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false],
+        'hh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false],
+        'Wh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true],
+        'wh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true],
+        'flb' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Foot-pound', 'AllowPrefix' => false],
+        'BTU' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'BTU', 'AllowPrefix' => false],
+        'btu' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'BTU', 'AllowPrefix' => false],
+        // Power
+        'HP' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Horsepower', 'AllowPrefix' => false],
+        'h' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Horsepower', 'AllowPrefix' => false],
+        'W' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true],
+        'w' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true],
+        'PS' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Pferdestärke', 'AllowPrefix' => false],
+        'T' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Tesla', 'AllowPrefix' => true],
+        'ga' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Gauss', 'AllowPrefix' => true],
+        // Temperature
+        'C' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Celsius', 'AllowPrefix' => false],
+        'cel' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Celsius', 'AllowPrefix' => false],
+        'F' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Fahrenheit', 'AllowPrefix' => false],
+        'fah' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Fahrenheit', 'AllowPrefix' => false],
+        'K' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Kelvin', 'AllowPrefix' => false],
+        'kel' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Kelvin', 'AllowPrefix' => false],
+        'Rank' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Rankine', 'AllowPrefix' => false],
+        'Reau' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Réaumur', 'AllowPrefix' => false],
+        // Volume
+        'l' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true],
+        'L' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true],
+        'lt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true],
+        'tsp' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Teaspoon', 'AllowPrefix' => false],
+        'tspm' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Modern Teaspoon', 'AllowPrefix' => false],
+        'tbs' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Tablespoon', 'AllowPrefix' => false],
+        'oz' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Fluid Ounce', 'AllowPrefix' => false],
+        'cup' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cup', 'AllowPrefix' => false],
+        'pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false],
+        'us_pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false],
+        'uk_pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.K. Pint', 'AllowPrefix' => false],
+        'qt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Quart', 'AllowPrefix' => false],
+        'uk_qt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Imperial Quart (UK)', 'AllowPrefix' => false],
+        'gal' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gallon', 'AllowPrefix' => false],
+        'uk_gal' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Imperial Gallon (UK)', 'AllowPrefix' => false],
+        'ang3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Angstrom', 'AllowPrefix' => true],
+        'ang^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Angstrom', 'AllowPrefix' => true],
+        'barrel' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'US Oil Barrel', 'AllowPrefix' => false],
+        'bushel' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'US Bushel', 'AllowPrefix' => false],
+        'in3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Inch', 'AllowPrefix' => false],
+        'in^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Inch', 'AllowPrefix' => false],
+        'ft3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Foot', 'AllowPrefix' => false],
+        'ft^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Foot', 'AllowPrefix' => false],
+        'ly3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Light Year', 'AllowPrefix' => false],
+        'ly^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Light Year', 'AllowPrefix' => false],
+        'm3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Meter', 'AllowPrefix' => true],
+        'm^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Meter', 'AllowPrefix' => true],
+        'mi3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Mile', 'AllowPrefix' => false],
+        'mi^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Mile', 'AllowPrefix' => false],
+        'yd3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Yard', 'AllowPrefix' => false],
+        'yd^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Yard', 'AllowPrefix' => false],
+        'Nmi3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Nautical Mile', 'AllowPrefix' => false],
+        'Nmi^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Nautical Mile', 'AllowPrefix' => false],
+        'Pica3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false],
+        'Pica^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false],
+        'Picapt3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false],
+        'Picapt^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false],
+        'GRT' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gross Registered Ton', 'AllowPrefix' => false],
+        'regton' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gross Registered Ton', 'AllowPrefix' => false],
+        'MTON' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Measurement Ton (Freight Ton)', 'AllowPrefix' => false],
+        // Area
+        'ha' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Hectare', 'AllowPrefix' => true],
+        'uk_acre' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'International Acre', 'AllowPrefix' => false],
+        'us_acre' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'US Survey/Statute Acre', 'AllowPrefix' => false],
+        'ang2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Angstrom', 'AllowPrefix' => true],
+        'ang^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Angstrom', 'AllowPrefix' => true],
+        'ar' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Are', 'AllowPrefix' => true],
+        'ft2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Feet', 'AllowPrefix' => false],
+        'ft^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Feet', 'AllowPrefix' => false],
+        'in2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Inches', 'AllowPrefix' => false],
+        'in^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Inches', 'AllowPrefix' => false],
+        'ly2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Light Years', 'AllowPrefix' => false],
+        'ly^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Light Years', 'AllowPrefix' => false],
+        'm2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Meters', 'AllowPrefix' => true],
+        'm^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Meters', 'AllowPrefix' => true],
+        'Morgen' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Morgen', 'AllowPrefix' => false],
+        'mi2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Miles', 'AllowPrefix' => false],
+        'mi^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Miles', 'AllowPrefix' => false],
+        'Nmi2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Nautical Miles', 'AllowPrefix' => false],
+        'Nmi^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Nautical Miles', 'AllowPrefix' => false],
+        'Pica2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false],
+        'Pica^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false],
+        'Picapt2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false],
+        'Picapt^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false],
+        'yd2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Yards', 'AllowPrefix' => false],
+        'yd^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Yards', 'AllowPrefix' => false],
+        // Information
+        'byte' => ['Group' => self::CATEGORY_INFORMATION, 'Unit Name' => 'Byte', 'AllowPrefix' => true],
+        'bit' => ['Group' => self::CATEGORY_INFORMATION, 'Unit Name' => 'Bit', 'AllowPrefix' => true],
+        // Speed
+        'm/s' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per second', 'AllowPrefix' => true],
+        'm/sec' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per second', 'AllowPrefix' => true],
+        'm/h' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per hour', 'AllowPrefix' => true],
+        'm/hr' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per hour', 'AllowPrefix' => true],
+        'mph' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Miles per hour', 'AllowPrefix' => false],
+        'admkn' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Admiralty Knot', 'AllowPrefix' => false],
+        'kn' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Knot', 'AllowPrefix' => false],
+    ];
+
+    /**
+     * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
+     *
+     * @var mixed[]
+     */
+    private static $conversionMultipliers = [
+        'Y' => ['multiplier' => 1E24, 'name' => 'yotta'],
+        'Z' => ['multiplier' => 1E21, 'name' => 'zetta'],
+        'E' => ['multiplier' => 1E18, 'name' => 'exa'],
+        'P' => ['multiplier' => 1E15, 'name' => 'peta'],
+        'T' => ['multiplier' => 1E12, 'name' => 'tera'],
+        'G' => ['multiplier' => 1E9, 'name' => 'giga'],
+        'M' => ['multiplier' => 1E6, 'name' => 'mega'],
+        'k' => ['multiplier' => 1E3, 'name' => 'kilo'],
+        'h' => ['multiplier' => 1E2, 'name' => 'hecto'],
+        'e' => ['multiplier' => 1E1, 'name' => 'dekao'],
+        'da' => ['multiplier' => 1E1, 'name' => 'dekao'],
+        'd' => ['multiplier' => 1E-1, 'name' => 'deci'],
+        'c' => ['multiplier' => 1E-2, 'name' => 'centi'],
+        'm' => ['multiplier' => 1E-3, 'name' => 'milli'],
+        'u' => ['multiplier' => 1E-6, 'name' => 'micro'],
+        'n' => ['multiplier' => 1E-9, 'name' => 'nano'],
+        'p' => ['multiplier' => 1E-12, 'name' => 'pico'],
+        'f' => ['multiplier' => 1E-15, 'name' => 'femto'],
+        'a' => ['multiplier' => 1E-18, 'name' => 'atto'],
+        'z' => ['multiplier' => 1E-21, 'name' => 'zepto'],
+        'y' => ['multiplier' => 1E-24, 'name' => 'yocto'],
+    ];
+
+    /**
+     * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
+     *
+     * @var mixed[]
+     */
+    private static $binaryConversionMultipliers = [
+        'Yi' => ['multiplier' => 2 ** 80, 'name' => 'yobi'],
+        'Zi' => ['multiplier' => 2 ** 70, 'name' => 'zebi'],
+        'Ei' => ['multiplier' => 2 ** 60, 'name' => 'exbi'],
+        'Pi' => ['multiplier' => 2 ** 50, 'name' => 'pebi'],
+        'Ti' => ['multiplier' => 2 ** 40, 'name' => 'tebi'],
+        'Gi' => ['multiplier' => 2 ** 30, 'name' => 'gibi'],
+        'Mi' => ['multiplier' => 2 ** 20, 'name' => 'mebi'],
+        'ki' => ['multiplier' => 2 ** 10, 'name' => 'kibi'],
+    ];
+
+    /**
+     * Details of the Units of measure conversion factors, organised by group.
+     *
+     * @var mixed[]
+     */
+    private static $unitConversions = [
+        // Conversion uses gram (g) as an intermediate unit
+        self::CATEGORY_WEIGHT_AND_MASS => [
+            'g' => 1.0,
+            'sg' => 6.85217658567918E-05,
+            'lbm' => 2.20462262184878E-03,
+            'u' => 6.02214179421676E+23,
+            'ozm' => 3.52739619495804E-02,
+            'grain' => 1.54323583529414E+01,
+            'cwt' => 2.20462262184878E-05,
+            'shweight' => 2.20462262184878E-05,
+            'uk_cwt' => 1.96841305522212E-05,
+            'lcwt' => 1.96841305522212E-05,
+            'hweight' => 1.96841305522212E-05,
+            'stone' => 1.57473044417770E-04,
+            'ton' => 1.10231131092439E-06,
+            'uk_ton' => 9.84206527611061E-07,
+            'LTON' => 9.84206527611061E-07,
+            'brton' => 9.84206527611061E-07,
+        ],
+        // Conversion uses meter (m) as an intermediate unit
+        self::CATEGORY_DISTANCE => [
+            'm' => 1.0,
+            'mi' => 6.21371192237334E-04,
+            'Nmi' => 5.39956803455724E-04,
+            'in' => 3.93700787401575E+01,
+            'ft' => 3.28083989501312E+00,
+            'yd' => 1.09361329833771E+00,
+            'ang' => 1.0E+10,
+            'ell' => 8.74890638670166E-01,
+            'ly' => 1.05700083402462E-16,
+            'parsec' => 3.24077928966473E-17,
+            'pc' => 3.24077928966473E-17,
+            'Pica' => 2.83464566929134E+03,
+            'Picapt' => 2.83464566929134E+03,
+            'pica' => 2.36220472440945E+02,
+            'survey_mi' => 6.21369949494950E-04,
+        ],
+        // Conversion uses second (s) as an intermediate unit
+        self::CATEGORY_TIME => [
+            'yr' => 3.16880878140289E-08,
+            'day' => 1.15740740740741E-05,
+            'd' => 1.15740740740741E-05,
+            'hr' => 2.77777777777778E-04,
+            'mn' => 1.66666666666667E-02,
+            'min' => 1.66666666666667E-02,
+            'sec' => 1.0,
+            's' => 1.0,
+        ],
+        // Conversion uses Pascal (Pa) as an intermediate unit
+        self::CATEGORY_PRESSURE => [
+            'Pa' => 1.0,
+            'p' => 1.0,
+            'atm' => 9.86923266716013E-06,
+            'at' => 9.86923266716013E-06,
+            'mmHg' => 7.50063755419211E-03,
+            'psi' => 1.45037737730209E-04,
+            'Torr' => 7.50061682704170E-03,
+        ],
+        // Conversion uses Newton (N) as an intermediate unit
+        self::CATEGORY_FORCE => [
+            'N' => 1.0,
+            'dyn' => 1.0E+5,
+            'dy' => 1.0E+5,
+            'lbf' => 2.24808923655339E-01,
+            'pond' => 1.01971621297793E+02,
+        ],
+        // Conversion uses Joule (J) as an intermediate unit
+        self::CATEGORY_ENERGY => [
+            'J' => 1.0,
+            'e' => 9.99999519343231E+06,
+            'c' => 2.39006249473467E-01,
+            'cal' => 2.38846190642017E-01,
+            'eV' => 6.24145700000000E+18,
+            'ev' => 6.24145700000000E+18,
+            'HPh' => 3.72506430801000E-07,
+            'hh' => 3.72506430801000E-07,
+            'Wh' => 2.77777916238711E-04,
+            'wh' => 2.77777916238711E-04,
+            'flb' => 2.37304222192651E+01,
+            'BTU' => 9.47815067349015E-04,
+            'btu' => 9.47815067349015E-04,
+        ],
+        // Conversion uses Horsepower (HP) as an intermediate unit
+        self::CATEGORY_POWER => [
+            'HP' => 1.0,
+            'h' => 1.0,
+            'W' => 7.45699871582270E+02,
+            'w' => 7.45699871582270E+02,
+            'PS' => 1.01386966542400E+00,
+        ],
+        // Conversion uses Tesla (T) as an intermediate unit
+        self::CATEGORY_MAGNETISM => [
+            'T' => 1.0,
+            'ga' => 10000.0,
+        ],
+        // Conversion uses litre (l) as an intermediate unit
+        self::CATEGORY_VOLUME => [
+            'l' => 1.0,
+            'L' => 1.0,
+            'lt' => 1.0,
+            'tsp' => 2.02884136211058E+02,
+            'tspm' => 2.0E+02,
+            'tbs' => 6.76280454036860E+01,
+            'oz' => 3.38140227018430E+01,
+            'cup' => 4.22675283773038E+00,
+            'pt' => 2.11337641886519E+00,
+            'us_pt' => 2.11337641886519E+00,
+            'uk_pt' => 1.75975398639270E+00,
+            'qt' => 1.05668820943259E+00,
+            'uk_qt' => 8.79876993196351E-01,
+            'gal' => 2.64172052358148E-01,
+            'uk_gal' => 2.19969248299088E-01,
+            'ang3' => 1.0E+27,
+            'ang^3' => 1.0E+27,
+            'barrel' => 6.28981077043211E-03,
+            'bushel' => 2.83775932584017E-02,
+            'in3' => 6.10237440947323E+01,
+            'in^3' => 6.10237440947323E+01,
+            'ft3' => 3.53146667214886E-02,
+            'ft^3' => 3.53146667214886E-02,
+            'ly3' => 1.18093498844171E-51,
+            'ly^3' => 1.18093498844171E-51,
+            'm3' => 1.0E-03,
+            'm^3' => 1.0E-03,
+            'mi3' => 2.39912758578928E-13,
+            'mi^3' => 2.39912758578928E-13,
+            'yd3' => 1.30795061931439E-03,
+            'yd^3' => 1.30795061931439E-03,
+            'Nmi3' => 1.57426214685811E-13,
+            'Nmi^3' => 1.57426214685811E-13,
+            'Pica3' => 2.27769904358706E+07,
+            'Pica^3' => 2.27769904358706E+07,
+            'Picapt3' => 2.27769904358706E+07,
+            'Picapt^3' => 2.27769904358706E+07,
+            'GRT' => 3.53146667214886E-04,
+            'regton' => 3.53146667214886E-04,
+            'MTON' => 8.82866668037215E-04,
+        ],
+        // Conversion uses hectare (ha) as an intermediate unit
+        self::CATEGORY_AREA => [
+            'ha' => 1.0,
+            'uk_acre' => 2.47105381467165E+00,
+            'us_acre' => 2.47104393046628E+00,
+            'ang2' => 1.0E+24,
+            'ang^2' => 1.0E+24,
+            'ar' => 1.0E+02,
+            'ft2' => 1.07639104167097E+05,
+            'ft^2' => 1.07639104167097E+05,
+            'in2' => 1.55000310000620E+07,
+            'in^2' => 1.55000310000620E+07,
+            'ly2' => 1.11725076312873E-28,
+            'ly^2' => 1.11725076312873E-28,
+            'm2' => 1.0E+04,
+            'm^2' => 1.0E+04,
+            'Morgen' => 4.0E+00,
+            'mi2' => 3.86102158542446E-03,
+            'mi^2' => 3.86102158542446E-03,
+            'Nmi2' => 2.91553349598123E-03,
+            'Nmi^2' => 2.91553349598123E-03,
+            'Pica2' => 8.03521607043214E+10,
+            'Pica^2' => 8.03521607043214E+10,
+            'Picapt2' => 8.03521607043214E+10,
+            'Picapt^2' => 8.03521607043214E+10,
+            'yd2' => 1.19599004630108E+04,
+            'yd^2' => 1.19599004630108E+04,
+        ],
+        // Conversion uses bit (bit) as an intermediate unit
+        self::CATEGORY_INFORMATION => [
+            'bit' => 1.0,
+            'byte' => 0.125,
+        ],
+        // Conversion uses Meters per Second (m/s) as an intermediate unit
+        self::CATEGORY_SPEED => [
+            'm/s' => 1.0,
+            'm/sec' => 1.0,
+            'm/h' => 3.60E+03,
+            'm/hr' => 3.60E+03,
+            'mph' => 2.23693629205440E+00,
+            'admkn' => 1.94260256941567E+00,
+            'kn' => 1.94384449244060E+00,
+        ],
+    ];
+
+    /**
+     *    getConversionGroups
+     * Returns a list of the different conversion groups for UOM conversions.
+     *
+     * @return array
+     */
+    public static function getConversionCategories()
+    {
+        $conversionGroups = [];
+        foreach (self::$conversionUnits as $conversionUnit) {
+            $conversionGroups[] = $conversionUnit['Group'];
+        }
+
+        return array_merge(array_unique($conversionGroups));
+    }
+
+    /**
+     *    getConversionGroupUnits
+     * Returns an array of units of measure, for a specified conversion group, or for all groups.
+     *
+     * @param string $category The group whose units of measure you want to retrieve
+     *
+     * @return array
+     */
+    public static function getConversionCategoryUnits($category = null)
+    {
+        $conversionGroups = [];
+        foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) {
+            if (($category === null) || ($conversionGroup['Group'] == $category)) {
+                $conversionGroups[$conversionGroup['Group']][] = $conversionUnit;
+            }
+        }
+
+        return $conversionGroups;
+    }
+
+    /**
+     * getConversionGroupUnitDetails.
+     *
+     * @param string $category The group whose units of measure you want to retrieve
+     *
+     * @return array
+     */
+    public static function getConversionCategoryUnitDetails($category = null)
+    {
+        $conversionGroups = [];
+        foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) {
+            if (($category === null) || ($conversionGroup['Group'] == $category)) {
+                $conversionGroups[$conversionGroup['Group']][] = [
+                    'unit' => $conversionUnit,
+                    'description' => $conversionGroup['Unit Name'],
+                ];
+            }
+        }
+
+        return $conversionGroups;
+    }
+
+    /**
+     *    getConversionMultipliers
+     * Returns an array of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
+     *
+     * @return mixed[]
+     */
+    public static function getConversionMultipliers()
+    {
+        return self::$conversionMultipliers;
+    }
+
+    /**
+     *    getBinaryConversionMultipliers
+     * Returns an array of the additional Multiplier prefixes that can be used with Information Units of Measure in CONVERTUOM().
+     *
+     * @return mixed[]
+     */
+    public static function getBinaryConversionMultipliers()
+    {
+        return self::$binaryConversionMultipliers;
+    }
+
+    /**
+     * CONVERT.
+     *
+     * Converts a number from one measurement system to another.
+     *    For example, CONVERT can translate a table of distances in miles to a table of distances
+     * in kilometers.
+     *
+     *    Excel Function:
+     *        CONVERT(value,fromUOM,toUOM)
+     *
+     * @param array|float|int|string $value the value in fromUOM to convert
+     *                      Or can be an array of values
+     * @param array|string $fromUOM the units for value
+     *                      Or can be an array of values
+     * @param array|string $toUOM the units for the result
+     *                      Or can be an array of values
+     *
+     * @return array|float|string Result, or a string containing an error
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function CONVERT($value, $fromUOM, $toUOM)
+    {
+        if (is_array($value) || is_array($fromUOM) || is_array($toUOM)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $fromUOM, $toUOM);
+        }
+
+        if (!is_numeric($value)) {
+            return Functions::VALUE();
+        }
+
+        try {
+            [$fromUOM, $fromCategory, $fromMultiplier] = self::getUOMDetails($fromUOM);
+            [$toUOM, $toCategory, $toMultiplier] = self::getUOMDetails($toUOM);
+        } catch (Exception $e) {
+            return Functions::NA();
+        }
+
+        if ($fromCategory !== $toCategory) {
+            return Functions::NA();
+        }
+
+        // @var float $value
+        $value *= $fromMultiplier;
+
+        if (($fromUOM === $toUOM) && ($fromMultiplier === $toMultiplier)) {
+            //    We've already factored $fromMultiplier into the value, so we need
+            //        to reverse it again
+            return $value / $fromMultiplier;
+        } elseif ($fromUOM === $toUOM) {
+            return $value / $toMultiplier;
+        } elseif ($fromCategory === self::CATEGORY_TEMPERATURE) {
+            return self::convertTemperature($fromUOM, $toUOM, $value);
+        }
+
+        $baseValue = $value * (1.0 / self::$unitConversions[$fromCategory][$fromUOM]);
+
+        return ($baseValue * self::$unitConversions[$fromCategory][$toUOM]) / $toMultiplier;
+    }
+
+    private static function getUOMDetails(string $uom)
+    {
+        if (isset(self::$conversionUnits[$uom])) {
+            $unitCategory = self::$conversionUnits[$uom]['Group'];
+
+            return [$uom, $unitCategory, 1.0];
+        }
+
+        // Check 1-character standard metric multiplier prefixes
+        $multiplierType = substr($uom, 0, 1);
+        $uom = substr($uom, 1);
+        if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) {
+            if (self::$conversionUnits[$uom]['AllowPrefix'] === false) {
+                throw new Exception('Prefix not allowed for UoM');
+            }
+            $unitCategory = self::$conversionUnits[$uom]['Group'];
+
+            return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']];
+        }
+
+        $multiplierType .= substr($uom, 0, 1);
+        $uom = substr($uom, 1);
+
+        // Check 2-character standard metric multiplier prefixes
+        if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) {
+            if (self::$conversionUnits[$uom]['AllowPrefix'] === false) {
+                throw new Exception('Prefix not allowed for UoM');
+            }
+            $unitCategory = self::$conversionUnits[$uom]['Group'];
+
+            return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']];
+        }
+
+        // Check 2-character binary multiplier prefixes
+        if (isset(self::$conversionUnits[$uom], self::$binaryConversionMultipliers[$multiplierType])) {
+            if (self::$conversionUnits[$uom]['AllowPrefix'] === false) {
+                throw new Exception('Prefix not allowed for UoM');
+            }
+            $unitCategory = self::$conversionUnits[$uom]['Group'];
+            if ($unitCategory !== 'Information') {
+                throw new Exception('Binary Prefix is only allowed for Information UoM');
+            }
+
+            return [$uom, $unitCategory, self::$binaryConversionMultipliers[$multiplierType]['multiplier']];
+        }
+
+        throw new Exception('UoM Not Found');
+    }
+
+    /**
+     * @param float|int $value
+     *
+     * @return float|int
+     */
+    protected static function convertTemperature(string $fromUOM, string $toUOM, $value)
+    {
+        $fromUOM = self::resolveTemperatureSynonyms($fromUOM);
+        $toUOM = self::resolveTemperatureSynonyms($toUOM);
+
+        if ($fromUOM === $toUOM) {
+            return $value;
+        }
+
+        // Convert to Kelvin
+        switch ($fromUOM) {
+            case 'F':
+                $value = ($value - 32) / 1.8 + 273.15;
+
+                break;
+            case 'C':
+                $value += 273.15;
+
+                break;
+            case 'Rank':
+                $value /= 1.8;
+
+                break;
+            case 'Reau':
+                $value = $value * 1.25 + 273.15;
+
+                break;
+        }
+
+        // Convert from Kelvin
+        switch ($toUOM) {
+            case 'F':
+                $value = ($value - 273.15) * 1.8 + 32.00;
+
+                break;
+            case 'C':
+                $value -= 273.15;
+
+                break;
+            case 'Rank':
+                $value *= 1.8;
+
+                break;
+            case 'Reau':
+                $value = ($value - 273.15) * 0.80000;
+
+                break;
+        }
+
+        return $value;
+    }
+
+    private static function resolveTemperatureSynonyms(string $uom)
+    {
+        switch ($uom) {
+            case 'fah':
+                return 'F';
+            case 'cel':
+                return 'C';
+            case 'kel':
+                return 'K';
+        }
+
+        return $uom;
+    }
+}

+ 33 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class EngineeringValidations
+{
+    /**
+     * @param mixed $value
+     */
+    public static function validateFloat($value): float
+    {
+        if (!is_numeric($value)) {
+            throw new Exception(Functions::VALUE());
+        }
+
+        return (float) $value;
+    }
+
+    /**
+     * @param mixed $value
+     */
+    public static function validateInt($value): int
+    {
+        if (!is_numeric($value)) {
+            throw new Exception(Functions::VALUE());
+        }
+
+        return (int) floor((float) $value);
+    }
+}

+ 104 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Erf.php

@@ -0,0 +1,104 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class Erf
+{
+    use ArrayEnabled;
+
+    private static $twoSqrtPi = 1.128379167095512574;
+
+    /**
+     * ERF.
+     *
+     * Returns the error function integrated between the lower and upper bound arguments.
+     *
+     *    Note: In Excel 2007 or earlier, if you input a negative value for the upper or lower bound arguments,
+     *            the function would return a #NUM! error. However, in Excel 2010, the function algorithm was
+     *            improved, so that it can now calculate the function for both positive and negative ranges.
+     *            PhpSpreadsheet follows Excel 2010 behaviour, and accepts negative arguments.
+     *
+     *    Excel Function:
+     *        ERF(lower[,upper])
+     *
+     * @param mixed $lower Lower bound float for integrating ERF
+     *                      Or can be an array of values
+     * @param mixed $upper Upper bound float for integrating ERF.
+     *                           If omitted, ERF integrates between zero and lower_limit
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function ERF($lower, $upper = null)
+    {
+        if (is_array($lower) || is_array($upper)) {
+            return self::evaluateArrayArguments([self::class, __FUNCTION__], $lower, $upper);
+        }
+
+        if (is_numeric($lower)) {
+            if ($upper === null) {
+                return self::erfValue($lower);
+            }
+            if (is_numeric($upper)) {
+                return self::erfValue($upper) - self::erfValue($lower);
+            }
+        }
+
+        return Functions::VALUE();
+    }
+
+    /**
+     * ERFPRECISE.
+     *
+     * Returns the error function integrated between the lower and upper bound arguments.
+     *
+     *    Excel Function:
+     *        ERF.PRECISE(limit)
+     *
+     * @param mixed $limit Float bound for integrating ERF, other bound is zero
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function ERFPRECISE($limit)
+    {
+        if (is_array($limit)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $limit);
+        }
+
+        return self::ERF($limit);
+    }
+
+    //
+    //    Private method to calculate the erf value
+    //
+    public static function erfValue($value)
+    {
+        if (abs($value) > 2.2) {
+            return 1 - ErfC::ERFC($value);
+        }
+        $sum = $term = $value;
+        $xsqr = ($value * $value);
+        $j = 1;
+        do {
+            $term *= $xsqr / $j;
+            $sum -= $term / (2 * $j + 1);
+            ++$j;
+            $term *= $xsqr / $j;
+            $sum += $term / (2 * $j + 1);
+            ++$j;
+            if ($sum == 0.0) {
+                break;
+            }
+        } while (abs($term / $sum) > Functions::PRECISION);
+
+        return self::$twoSqrtPi * $sum;
+    }
+}

+ 76 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Engineering;
+
+use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class ErfC
+{
+    use ArrayEnabled;
+
+    /**
+     * ERFC.
+     *
+     *    Returns the complementary ERF function integrated between x and infinity
+     *
+     *    Note: In Excel 2007 or earlier, if you input a negative value for the lower bound argument,
+     *        the function would return a #NUM! error. However, in Excel 2010, the function algorithm was
+     *        improved, so that it can now calculate the function for both positive and negative x values.
+     *            PhpSpreadsheet follows Excel 2010 behaviour, and accepts nagative arguments.
+     *
+     *    Excel Function:
+     *        ERFC(x)
+     *
+     * @param mixed $value The float lower bound for integrating ERFC
+     *                      Or can be an array of values
+     *
+     * @return array|float|string
+     *         If an array of numbers is passed as an argument, then the returned result will also be an array
+     *            with the same dimensions
+     */
+    public static function ERFC($value)
+    {
+        if (is_array($value)) {
+            return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+        }
+
+        if (is_numeric($value)) {
+            return self::erfcValue($value);
+        }
+
+        return Functions::VALUE();
+    }
+
+    //
+    //    Private method to calculate the erfc value
+    //
+    private static $oneSqrtPi = 0.564189583547756287;
+
+    private static function erfcValue($value)
+    {
+        if (abs($value) < 2.2) {
+            return 1 - Erf::erfValue($value);
+        }
+        if ($value < 0) {
+            return 2 - self::erfcValue(-$value);
+        }
+        $a = $n = 1;
+        $b = $c = $value;
+        $d = ($value * $value) + 0.5;
+        $q1 = $q2 = $b / $d;
+        do {
+            $t = $a * $n + $b * $value;
+            $a = $b;
+            $b = $t;
+            $t = $c * $n + $d * $value;
+            $c = $d;
+            $d = $t;
+            $n += 0.5;
+            $q1 = $q2;
+            $q2 = $b / $d;
+        } while ((abs($q1 - $q2) / $q2) > Functions::PRECISION);
+
+        return self::$oneSqrtPi * exp(-$value * $value) * $q2;
+    }
+}

+ 26 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation;
+
+use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
+
+class Exception extends PhpSpreadsheetException
+{
+    /**
+     * Error handler callback.
+     *
+     * @param mixed $code
+     * @param mixed $string
+     * @param mixed $file
+     * @param mixed $line
+     * @param mixed $context
+     */
+    public static function errorHandlerCallback($code, $string, $file, $line, $context): void
+    {
+        $e = new self($string, $code);
+        $e->line = $line;
+        $e->file = $file;
+
+        throw $e;
+    }
+}

+ 22 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation;
+
+class ExceptionHandler
+{
+    /**
+     * Register errorhandler.
+     */
+    public function __construct()
+    {
+        set_error_handler([Exception::class, 'errorHandlerCallback'], E_ALL);
+    }
+
+    /**
+     * Unregister errorhandler.
+     */
+    public function __destruct()
+    {
+        restore_error_handler();
+    }
+}

File diff suppressed because it is too large
+ 1430 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial.php


+ 210 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Amortization.php

@@ -0,0 +1,210 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
+
+use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class Amortization
+{
+    /**
+     * AMORDEGRC.
+     *
+     * Returns the depreciation for each accounting period.
+     * This function is provided for the French accounting system. If an asset is purchased in
+     * the middle of the accounting period, the prorated depreciation is taken into account.
+     * The function is similar to AMORLINC, except that a depreciation coefficient is applied in
+     * the calculation depending on the life of the assets.
+     * This function will return the depreciation until the last period of the life of the assets
+     * or until the cumulated value of depreciation is greater than the cost of the assets minus
+     * the salvage value.
+     *
+     * Excel Function:
+     *        AMORDEGRC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
+     *
+     * @param mixed $cost The float cost of the asset
+     * @param mixed $purchased Date of the purchase of the asset
+     * @param mixed $firstPeriod Date of the end of the first period
+     * @param mixed $salvage The salvage value at the end of the life of the asset
+     * @param mixed $period the period (float)
+     * @param mixed $rate rate of depreciation (float)
+     * @param mixed $basis The type of day count to use (int).
+     *                         0 or omitted    US (NASD) 30/360
+     *                         1               Actual/actual
+     *                         2               Actual/360
+     *                         3               Actual/365
+     *                         4               European 30/360
+     *
+     * @return float|string (string containing the error type if there is an error)
+     */
+    public static function AMORDEGRC(
+        $cost,
+        $purchased,
+        $firstPeriod,
+        $salvage,
+        $period,
+        $rate,
+        $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
+    ) {
+        $cost = Functions::flattenSingleValue($cost);
+        $purchased = Functions::flattenSingleValue($purchased);
+        $firstPeriod = Functions::flattenSingleValue($firstPeriod);
+        $salvage = Functions::flattenSingleValue($salvage);
+        $period = Functions::flattenSingleValue($period);
+        $rate = Functions::flattenSingleValue($rate);
+        $basis = ($basis === null)
+            ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
+            : Functions::flattenSingleValue($basis);
+
+        try {
+            $cost = FinancialValidations::validateFloat($cost);
+            $purchased = FinancialValidations::validateDate($purchased);
+            $firstPeriod = FinancialValidations::validateDate($firstPeriod);
+            $salvage = FinancialValidations::validateFloat($salvage);
+            $period = FinancialValidations::validateInt($period);
+            $rate = FinancialValidations::validateFloat($rate);
+            $basis = FinancialValidations::validateBasis($basis);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $yearFrac = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis);
+        if (is_string($yearFrac)) {
+            return $yearFrac;
+        }
+
+        $amortiseCoeff = self::getAmortizationCoefficient($rate);
+
+        $rate *= $amortiseCoeff;
+        $fNRate = round($yearFrac * $rate * $cost, 0);
+        $cost -= $fNRate;
+        $fRest = $cost - $salvage;
+
+        for ($n = 0; $n < $period; ++$n) {
+            $fNRate = round($rate * $cost, 0);
+            $fRest -= $fNRate;
+
+            if ($fRest < 0.0) {
+                switch ($period - $n) {
+                    case 0:
+                    case 1:
+                        return round($cost * 0.5, 0);
+                    default:
+                        return 0.0;
+                }
+            }
+            $cost -= $fNRate;
+        }
+
+        return $fNRate;
+    }
+
+    /**
+     * AMORLINC.
+     *
+     * Returns the depreciation for each accounting period.
+     * This function is provided for the French accounting system. If an asset is purchased in
+     * the middle of the accounting period, the prorated depreciation is taken into account.
+     *
+     * Excel Function:
+     *        AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis])
+     *
+     * @param mixed $cost The cost of the asset as a float
+     * @param mixed $purchased Date of the purchase of the asset
+     * @param mixed $firstPeriod Date of the end of the first period
+     * @param mixed $salvage The salvage value at the end of the life of the asset
+     * @param mixed $period The period as a float
+     * @param mixed $rate Rate of depreciation as  float
+     * @param mixed $basis Integer indicating the type of day count to use.
+     *                             0 or omitted    US (NASD) 30/360
+     *                             1               Actual/actual
+     *                             2               Actual/360
+     *                             3               Actual/365
+     *                             4               European 30/360
+     *
+     * @return float|string (string containing the error type if there is an error)
+     */
+    public static function AMORLINC(
+        $cost,
+        $purchased,
+        $firstPeriod,
+        $salvage,
+        $period,
+        $rate,
+        $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
+    ) {
+        $cost = Functions::flattenSingleValue($cost);
+        $purchased = Functions::flattenSingleValue($purchased);
+        $firstPeriod = Functions::flattenSingleValue($firstPeriod);
+        $salvage = Functions::flattenSingleValue($salvage);
+        $period = Functions::flattenSingleValue($period);
+        $rate = Functions::flattenSingleValue($rate);
+        $basis = ($basis === null)
+            ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
+            : Functions::flattenSingleValue($basis);
+
+        try {
+            $cost = FinancialValidations::validateFloat($cost);
+            $purchased = FinancialValidations::validateDate($purchased);
+            $firstPeriod = FinancialValidations::validateDate($firstPeriod);
+            $salvage = FinancialValidations::validateFloat($salvage);
+            $period = FinancialValidations::validateFloat($period);
+            $rate = FinancialValidations::validateFloat($rate);
+            $basis = FinancialValidations::validateBasis($basis);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $fOneRate = $cost * $rate;
+        $fCostDelta = $cost - $salvage;
+        //    Note, quirky variation for leap years on the YEARFRAC for this function
+        $purchasedYear = DateTimeExcel\DateParts::year($purchased);
+        $yearFrac = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis);
+        if (is_string($yearFrac)) {
+            return $yearFrac;
+        }
+
+        if (
+            ($basis == FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL) &&
+            ($yearFrac < 1) && (Functions::scalar(DateTimeExcel\Helpers::isLeapYear($purchasedYear)))
+        ) {
+            $yearFrac *= 365 / 366;
+        }
+
+        $f0Rate = $yearFrac * $rate * $cost;
+        $nNumOfFullPeriods = (int) (($cost - $salvage - $f0Rate) / $fOneRate);
+
+        if ($period == 0) {
+            return $f0Rate;
+        } elseif ($period <= $nNumOfFullPeriods) {
+            return $fOneRate;
+        } elseif ($period == ($nNumOfFullPeriods + 1)) {
+            return $fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate;
+        }
+
+        return 0.0;
+    }
+
+    private static function getAmortizationCoefficient(float $rate): float
+    {
+        //    The depreciation coefficients are:
+        //    Life of assets (1/rate)        Depreciation coefficient
+        //    Less than 3 years            1
+        //    Between 3 and 4 years        1.5
+        //    Between 5 and 6 years        2
+        //    More than 6 years            2.5
+        $fUsePer = 1.0 / $rate;
+
+        if ($fUsePer < 3.0) {
+            return 1.0;
+        } elseif ($fUsePer < 4.0) {
+            return 1.5;
+        } elseif ($fUsePer <= 6.0) {
+            return 2.0;
+        }
+
+        return 2.5;
+    }
+}

+ 53 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php

@@ -0,0 +1,53 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
+use PhpOffice\PhpSpreadsheet\Calculation\Financial\FinancialValidations;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class CashFlowValidations extends FinancialValidations
+{
+    /**
+     * @param mixed $rate
+     */
+    public static function validateRate($rate): float
+    {
+        $rate = self::validateFloat($rate);
+
+        return $rate;
+    }
+
+    /**
+     * @param mixed $type
+     */
+    public static function validatePeriodType($type): int
+    {
+        $rate = self::validateInt($type);
+        if (
+            $type !== FinancialConstants::PAYMENT_END_OF_PERIOD &&
+            $type !== FinancialConstants::PAYMENT_BEGINNING_OF_PERIOD
+        ) {
+            throw new Exception(Functions::NAN());
+        }
+
+        return $rate;
+    }
+
+    /**
+     * @param mixed $presentValue
+     */
+    public static function validatePresentValue($presentValue): float
+    {
+        return self::validateFloat($presentValue);
+    }
+
+    /**
+     * @param mixed $futureValue
+     */
+    public static function validateFutureValue($futureValue): float
+    {
+        return self::validateFloat($futureValue);
+    }
+}

+ 199 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic.php

@@ -0,0 +1,199 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
+use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class Periodic
+{
+    /**
+     * FV.
+     *
+     * Returns the Future Value of a cash flow with constant payments and interest rate (annuities).
+     *
+     * Excel Function:
+     *        FV(rate,nper,pmt[,pv[,type]])
+     *
+     * @param mixed $rate The interest rate per period
+     * @param mixed $numberOfPeriods Total number of payment periods in an annuity as an integer
+     * @param mixed $payment The payment made each period: it cannot change over the
+     *                            life of the annuity. Typically, pmt contains principal
+     *                            and interest but no other fees or taxes.
+     * @param mixed $presentValue present Value, or the lump-sum amount that a series of
+     *                            future payments is worth right now
+     * @param mixed $type A number 0 or 1 and indicates when payments are due:
+     *                      0 or omitted    At the end of the period.
+     *                      1               At the beginning of the period.
+     *
+     * @return float|string
+     */
+    public static function futureValue(
+        $rate,
+        $numberOfPeriods,
+        $payment = 0.0,
+        $presentValue = 0.0,
+        $type = FinancialConstants::PAYMENT_END_OF_PERIOD
+    ) {
+        $rate = Functions::flattenSingleValue($rate);
+        $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
+        $payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment);
+        $presentValue = ($presentValue === null) ? 0.0 : Functions::flattenSingleValue($presentValue);
+        $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
+
+        try {
+            $rate = CashFlowValidations::validateRate($rate);
+            $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
+            $payment = CashFlowValidations::validateFloat($payment);
+            $presentValue = CashFlowValidations::validatePresentValue($presentValue);
+            $type = CashFlowValidations::validatePeriodType($type);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        return self::calculateFutureValue($rate, $numberOfPeriods, $payment, $presentValue, $type);
+    }
+
+    /**
+     * PV.
+     *
+     * Returns the Present Value of a cash flow with constant payments and interest rate (annuities).
+     *
+     * @param mixed $rate Interest rate per period
+     * @param mixed $numberOfPeriods Number of periods as an integer
+     * @param mixed $payment Periodic payment (annuity)
+     * @param mixed $futureValue Future Value
+     * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
+     *
+     * @return float|string Result, or a string containing an error
+     */
+    public static function presentValue(
+        $rate,
+        $numberOfPeriods,
+        $payment = 0.0,
+        $futureValue = 0.0,
+        $type = FinancialConstants::PAYMENT_END_OF_PERIOD
+    ) {
+        $rate = Functions::flattenSingleValue($rate);
+        $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
+        $payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment);
+        $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
+        $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
+
+        try {
+            $rate = CashFlowValidations::validateRate($rate);
+            $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
+            $payment = CashFlowValidations::validateFloat($payment);
+            $futureValue = CashFlowValidations::validateFutureValue($futureValue);
+            $type = CashFlowValidations::validatePeriodType($type);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Validate parameters
+        if ($numberOfPeriods < 0) {
+            return Functions::NAN();
+        }
+
+        return self::calculatePresentValue($rate, $numberOfPeriods, $payment, $futureValue, $type);
+    }
+
+    /**
+     * NPER.
+     *
+     * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate.
+     *
+     * @param mixed $rate Interest rate per period
+     * @param mixed $payment Periodic payment (annuity)
+     * @param mixed $presentValue Present Value
+     * @param mixed $futureValue Future Value
+     * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
+     *
+     * @return float|string Result, or a string containing an error
+     */
+    public static function periods(
+        $rate,
+        $payment,
+        $presentValue,
+        $futureValue = 0.0,
+        $type = FinancialConstants::PAYMENT_END_OF_PERIOD
+    ) {
+        $rate = Functions::flattenSingleValue($rate);
+        $payment = Functions::flattenSingleValue($payment);
+        $presentValue = Functions::flattenSingleValue($presentValue);
+        $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
+        $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
+
+        try {
+            $rate = CashFlowValidations::validateRate($rate);
+            $payment = CashFlowValidations::validateFloat($payment);
+            $presentValue = CashFlowValidations::validatePresentValue($presentValue);
+            $futureValue = CashFlowValidations::validateFutureValue($futureValue);
+            $type = CashFlowValidations::validatePeriodType($type);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Validate parameters
+        if ($payment == 0.0) {
+            return Functions::NAN();
+        }
+
+        return self::calculatePeriods($rate, $payment, $presentValue, $futureValue, $type);
+    }
+
+    private static function calculateFutureValue(
+        float $rate,
+        int $numberOfPeriods,
+        float $payment,
+        float $presentValue,
+        int $type
+    ): float {
+        if ($rate !== null && $rate != 0) {
+            return -$presentValue *
+                (1 + $rate) ** $numberOfPeriods - $payment * (1 + $rate * $type) * ((1 + $rate) ** $numberOfPeriods - 1)
+                    / $rate;
+        }
+
+        return -$presentValue - $payment * $numberOfPeriods;
+    }
+
+    private static function calculatePresentValue(
+        float $rate,
+        int $numberOfPeriods,
+        float $payment,
+        float $futureValue,
+        int $type
+    ): float {
+        if ($rate != 0.0) {
+            return (-$payment * (1 + $rate * $type)
+                    * (((1 + $rate) ** $numberOfPeriods - 1) / $rate) - $futureValue) / (1 + $rate) ** $numberOfPeriods;
+        }
+
+        return -$futureValue - $payment * $numberOfPeriods;
+    }
+
+    /**
+     * @return float|string
+     */
+    private static function calculatePeriods(
+        float $rate,
+        float $payment,
+        float $presentValue,
+        float $futureValue,
+        int $type
+    ) {
+        if ($rate != 0.0) {
+            if ($presentValue == 0.0) {
+                return Functions::NAN();
+            }
+
+            return log(($payment * (1 + $rate * $type) / $rate - $futureValue) /
+                    ($presentValue + $payment * (1 + $rate * $type) / $rate)) / log(1 + $rate);
+        }
+
+        return (-$presentValue - $futureValue) / $payment;
+    }
+}

+ 141 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php

@@ -0,0 +1,141 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
+use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class Cumulative
+{
+    /**
+     * CUMIPMT.
+     *
+     * Returns the cumulative interest paid on a loan between the start and end periods.
+     *
+     * Excel Function:
+     *        CUMIPMT(rate,nper,pv,start,end[,type])
+     *
+     * @param mixed $rate The Interest rate
+     * @param mixed $periods The total number of payment periods
+     * @param mixed $presentValue Present Value
+     * @param mixed $start The first period in the calculation.
+     *                       Payment periods are numbered beginning with 1.
+     * @param mixed $end the last period in the calculation
+     * @param mixed $type A number 0 or 1 and indicates when payments are due:
+     *                    0 or omitted    At the end of the period.
+     *                    1               At the beginning of the period.
+     *
+     * @return float|string
+     */
+    public static function interest(
+        $rate,
+        $periods,
+        $presentValue,
+        $start,
+        $end,
+        $type = FinancialConstants::PAYMENT_END_OF_PERIOD
+    ) {
+        $rate = Functions::flattenSingleValue($rate);
+        $periods = Functions::flattenSingleValue($periods);
+        $presentValue = Functions::flattenSingleValue($presentValue);
+        $start = Functions::flattenSingleValue($start);
+        $end = Functions::flattenSingleValue($end);
+        $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
+
+        try {
+            $rate = CashFlowValidations::validateRate($rate);
+            $periods = CashFlowValidations::validateInt($periods);
+            $presentValue = CashFlowValidations::validatePresentValue($presentValue);
+            $start = CashFlowValidations::validateInt($start);
+            $end = CashFlowValidations::validateInt($end);
+            $type = CashFlowValidations::validatePeriodType($type);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Validate parameters
+        if ($start < 1 || $start > $end) {
+            return Functions::NAN();
+        }
+
+        // Calculate
+        $interest = 0;
+        for ($per = $start; $per <= $end; ++$per) {
+            $ipmt = Interest::payment($rate, $per, $periods, $presentValue, 0, $type);
+            if (is_string($ipmt)) {
+                return $ipmt;
+            }
+
+            $interest += $ipmt;
+        }
+
+        return $interest;
+    }
+
+    /**
+     * CUMPRINC.
+     *
+     * Returns the cumulative principal paid on a loan between the start and end periods.
+     *
+     * Excel Function:
+     *        CUMPRINC(rate,nper,pv,start,end[,type])
+     *
+     * @param mixed $rate The Interest rate
+     * @param mixed $periods The total number of payment periods as an integer
+     * @param mixed $presentValue Present Value
+     * @param mixed $start The first period in the calculation.
+     *                       Payment periods are numbered beginning with 1.
+     * @param mixed $end the last period in the calculation
+     * @param mixed $type A number 0 or 1 and indicates when payments are due:
+     *                    0 or omitted    At the end of the period.
+     *                    1               At the beginning of the period.
+     *
+     * @return float|string
+     */
+    public static function principal(
+        $rate,
+        $periods,
+        $presentValue,
+        $start,
+        $end,
+        $type = FinancialConstants::PAYMENT_END_OF_PERIOD
+    ) {
+        $rate = Functions::flattenSingleValue($rate);
+        $periods = Functions::flattenSingleValue($periods);
+        $presentValue = Functions::flattenSingleValue($presentValue);
+        $start = Functions::flattenSingleValue($start);
+        $end = Functions::flattenSingleValue($end);
+        $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
+
+        try {
+            $rate = CashFlowValidations::validateRate($rate);
+            $periods = CashFlowValidations::validateInt($periods);
+            $presentValue = CashFlowValidations::validatePresentValue($presentValue);
+            $start = CashFlowValidations::validateInt($start);
+            $end = CashFlowValidations::validateInt($end);
+            $type = CashFlowValidations::validatePeriodType($type);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Validate parameters
+        if ($start < 1 || $start > $end) {
+            return Functions::VALUE();
+        }
+
+        // Calculate
+        $principal = 0;
+        for ($per = $start; $per <= $end; ++$per) {
+            $ppmt = Payments::interestPayment($rate, $per, $periods, $presentValue, 0, $type);
+            if (is_string($ppmt)) {
+                return $ppmt;
+            }
+
+            $principal += $ppmt;
+        }
+
+        return $principal;
+    }
+}

+ 216 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php

@@ -0,0 +1,216 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
+use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class Interest
+{
+    private const FINANCIAL_MAX_ITERATIONS = 128;
+
+    private const FINANCIAL_PRECISION = 1.0e-08;
+
+    /**
+     * IPMT.
+     *
+     * Returns the interest payment for a given period for an investment based on periodic, constant payments
+     *         and a constant interest rate.
+     *
+     * Excel Function:
+     *        IPMT(rate,per,nper,pv[,fv][,type])
+     *
+     * @param mixed $interestRate Interest rate per period
+     * @param mixed $period Period for which we want to find the interest
+     * @param mixed $numberOfPeriods Number of periods
+     * @param mixed $presentValue Present Value
+     * @param mixed $futureValue Future Value
+     * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
+     *
+     * @return float|string
+     */
+    public static function payment(
+        $interestRate,
+        $period,
+        $numberOfPeriods,
+        $presentValue,
+        $futureValue = 0,
+        $type = FinancialConstants::PAYMENT_END_OF_PERIOD
+    ) {
+        $interestRate = Functions::flattenSingleValue($interestRate);
+        $period = Functions::flattenSingleValue($period);
+        $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
+        $presentValue = Functions::flattenSingleValue($presentValue);
+        $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
+        $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
+
+        try {
+            $interestRate = CashFlowValidations::validateRate($interestRate);
+            $period = CashFlowValidations::validateInt($period);
+            $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
+            $presentValue = CashFlowValidations::validatePresentValue($presentValue);
+            $futureValue = CashFlowValidations::validateFutureValue($futureValue);
+            $type = CashFlowValidations::validatePeriodType($type);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Validate parameters
+        if ($period <= 0 || $period > $numberOfPeriods) {
+            return Functions::NAN();
+        }
+
+        // Calculate
+        $interestAndPrincipal = new InterestAndPrincipal(
+            $interestRate,
+            $period,
+            $numberOfPeriods,
+            $presentValue,
+            $futureValue,
+            $type
+        );
+
+        return $interestAndPrincipal->interest();
+    }
+
+    /**
+     * ISPMT.
+     *
+     * Returns the interest payment for an investment based on an interest rate and a constant payment schedule.
+     *
+     * Excel Function:
+     *     =ISPMT(interest_rate, period, number_payments, pv)
+     *
+     * @param mixed $interestRate is the interest rate for the investment
+     * @param mixed $period is the period to calculate the interest rate.  It must be betweeen 1 and number_payments.
+     * @param mixed $numberOfPeriods is the number of payments for the annuity
+     * @param mixed $principleRemaining is the loan amount or present value of the payments
+     */
+    public static function schedulePayment($interestRate, $period, $numberOfPeriods, $principleRemaining)
+    {
+        $interestRate = Functions::flattenSingleValue($interestRate);
+        $period = Functions::flattenSingleValue($period);
+        $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
+        $principleRemaining = Functions::flattenSingleValue($principleRemaining);
+
+        try {
+            $interestRate = CashFlowValidations::validateRate($interestRate);
+            $period = CashFlowValidations::validateInt($period);
+            $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
+            $principleRemaining = CashFlowValidations::validateFloat($principleRemaining);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Validate parameters
+        if ($period <= 0 || $period > $numberOfPeriods) {
+            return Functions::NAN();
+        }
+
+        // Return value
+        $returnValue = 0;
+
+        // Calculate
+        $principlePayment = ($principleRemaining * 1.0) / ($numberOfPeriods * 1.0);
+        for ($i = 0; $i <= $period; ++$i) {
+            $returnValue = $interestRate * $principleRemaining * -1;
+            $principleRemaining -= $principlePayment;
+            // principle needs to be 0 after the last payment, don't let floating point screw it up
+            if ($i == $numberOfPeriods) {
+                $returnValue = 0.0;
+            }
+        }
+
+        return $returnValue;
+    }
+
+    /**
+     * RATE.
+     *
+     * Returns the interest rate per period of an annuity.
+     * RATE is calculated by iteration and can have zero or more solutions.
+     * If the successive results of RATE do not converge to within 0.0000001 after 20 iterations,
+     * RATE returns the #NUM! error value.
+     *
+     * Excel Function:
+     *        RATE(nper,pmt,pv[,fv[,type[,guess]]])
+     *
+     * @param mixed $numberOfPeriods The total number of payment periods in an annuity
+     * @param mixed $payment The payment made each period and cannot change over the life of the annuity.
+     *                           Typically, pmt includes principal and interest but no other fees or taxes.
+     * @param mixed $presentValue The present value - the total amount that a series of future payments is worth now
+     * @param mixed $futureValue The future value, or a cash balance you want to attain after the last payment is made.
+     *                               If fv is omitted, it is assumed to be 0 (the future value of a loan,
+     *                               for example, is 0).
+     * @param mixed $type A number 0 or 1 and indicates when payments are due:
+     *                      0 or omitted    At the end of the period.
+     *                      1               At the beginning of the period.
+     * @param mixed $guess Your guess for what the rate will be.
+     *                          If you omit guess, it is assumed to be 10 percent.
+     *
+     * @return float|string
+     */
+    public static function rate(
+        $numberOfPeriods,
+        $payment,
+        $presentValue,
+        $futureValue = 0.0,
+        $type = FinancialConstants::PAYMENT_END_OF_PERIOD,
+        $guess = 0.1
+    ) {
+        $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
+        $payment = Functions::flattenSingleValue($payment);
+        $presentValue = Functions::flattenSingleValue($presentValue);
+        $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
+        $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
+        $guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess);
+
+        try {
+            $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
+            $payment = CashFlowValidations::validateFloat($payment);
+            $presentValue = CashFlowValidations::validatePresentValue($presentValue);
+            $futureValue = CashFlowValidations::validateFutureValue($futureValue);
+            $type = CashFlowValidations::validatePeriodType($type);
+            $guess = CashFlowValidations::validateFloat($guess);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $rate = $guess;
+        // rest of code adapted from python/numpy
+        $close = false;
+        $iter = 0;
+        while (!$close && $iter < self::FINANCIAL_MAX_ITERATIONS) {
+            $nextdiff = self::rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type);
+            if (!is_numeric($nextdiff)) {
+                break;
+            }
+            $rate1 = $rate - $nextdiff;
+            $close = abs($rate1 - $rate) < self::FINANCIAL_PRECISION;
+            ++$iter;
+            $rate = $rate1;
+        }
+
+        return $close ? $rate : Functions::NAN();
+    }
+
+    private static function rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type)
+    {
+        if ($rate == 0.0) {
+            return Functions::NAN();
+        }
+        $tt1 = ($rate + 1) ** $numberOfPeriods;
+        $tt2 = ($rate + 1) ** ($numberOfPeriods - 1);
+        $numerator = $futureValue + $tt1 * $presentValue + $payment * ($tt1 - 1) * ($rate * $type + 1) / $rate;
+        $denominator = $numberOfPeriods * $tt2 * $presentValue - $payment * ($tt1 - 1)
+            * ($rate * $type + 1) / ($rate * $rate) + $numberOfPeriods
+            * $payment * $tt2 * ($rate * $type + 1) / $rate + $payment * ($tt1 - 1) * $type / $rate;
+        if ($denominator == 0) {
+            return Functions::NAN();
+        }
+
+        return $numerator / $denominator;
+    }
+}

+ 44 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
+
+class InterestAndPrincipal
+{
+    protected $interest;
+
+    protected $principal;
+
+    public function __construct(
+        float $rate = 0.0,
+        int $period = 0,
+        int $numberOfPeriods = 0,
+        float $presentValue = 0,
+        float $futureValue = 0,
+        int $type = FinancialConstants::PAYMENT_END_OF_PERIOD
+    ) {
+        $payment = Payments::annuity($rate, $numberOfPeriods, $presentValue, $futureValue, $type);
+        $capital = $presentValue;
+        $interest = 0.0;
+        $principal = 0.0;
+        for ($i = 1; $i <= $period; ++$i) {
+            $interest = ($type === FinancialConstants::PAYMENT_BEGINNING_OF_PERIOD && $i == 1) ? 0 : -$capital * $rate;
+            $principal = $payment - $interest;
+            $capital += $principal;
+        }
+
+        $this->interest = $interest;
+        $this->principal = $principal;
+    }
+
+    public function interest(): float
+    {
+        return $this->interest;
+    }
+
+    public function principal(): float
+    {
+        return $this->principal;
+    }
+}

+ 115 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php

@@ -0,0 +1,115 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Constant\Periodic;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\CashFlowValidations;
+use PhpOffice\PhpSpreadsheet\Calculation\Financial\Constants as FinancialConstants;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class Payments
+{
+    /**
+     * PMT.
+     *
+     * Returns the constant payment (annuity) for a cash flow with a constant interest rate.
+     *
+     * @param mixed $interestRate Interest rate per period
+     * @param mixed $numberOfPeriods Number of periods
+     * @param mixed $presentValue Present Value
+     * @param mixed $futureValue Future Value
+     * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
+     *
+     * @return float|string Result, or a string containing an error
+     */
+    public static function annuity(
+        $interestRate,
+        $numberOfPeriods,
+        $presentValue,
+        $futureValue = 0,
+        $type = FinancialConstants::PAYMENT_END_OF_PERIOD
+    ) {
+        $interestRate = Functions::flattenSingleValue($interestRate);
+        $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
+        $presentValue = Functions::flattenSingleValue($presentValue);
+        $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
+        $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
+
+        try {
+            $interestRate = CashFlowValidations::validateRate($interestRate);
+            $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
+            $presentValue = CashFlowValidations::validatePresentValue($presentValue);
+            $futureValue = CashFlowValidations::validateFutureValue($futureValue);
+            $type = CashFlowValidations::validatePeriodType($type);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Calculate
+        if ($interestRate != 0.0) {
+            return (-$futureValue - $presentValue * (1 + $interestRate) ** $numberOfPeriods) /
+                (1 + $interestRate * $type) / (((1 + $interestRate) ** $numberOfPeriods - 1) / $interestRate);
+        }
+
+        return (-$presentValue - $futureValue) / $numberOfPeriods;
+    }
+
+    /**
+     * PPMT.
+     *
+     * Returns the interest payment for a given period for an investment based on periodic, constant payments
+     *         and a constant interest rate.
+     *
+     * @param mixed $interestRate Interest rate per period
+     * @param mixed $period Period for which we want to find the interest
+     * @param mixed $numberOfPeriods Number of periods
+     * @param mixed $presentValue Present Value
+     * @param mixed $futureValue Future Value
+     * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
+     *
+     * @return float|string Result, or a string containing an error
+     */
+    public static function interestPayment(
+        $interestRate,
+        $period,
+        $numberOfPeriods,
+        $presentValue,
+        $futureValue = 0,
+        $type = FinancialConstants::PAYMENT_END_OF_PERIOD
+    ) {
+        $interestRate = Functions::flattenSingleValue($interestRate);
+        $period = Functions::flattenSingleValue($period);
+        $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
+        $presentValue = Functions::flattenSingleValue($presentValue);
+        $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
+        $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
+
+        try {
+            $interestRate = CashFlowValidations::validateRate($interestRate);
+            $period = CashFlowValidations::validateInt($period);
+            $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
+            $presentValue = CashFlowValidations::validatePresentValue($presentValue);
+            $futureValue = CashFlowValidations::validateFutureValue($futureValue);
+            $type = CashFlowValidations::validatePeriodType($type);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Validate parameters
+        if ($period <= 0 || $period > $numberOfPeriods) {
+            return Functions::NAN();
+        }
+
+        // Calculate
+        $interestAndPrincipal = new InterestAndPrincipal(
+            $interestRate,
+            $period,
+            $numberOfPeriods,
+            $presentValue,
+            $futureValue,
+            $type
+        );
+
+        return $interestAndPrincipal->principal();
+    }
+}

+ 108 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php

@@ -0,0 +1,108 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class Single
+{
+    /**
+     * FVSCHEDULE.
+     *
+     * Returns the future value of an initial principal after applying a series of compound interest rates.
+     * Use FVSCHEDULE to calculate the future value of an investment with a variable or adjustable rate.
+     *
+     * Excel Function:
+     *        FVSCHEDULE(principal,schedule)
+     *
+     * @param mixed $principal the present value
+     * @param float[] $schedule an array of interest rates to apply
+     *
+     * @return float|string
+     */
+    public static function futureValue($principal, $schedule)
+    {
+        $principal = Functions::flattenSingleValue($principal);
+        $schedule = Functions::flattenArray($schedule);
+
+        try {
+            $principal = CashFlowValidations::validateFloat($principal);
+
+            foreach ($schedule as $rate) {
+                $rate = CashFlowValidations::validateFloat($rate);
+                $principal *= 1 + $rate;
+            }
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        return $principal;
+    }
+
+    /**
+     * PDURATION.
+     *
+     * Calculates the number of periods required for an investment to reach a specified value.
+     *
+     * @param mixed $rate Interest rate per period
+     * @param mixed $presentValue Present Value
+     * @param mixed $futureValue Future Value
+     *
+     * @return float|string Result, or a string containing an error
+     */
+    public static function periods($rate, $presentValue, $futureValue)
+    {
+        $rate = Functions::flattenSingleValue($rate);
+        $presentValue = Functions::flattenSingleValue($presentValue);
+        $futureValue = Functions::flattenSingleValue($futureValue);
+
+        try {
+            $rate = CashFlowValidations::validateRate($rate);
+            $presentValue = CashFlowValidations::validatePresentValue($presentValue);
+            $futureValue = CashFlowValidations::validateFutureValue($futureValue);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Validate parameters
+        if ($rate <= 0.0 || $presentValue <= 0.0 || $futureValue <= 0.0) {
+            return Functions::NAN();
+        }
+
+        return (log($futureValue) - log($presentValue)) / log(1 + $rate);
+    }
+
+    /**
+     * RRI.
+     *
+     * Calculates the interest rate required for an investment to grow to a specified future value .
+     *
+     * @param float $periods The number of periods over which the investment is made
+     * @param float $presentValue Present Value
+     * @param float $futureValue Future Value
+     *
+     * @return float|string Result, or a string containing an error
+     */
+    public static function interestRate($periods = 0.0, $presentValue = 0.0, $futureValue = 0.0)
+    {
+        $periods = Functions::flattenSingleValue($periods);
+        $presentValue = Functions::flattenSingleValue($presentValue);
+        $futureValue = Functions::flattenSingleValue($futureValue);
+
+        try {
+            $periods = CashFlowValidations::validateFloat($periods);
+            $presentValue = CashFlowValidations::validatePresentValue($presentValue);
+            $futureValue = CashFlowValidations::validateFutureValue($futureValue);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        // Validate parameters
+        if ($periods <= 0.0 || $presentValue <= 0.0 || $futureValue < 0.0) {
+            return Functions::NAN();
+        }
+
+        return ($futureValue / $presentValue) ** (1 / $periods) - 1;
+    }
+}

+ 258 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php

@@ -0,0 +1,258 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable;
+
+use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel;
+use PhpOffice\PhpSpreadsheet\Calculation\Exception;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class NonPeriodic
+{
+    const FINANCIAL_MAX_ITERATIONS = 128;
+
+    const FINANCIAL_PRECISION = 1.0e-08;
+
+    const DEFAULT_GUESS = 0.1;
+
+    /**
+     * XIRR.
+     *
+     * Returns the internal rate of return for a schedule of cash flows that is not necessarily periodic.
+     *
+     * Excel Function:
+     *        =XIRR(values,dates,guess)
+     *
+     * @param float[] $values     A series of cash flow payments
+     *                                The series of values must contain at least one positive value & one negative value
+     * @param mixed[] $dates      A series of payment dates
+     *                                The first payment date indicates the beginning of the schedule of payments
+     *                                All other dates must be later than this date, but they may occur in any order
+     * @param mixed $guess        An optional guess at the expected answer
+     *
+     * @return float|string
+     */
+    public static function rate($values, $dates, $guess = self::DEFAULT_GUESS)
+    {
+        $rslt = self::xirrPart1($values, $dates);
+        if ($rslt !== '') {
+            return $rslt;
+        }
+
+        // create an initial range, with a root somewhere between 0 and guess
+        $guess = Functions::flattenSingleValue($guess) ?? self::DEFAULT_GUESS;
+        if (!is_numeric($guess)) {
+            return Functions::VALUE();
+        }
+        $guess = ($guess + 0.0) ?: self::DEFAULT_GUESS;
+        $x1 = 0.0;
+        $x2 = $guess + 0.0;
+        $f1 = self::xnpvOrdered($x1, $values, $dates, false);
+        $f2 = self::xnpvOrdered($x2, $values, $dates, false);
+        $found = false;
+        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
+            if (!is_numeric($f1) || !is_numeric($f2)) {
+                break;
+            }
+            $f1 = (float) $f1;
+            $f2 = (float) $f2;
+            if (($f1 * $f2) < 0.0) {
+                $found = true;
+
+                break;
+            } elseif (abs($f1) < abs($f2)) {
+                $x1 += 1.6 * ($x1 - $x2);
+                $f1 = self::xnpvOrdered($x1, $values, $dates, false);
+            } else {
+                $x2 += 1.6 * ($x2 - $x1);
+                $f2 = self::xnpvOrdered($x2, $values, $dates, false);
+            }
+        }
+        if (!$found) {
+            return Functions::NAN();
+        }
+
+        return self::xirrPart3($values, $dates, $x1, $x2);
+    }
+
+    /**
+     * XNPV.
+     *
+     * Returns the net present value for a schedule of cash flows that is not necessarily periodic.
+     * To calculate the net present value for a series of cash flows that is periodic, use the NPV function.
+     *
+     * Excel Function:
+     *        =XNPV(rate,values,dates)
+     *
+     * @param float $rate the discount rate to apply to the cash flows
+     * @param float[] $values A series of cash flows that corresponds to a schedule of payments in dates.
+     *                          The first payment is optional and corresponds to a cost or payment that occurs
+     *                              at the beginning of the investment.
+     *                          If the first value is a cost or payment, it must be a negative value.
+     *                             All succeeding payments are discounted based on a 365-day year.
+     *                          The series of values must contain at least one positive value and one negative value.
+     * @param mixed[] $dates A schedule of payment dates that corresponds to the cash flow payments.
+     *                         The first payment date indicates the beginning of the schedule of payments.
+     *                         All other dates must be later than this date, but they may occur in any order.
+     *
+     * @return float|string
+     */
+    public static function presentValue($rate, $values, $dates)
+    {
+        return self::xnpvOrdered($rate, $values, $dates, true);
+    }
+
+    private static function bothNegAndPos(bool $neg, bool $pos): bool
+    {
+        return $neg && $pos;
+    }
+
+    /**
+     * @param mixed $values
+     * @param mixed $dates
+     */
+    private static function xirrPart1(&$values, &$dates): string
+    {
+        $values = Functions::flattenArray($values);
+        $dates = Functions::flattenArray($dates);
+        $valuesIsArray = count($values) > 1;
+        $datesIsArray = count($dates) > 1;
+        if (!$valuesIsArray && !$datesIsArray) {
+            return Functions::NA();
+        }
+        if (count($values) != count($dates)) {
+            return Functions::NAN();
+        }
+
+        $datesCount = count($dates);
+        for ($i = 0; $i < $datesCount; ++$i) {
+            try {
+                $dates[$i] = DateTimeExcel\Helpers::getDateValue($dates[$i]);
+            } catch (Exception $e) {
+                return $e->getMessage();
+            }
+        }
+
+        return self::xirrPart2($values);
+    }
+
+    private static function xirrPart2(array &$values): string
+    {
+        $valCount = count($values);
+        $foundpos = false;
+        $foundneg = false;
+        for ($i = 0; $i < $valCount; ++$i) {
+            $fld = $values[$i];
+            if (!is_numeric($fld)) {
+                return Functions::VALUE();
+            } elseif ($fld > 0) {
+                $foundpos = true;
+            } elseif ($fld < 0) {
+                $foundneg = true;
+            }
+        }
+        if (!self::bothNegAndPos($foundneg, $foundpos)) {
+            return Functions::NAN();
+        }
+
+        return '';
+    }
+
+    /**
+     * @return float|string
+     */
+    private static function xirrPart3(array $values, array $dates, float $x1, float $x2)
+    {
+        $f = self::xnpvOrdered($x1, $values, $dates, false);
+        if ($f < 0.0) {
+            $rtb = $x1;
+            $dx = $x2 - $x1;
+        } else {
+            $rtb = $x2;
+            $dx = $x1 - $x2;
+        }
+
+        $rslt = Functions::VALUE();
+        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
+            $dx *= 0.5;
+            $x_mid = $rtb + $dx;
+            $f_mid = (float) self::xnpvOrdered($x_mid, $values, $dates, false);
+            if ($f_mid <= 0.0) {
+                $rtb = $x_mid;
+            }
+            if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
+                $rslt = $x_mid;
+
+                break;
+            }
+        }
+
+        return $rslt;
+    }
+
+    /**
+     * @param mixed $rate
+     * @param mixed $values
+     * @param mixed $dates
+     *
+     * @return float|string
+     */
+    private static function xnpvOrdered($rate, $values, $dates, bool $ordered = true)
+    {
+        $rate = Functions::flattenSingleValue($rate);
+        $values = Functions::flattenArray($values);
+        $dates = Functions::flattenArray($dates);
+        $valCount = count($values);
+
+        try {
+            self::validateXnpv($rate, $values, $dates);
+            $date0 = DateTimeExcel\Helpers::getDateValue($dates[0]);
+        } catch (Exception $e) {
+            return $e->getMessage();
+        }
+
+        $xnpv = 0.0;
+        for ($i = 0; $i < $valCount; ++$i) {
+            if (!is_numeric($values[$i])) {
+                return Functions::VALUE();
+            }
+
+            try {
+                $datei = DateTimeExcel\Helpers::getDateValue($dates[$i]);
+            } catch (Exception $e) {
+                return $e->getMessage();
+            }
+            if ($date0 > $datei) {
+                $dif = $ordered ? Functions::NAN() : -((int) DateTimeExcel\Difference::interval($datei, $date0, 'd'));
+            } else {
+                $dif = DateTimeExcel\Difference::interval($date0, $datei, 'd');
+            }
+            if (!is_numeric($dif)) {
+                return $dif;
+            }
+            if ($rate <= -1.0) {
+                $xnpv += -abs($values[$i]) / (-1 - $rate) ** ($dif / 365);
+            } else {
+                $xnpv += $values[$i] / (1 + $rate) ** ($dif / 365);
+            }
+        }
+
+        return is_finite($xnpv) ? $xnpv : Functions::VALUE();
+    }
+
+    /**
+     * @param mixed $rate
+     */
+    private static function validateXnpv($rate, array $values, array $dates): void
+    {
+        if (!is_numeric($rate)) {
+            throw new Exception(Functions::VALUE());
+        }
+        $valCount = count($values);
+        if ($valCount != count($dates)) {
+            throw new Exception(Functions::NAN());
+        }
+        if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) {
+            throw new Exception(Functions::NAN());
+        }
+    }
+}

+ 160 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php

@@ -0,0 +1,160 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Financial\CashFlow\Variable;
+
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+
+class Periodic
+{
+    const FINANCIAL_MAX_ITERATIONS = 128;
+
+    const FINANCIAL_PRECISION = 1.0e-08;
+
+    /**
+     * IRR.
+     *
+     * Returns the internal rate of return for a series of cash flows represented by the numbers in values.
+     * These cash flows do not have to be even, as they would be for an annuity. However, the cash flows must occur
+     * at regular intervals, such as monthly or annually. The internal rate of return is the interest rate received
+     * for an investment consisting of payments (negative values) and income (positive values) that occur at regular
+     * periods.
+     *
+     * Excel Function:
+     *        IRR(values[,guess])
+     *
+     * @param mixed $values An array or a reference to cells that contain numbers for which you want
+     *                                    to calculate the internal rate of return.
+     *                                Values must contain at least one positive value and one negative value to
+     *                                    calculate the internal rate of return.
+     * @param mixed $guess A number that you guess is close to the result of IRR
+     *
+     * @return float|string
+     */
+    public static function rate($values, $guess = 0.1)
+    {
+        if (!is_array($values)) {
+            return Functions::VALUE();
+        }
+        $values = Functions::flattenArray($values);
+        $guess = Functions::flattenSingleValue($guess);
+
+        // create an initial range, with a root somewhere between 0 and guess
+        $x1 = 0.0;
+        $x2 = $guess;
+        $f1 = self::presentValue($x1, $values);
+        $f2 = self::presentValue($x2, $values);
+        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
+            if (($f1 * $f2) < 0.0) {
+                break;
+            }
+            if (abs($f1) < abs($f2)) {
+                $f1 = self::presentValue($x1 += 1.6 * ($x1 - $x2), $values);
+            } else {
+                $f2 = self::presentValue($x2 += 1.6 * ($x2 - $x1), $values);
+            }
+        }
+        if (($f1 * $f2) > 0.0) {
+            return Functions::VALUE();
+        }
+
+        $f = self::presentValue($x1, $values);
+        if ($f < 0.0) {
+            $rtb = $x1;
+            $dx = $x2 - $x1;
+        } else {
+            $rtb = $x2;
+            $dx = $x1 - $x2;
+        }
+
+        for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
+            $dx *= 0.5;
+            $x_mid = $rtb + $dx;
+            $f_mid = self::presentValue($x_mid, $values);
+            if ($f_mid <= 0.0) {
+                $rtb = $x_mid;
+            }
+            if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
+                return $x_mid;
+            }
+        }
+
+        return Functions::VALUE();
+    }
+
+    /**
+     * MIRR.
+     *
+     * Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both
+     *        the cost of the investment and the interest received on reinvestment of cash.
+     *
+     * Excel Function:
+     *        MIRR(values,finance_rate, reinvestment_rate)
+     *
+     * @param mixed $values An array or a reference to cells that contain a series of payments and
+     *                         income occurring at regular intervals.
+     *                      Payments are negative value, income is positive values.
+     * @param mixed $financeRate The interest rate you pay on the money used in the cash flows
+     * @param mixed $reinvestmentRate The interest rate you receive on the cash flows as you reinvest them
+     *
+     * @return float|string Result, or a string containing an error
+     */
+    public static function modifiedRate($values, $financeRate, $reinvestmentRate)
+    {
+        if (!is_array($values)) {
+            return Functions::VALUE();
+        }
+        $values = Functions::flattenArray($values);
+        $financeRate = Functions::flattenSingleValue($financeRate);
+        $reinvestmentRate = Functions::flattenSingleValue($reinvestmentRate);
+        $n = count($values);
+
+        $rr = 1.0 + $reinvestmentRate;
+        $fr = 1.0 + $financeRate;
+
+        $npvPos = $npvNeg = 0.0;
+        foreach ($values as $i => $v) {
+            if ($v >= 0) {
+                $npvPos += $v / $rr ** $i;
+            } else {
+                $npvNeg += $v / $fr ** $i;
+            }
+        }
+
+        if (($npvNeg === 0.0) || ($npvPos === 0.0) || ($reinvestmentRate <= -1.0)) {
+            return Functions::VALUE();
+        }
+
+        $mirr = ((-$npvPos * $rr ** $n)
+                / ($npvNeg * ($rr))) ** (1.0 / ($n - 1)) - 1.0;
+
+        return is_finite($mirr) ? $mirr : Functions::VALUE();
+    }
+
+    /**
+     * NPV.
+     *
+     * Returns the Net Present Value of a cash flow series given a discount rate.
+     *
+     * @param mixed $rate
+     *
+     * @return float
+     */
+    public static function presentValue($rate, ...$args)
+    {
+        $returnValue = 0;
+
+        $rate = Functions::flattenSingleValue($rate);
+        $aArgs = Functions::flattenArray($args);
+
+        // Calculate
+        $countArgs = count($aArgs);
+        for ($i = 1; $i <= $countArgs; ++$i) {
+            // Is it a numeric value?
+            if (is_numeric($aArgs[$i - 1])) {
+                $returnValue += $aArgs[$i - 1] / (1 + $rate) ** $i;
+            }
+        }
+
+        return $returnValue;
+    }
+}

+ 19 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Constants.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace PhpOffice\PhpSpreadsheet\Calculation\Financial;
+
+class Constants
+{
+    public const BASIS_DAYS_PER_YEAR_NASD = 0;
+    public const BASIS_DAYS_PER_YEAR_ACTUAL = 1;
+    public const BASIS_DAYS_PER_YEAR_360 = 2;
+    public const BASIS_DAYS_PER_YEAR_365 = 3;
+    public const BASIS_DAYS_PER_YEAR_360_EUROPEAN = 4;
+
+    public const FREQUENCY_ANNUAL = 1;
+    public const FREQUENCY_SEMI_ANNUAL = 2;
+    public const FREQUENCY_QUARTERLY = 4;
+
+    public const PAYMENT_END_OF_PERIOD = 0;
+    public const PAYMENT_BEGINNING_OF_PERIOD = 1;
+}

+ 0 - 0
vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Coupons.php


Some files were not shown because too many files changed in this diff