diff --git a/CHANGELOG.md b/CHANGELOG.md index 95b2274e4..a392f8f14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# v4.1.1-alpha1 + +- Move all banners to GitHub. +- Adds library phpspreadsheet to JCB. +- Add import item example to demo component. +- Updates the Superpower class with the GetRemote class in the plugin. +- Ensures the super power autoloader triggers the correct repositories. + # v4.1.0 - Add [AllowDynamicProperties] in the base view class for J5 diff --git a/ComponentbuilderInstallerScript.php b/ComponentbuilderInstallerScript.php index bf9c93604..ac7165f56 100644 --- a/ComponentbuilderInstallerScript.php +++ b/ComponentbuilderInstallerScript.php @@ -3292,7 +3292,7 @@ class Com_ComponentbuilderInstallerScript implements InstallerScriptInterface echo '
'; +The Component Builder for [Joomla](https://extensions.joomla.org/extension/component-builder/) is highly advanced tool that is truly able to build extremely complex components in a fraction of the time.
diff --git a/componentbuilder_update_server.xml b/componentbuilder_update_server.xml
index 386c66eb1..0b0e3bafe 100644
--- a/componentbuilder_update_server.xml
+++ b/componentbuilder_update_server.xml
@@ -107,4 +107,22 @@
to
+ foreach ($definition->info[$token->name]->attr_transform_pre as $transform) {
+ $attr = $transform->transform($o = $attr, $config, $context);
+ if ($e) {
+ if ($attr != $o) {
+ $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+ }
+ }
+ }
+
+ // create alias to this element's attribute definition array, see
+ // also $d_defs (global attribute definition array)
+ // DEFINITION CALL
+ $defs = $definition->info[$token->name]->attr;
+
+ $attr_key = false;
+ $context->register('CurrentAttr', $attr_key);
+
+ // iterate through all the attribute keypairs
+ // Watch out for name collisions: $key has previously been used
+ foreach ($attr as $attr_key => $value) {
+
+ // call the definition
+ if (isset($defs[$attr_key])) {
+ // there is a local definition defined
+ if ($defs[$attr_key] === false) {
+ // We've explicitly been told not to allow this element.
+ // This is usually when there's a global definition
+ // that must be overridden.
+ // Theoretically speaking, we could have a
+ // AttrDef_DenyAll, but this is faster!
+ $result = false;
+ } else {
+ // validate according to the element's definition
+ $result = $defs[$attr_key]->validate(
+ $value,
+ $config,
+ $context
+ );
+ }
+ } elseif (isset($d_defs[$attr_key])) {
+ // there is a global definition defined, validate according
+ // to the global definition
+ $result = $d_defs[$attr_key]->validate(
+ $value,
+ $config,
+ $context
+ );
+ } else {
+ // system never heard of the attribute? DELETE!
+ $result = false;
+ }
+
+ // put the results into effect
+ if ($result === false || $result === null) {
+ // this is a generic error message that should replaced
+ // with more specific ones when possible
+ if ($e) {
+ $e->send(E_ERROR, 'AttrValidator: Attribute removed');
+ }
+
+ // remove the attribute
+ unset($attr[$attr_key]);
+ } elseif (is_string($result)) {
+ // generally, if a substitution is happening, there
+ // was some sort of implicit correction going on. We'll
+ // delegate it to the attribute classes to say exactly what.
+
+ // simple substitution
+ $attr[$attr_key] = $result;
+ } else {
+ // nothing happens
+ }
+
+ // we'd also want slightly more complicated substitution
+ // involving an array as the return value,
+ // although we're not sure how colliding attributes would
+ // resolve (certain ones would be completely overriden,
+ // others would prepend themselves).
+ }
+
+ $context->destroy('CurrentAttr');
+
+ // post transforms
+
+ // global (error reporting untested)
+ foreach ($definition->info_attr_transform_post as $transform) {
+ $attr = $transform->transform($o = $attr, $config, $context);
+ if ($e) {
+ if ($attr != $o) {
+ $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+ }
+ }
+ }
+
+ // local (error reporting untested)
+ foreach ($definition->info[$token->name]->attr_transform_post as $transform) {
+ $attr = $transform->transform($o = $attr, $config, $context);
+ if ($e) {
+ if ($attr != $o) {
+ $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+ }
+ }
+ }
+
+ $token->attr = $attr;
+
+ // destroy CurrentToken if we made it ourselves
+ if (!$current_token) {
+ $context->destroy('CurrentToken');
+ }
+
+ }
+
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php
new file mode 100644
index 000000000..bd8f9984f
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php
@@ -0,0 +1,91 @@
+
+if (!defined('PHP_EOL')) {
+ switch (strtoupper(substr(PHP_OS, 0, 3))) {
+ case 'WIN':
+ define('PHP_EOL', "\r\n");
+ break;
+ case 'DAR':
+ define('PHP_EOL', "\r");
+ break;
+ default:
+ define('PHP_EOL', "\n");
+ }
+}
+
+/**
+ * Bootstrap class that contains meta-functionality for HTML Purifier such as
+ * the autoload function.
+ *
+ * @note
+ * This class may be used without any other files from HTML Purifier.
+ */
+class HTMLPurifier_Bootstrap
+{
+
+ /**
+ * Autoload function for HTML Purifier
+ * @param string $class Class to load
+ * @return bool
+ */
+ public static function autoload($class)
+ {
+ $file = HTMLPurifier_Bootstrap::getPath($class);
+ if (!$file) {
+ return false;
+ }
+ // Technically speaking, it should be ok and more efficient to
+ // just do 'require', but Antonio Parraga reports that with
+ // Zend extensions such as Zend debugger and APC, this invariant
+ // may be broken. Since we have efficient alternatives, pay
+ // the cost here and avoid the bug.
+ require_once HTMLPURIFIER_PREFIX . '/' . $file;
+ return true;
+ }
+
+ /**
+ * Returns the path for a specific class.
+ * @param string $class Class path to get
+ * @return string
+ */
+ public static function getPath($class)
+ {
+ if (strncmp('HTMLPurifier', $class, 12) !== 0) {
+ return false;
+ }
+ // Custom implementations
+ if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) {
+ $code = str_replace('_', '-', substr($class, 22));
+ $file = 'HTMLPurifier/Language/classes/' . $code . '.php';
+ } else {
+ $file = str_replace('_', '/', $class) . '.php';
+ }
+ if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) {
+ return false;
+ }
+ return $file;
+ }
+
+ /**
+ * "Pre-registers" our autoloader on the SPL stack.
+ */
+ public static function registerAutoload()
+ {
+ $autoload = array('HTMLPurifier_Bootstrap', 'autoload');
+ if (spl_autoload_functions() === false) {
+ spl_autoload_register($autoload);
+ } else {
+ // prepend flag exists, no need for shenanigans
+ spl_autoload_register($autoload, true, true);
+ }
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php
new file mode 100644
index 000000000..1bc419c53
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php
@@ -0,0 +1,566 @@
+info['text-align'] = new HTMLPurifier_AttrDef_Enum(
+ ['left', 'right', 'center', 'justify'],
+ false
+ );
+
+ $border_style =
+ $this->info['border-bottom-style'] =
+ $this->info['border-right-style'] =
+ $this->info['border-left-style'] =
+ $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
+ [
+ 'none',
+ 'hidden',
+ 'dotted',
+ 'dashed',
+ 'solid',
+ 'double',
+ 'groove',
+ 'ridge',
+ 'inset',
+ 'outset'
+ ],
+ false
+ );
+
+ $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
+
+ $this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
+ ['none', 'left', 'right', 'both'],
+ false
+ );
+ $this->info['float'] = new HTMLPurifier_AttrDef_Enum(
+ ['none', 'left', 'right'],
+ false
+ );
+ $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
+ ['normal', 'italic', 'oblique'],
+ false
+ );
+ $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
+ ['normal', 'small-caps'],
+ false
+ );
+
+ $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_Enum(['none']),
+ new HTMLPurifier_AttrDef_CSS_URI()
+ ]
+ );
+
+ $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
+ ['inside', 'outside'],
+ false
+ );
+ $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
+ [
+ 'disc',
+ 'circle',
+ 'square',
+ 'decimal',
+ 'lower-roman',
+ 'upper-roman',
+ 'lower-alpha',
+ 'upper-alpha',
+ 'none'
+ ],
+ false
+ );
+ $this->info['list-style-image'] = $uri_or_none;
+
+ $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
+
+ $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
+ ['capitalize', 'uppercase', 'lowercase', 'none'],
+ false
+ );
+ $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
+
+ $this->info['background-image'] = $uri_or_none;
+ $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum(
+ ['repeat', 'repeat-x', 'repeat-y', 'no-repeat']
+ );
+ $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
+ ['scroll', 'fixed']
+ );
+ $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
+
+ $this->info['background-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_Enum(
+ [
+ 'auto',
+ 'cover',
+ 'contain',
+ 'initial',
+ 'inherit',
+ ]
+ ),
+ new HTMLPurifier_AttrDef_CSS_Percentage(),
+ new HTMLPurifier_AttrDef_CSS_Length()
+ ]
+ );
+
+ $border_color =
+ $this->info['border-top-color'] =
+ $this->info['border-bottom-color'] =
+ $this->info['border-left-color'] =
+ $this->info['border-right-color'] =
+ $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_Enum(['transparent']),
+ new HTMLPurifier_AttrDef_CSS_Color()
+ ]
+ );
+
+ $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
+
+ $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);
+
+ $border_width =
+ $this->info['border-top-width'] =
+ $this->info['border-bottom-width'] =
+ $this->info['border-left-width'] =
+ $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_Enum(['thin', 'medium', 'thick']),
+ new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
+ ]
+ );
+
+ $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
+
+ $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_Enum(['normal']),
+ new HTMLPurifier_AttrDef_CSS_Length()
+ ]
+ );
+
+ $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_Enum(['normal']),
+ new HTMLPurifier_AttrDef_CSS_Length()
+ ]
+ );
+
+ $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_Enum(
+ [
+ 'xx-small',
+ 'x-small',
+ 'small',
+ 'medium',
+ 'large',
+ 'x-large',
+ 'xx-large',
+ 'larger',
+ 'smaller'
+ ]
+ ),
+ new HTMLPurifier_AttrDef_CSS_Percentage(),
+ new HTMLPurifier_AttrDef_CSS_Length()
+ ]
+ );
+
+ $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_Enum(['normal']),
+ new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
+ new HTMLPurifier_AttrDef_CSS_Length('0'),
+ new HTMLPurifier_AttrDef_CSS_Percentage(true)
+ ]
+ );
+
+ $margin =
+ $this->info['margin-top'] =
+ $this->info['margin-bottom'] =
+ $this->info['margin-left'] =
+ $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_CSS_Length(),
+ new HTMLPurifier_AttrDef_CSS_Percentage(),
+ new HTMLPurifier_AttrDef_Enum(['auto'])
+ ]
+ );
+
+ $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
+
+ // non-negative
+ $padding =
+ $this->info['padding-top'] =
+ $this->info['padding-bottom'] =
+ $this->info['padding-left'] =
+ $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_CSS_Length('0'),
+ new HTMLPurifier_AttrDef_CSS_Percentage(true)
+ ]
+ );
+
+ $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
+
+ $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_CSS_Length(),
+ new HTMLPurifier_AttrDef_CSS_Percentage()
+ ]
+ );
+
+ $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_CSS_Length('0'),
+ new HTMLPurifier_AttrDef_CSS_Percentage(true),
+ new HTMLPurifier_AttrDef_Enum(['auto', 'initial', 'inherit'])
+ ]
+ );
+ $trusted_min_wh = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_CSS_Length('0'),
+ new HTMLPurifier_AttrDef_CSS_Percentage(true),
+ new HTMLPurifier_AttrDef_Enum(['initial', 'inherit'])
+ ]
+ );
+ $trusted_max_wh = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_CSS_Length('0'),
+ new HTMLPurifier_AttrDef_CSS_Percentage(true),
+ new HTMLPurifier_AttrDef_Enum(['none', 'initial', 'inherit'])
+ ]
+ );
+ $max = $config->get('CSS.MaxImgLength');
+
+ $this->info['width'] =
+ $this->info['height'] =
+ $max === null ?
+ $trusted_wh :
+ new HTMLPurifier_AttrDef_Switch(
+ 'img',
+ // For img tags:
+ new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_CSS_Length('0', $max),
+ new HTMLPurifier_AttrDef_Enum(['auto'])
+ ]
+ ),
+ // For everyone else:
+ $trusted_wh
+ );
+ $this->info['min-width'] =
+ $this->info['min-height'] =
+ $max === null ?
+ $trusted_min_wh :
+ new HTMLPurifier_AttrDef_Switch(
+ 'img',
+ // For img tags:
+ new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_CSS_Length('0', $max),
+ new HTMLPurifier_AttrDef_Enum(['initial', 'inherit'])
+ ]
+ ),
+ // For everyone else:
+ $trusted_min_wh
+ );
+ $this->info['max-width'] =
+ $this->info['max-height'] =
+ $max === null ?
+ $trusted_max_wh :
+ new HTMLPurifier_AttrDef_Switch(
+ 'img',
+ // For img tags:
+ new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_CSS_Length('0', $max),
+ new HTMLPurifier_AttrDef_Enum(['none', 'initial', 'inherit'])
+ ]
+ ),
+ // For everyone else:
+ $trusted_max_wh
+ );
+
+ // text-decoration and related shorthands
+ $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
+
+ $this->info['text-decoration-line'] = new HTMLPurifier_AttrDef_Enum(
+ ['none', 'underline', 'overline', 'line-through', 'initial', 'inherit']
+ );
+
+ $this->info['text-decoration-style'] = new HTMLPurifier_AttrDef_Enum(
+ ['solid', 'double', 'dotted', 'dashed', 'wavy', 'initial', 'inherit']
+ );
+
+ $this->info['text-decoration-color'] = new HTMLPurifier_AttrDef_CSS_Color();
+
+ $this->info['text-decoration-thickness'] = new HTMLPurifier_AttrDef_CSS_Composite([
+ new HTMLPurifier_AttrDef_CSS_Length(),
+ new HTMLPurifier_AttrDef_CSS_Percentage(),
+ new HTMLPurifier_AttrDef_Enum(['auto', 'from-font', 'initial', 'inherit'])
+ ]);
+
+ $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
+
+ // this could use specialized code
+ $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
+ [
+ 'normal',
+ 'bold',
+ 'bolder',
+ 'lighter',
+ '100',
+ '200',
+ '300',
+ '400',
+ '500',
+ '600',
+ '700',
+ '800',
+ '900'
+ ],
+ false
+ );
+
+ // MUST be called after other font properties, as it references
+ // a CSSDefinition object
+ $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);
+
+ // same here
+ $this->info['border'] =
+ $this->info['border-bottom'] =
+ $this->info['border-top'] =
+ $this->info['border-left'] =
+ $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
+
+ $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(
+ ['collapse', 'separate']
+ );
+
+ $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(
+ ['top', 'bottom']
+ );
+
+ $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(
+ ['auto', 'fixed']
+ );
+
+ $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_Enum(
+ [
+ 'baseline',
+ 'sub',
+ 'super',
+ 'top',
+ 'text-top',
+ 'middle',
+ 'bottom',
+ 'text-bottom'
+ ]
+ ),
+ new HTMLPurifier_AttrDef_CSS_Length(),
+ new HTMLPurifier_AttrDef_CSS_Percentage()
+ ]
+ );
+
+ $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
+
+ // These CSS properties don't work on many browsers, but we live
+ // in THE FUTURE!
+ $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(
+ ['nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line']
+ );
+
+ if ($config->get('CSS.Proprietary')) {
+ $this->doSetupProprietary($config);
+ }
+
+ if ($config->get('CSS.AllowTricky')) {
+ $this->doSetupTricky($config);
+ }
+
+ if ($config->get('CSS.Trusted')) {
+ $this->doSetupTrusted($config);
+ }
+
+ $allow_important = $config->get('CSS.AllowImportant');
+ // wrap all attr-defs with decorator that handles !important
+ foreach ($this->info as $k => $v) {
+ $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important);
+ }
+
+ $this->setupConfigStuff($config);
+ }
+
+ /**
+ * @param HTMLPurifier_Config $config
+ */
+ protected function doSetupProprietary($config)
+ {
+ // Internet Explorer only scrollbar colors
+ $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
+ $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
+ $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
+ $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
+ $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
+ $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
+
+ // vendor specific prefixes of opacity
+ $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
+ $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
+
+ // only opacity, for now
+ $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
+
+ // more CSS3
+ $this->info['page-break-after'] =
+ $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(
+ [
+ 'auto',
+ 'always',
+ 'avoid',
+ 'left',
+ 'right'
+ ]
+ );
+ $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(['auto', 'avoid']);
+
+ $border_radius = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_CSS_Percentage(true), // disallow negative
+ new HTMLPurifier_AttrDef_CSS_Length('0') // disallow negative
+ ]);
+
+ $this->info['border-top-left-radius'] =
+ $this->info['border-top-right-radius'] =
+ $this->info['border-bottom-right-radius'] =
+ $this->info['border-bottom-left-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 2);
+ // TODO: support SLASH syntax
+ $this->info['border-radius'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_radius, 4);
+
+ }
+
+ /**
+ * @param HTMLPurifier_Config $config
+ */
+ protected function doSetupTricky($config)
+ {
+ $this->info['display'] = new HTMLPurifier_AttrDef_Enum(
+ [
+ 'inline',
+ 'block',
+ 'list-item',
+ 'run-in',
+ 'compact',
+ 'marker',
+ 'table',
+ 'inline-block',
+ 'inline-table',
+ 'table-row-group',
+ 'table-header-group',
+ 'table-footer-group',
+ 'table-row',
+ 'table-column-group',
+ 'table-column',
+ 'table-cell',
+ 'table-caption',
+ 'none'
+ ]
+ );
+ $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(
+ ['visible', 'hidden', 'collapse']
+ );
+ $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(['visible', 'hidden', 'auto', 'scroll']);
+ $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
+ }
+
+ /**
+ * @param HTMLPurifier_Config $config
+ */
+ protected function doSetupTrusted($config)
+ {
+ $this->info['position'] = new HTMLPurifier_AttrDef_Enum(
+ ['static', 'relative', 'absolute', 'fixed']
+ );
+ $this->info['top'] =
+ $this->info['left'] =
+ $this->info['right'] =
+ $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_CSS_Length(),
+ new HTMLPurifier_AttrDef_CSS_Percentage(),
+ new HTMLPurifier_AttrDef_Enum(['auto']),
+ ]
+ );
+ $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(
+ [
+ new HTMLPurifier_AttrDef_Integer(),
+ new HTMLPurifier_AttrDef_Enum(['auto']),
+ ]
+ );
+ }
+
+ /**
+ * Performs extra config-based processing. Based off of
+ * HTMLPurifier_HTMLDefinition.
+ * @param HTMLPurifier_Config $config
+ * @todo Refactor duplicate elements into common class (probably using
+ * composition, not inheritance).
+ */
+ protected function setupConfigStuff($config)
+ {
+ // setup allowed elements
+ $support = "(for information on implementing this, see the " .
+ "support forums) ";
+ $allowed_properties = $config->get('CSS.AllowedProperties');
+ if ($allowed_properties !== null) {
+ foreach ($this->info as $name => $d) {
+ if (!isset($allowed_properties[$name])) {
+ unset($this->info[$name]);
+ }
+ unset($allowed_properties[$name]);
+ }
+ // emit errors
+ foreach ($allowed_properties as $name => $d) {
+ // :TODO: Is this htmlspecialchars() call really necessary?
+ $name = htmlspecialchars($name);
+ trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
+ }
+ }
+
+ $forbidden_properties = $config->get('CSS.ForbiddenProperties');
+ if ($forbidden_properties !== null) {
+ foreach ($this->info as $name => $d) {
+ if (isset($forbidden_properties[$name])) {
+ unset($this->info[$name]);
+ }
+ }
+ }
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php
new file mode 100644
index 000000000..8eb17b82e
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php
@@ -0,0 +1,52 @@
+elements;
+ }
+
+ /**
+ * Validates nodes according to definition and returns modification.
+ *
+ * @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node
+ * @param HTMLPurifier_Config $config HTMLPurifier_Config object
+ * @param HTMLPurifier_Context $context HTMLPurifier_Context object
+ * @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children
+ */
+ abstract public function validateChildren($children, $config, $context);
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php
new file mode 100644
index 000000000..7439be26b
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php
@@ -0,0 +1,67 @@
+inline = new HTMLPurifier_ChildDef_Optional($inline);
+ $this->block = new HTMLPurifier_ChildDef_Optional($block);
+ $this->elements = $this->block->elements;
+ }
+
+ /**
+ * @param HTMLPurifier_Node[] $children
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function validateChildren($children, $config, $context)
+ {
+ if ($context->get('IsInline') === false) {
+ return $this->block->validateChildren(
+ $children,
+ $config,
+ $context
+ );
+ } else {
+ return $this->inline->validateChildren(
+ $children,
+ $config,
+ $context
+ );
+ }
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php
new file mode 100644
index 000000000..f515888a1
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php
@@ -0,0 +1,102 @@
+dtd_regex = $dtd_regex;
+ $this->_compileRegex();
+ }
+
+ /**
+ * Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex)
+ */
+ protected function _compileRegex()
+ {
+ $raw = str_replace(' ', '', $this->dtd_regex);
+ if ($raw[0] != '(') {
+ $raw = "($raw)";
+ }
+ $el = '[#a-zA-Z0-9_.-]+';
+ $reg = $raw;
+
+ // COMPLICATED! AND MIGHT BE BUGGY! I HAVE NO CLUE WHAT I'M
+ // DOING! Seriously: if there's problems, please report them.
+
+ // collect all elements into the $elements array
+ preg_match_all("/$el/", $reg, $matches);
+ foreach ($matches[0] as $match) {
+ $this->elements[$match] = true;
+ }
+
+ // setup all elements as parentheticals with leading commas
+ $reg = preg_replace("/$el/", '(,\\0)', $reg);
+
+ // remove commas when they were not solicited
+ $reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg);
+
+ // remove all non-paranthetical commas: they are handled by first regex
+ $reg = preg_replace("/,\(/", '(', $reg);
+
+ $this->_pcre_regex = $reg;
+ }
+
+ /**
+ * @param HTMLPurifier_Node[] $children
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function validateChildren($children, $config, $context)
+ {
+ $list_of_children = '';
+ $nesting = 0; // depth into the nest
+ foreach ($children as $node) {
+ if (!empty($node->is_whitespace)) {
+ continue;
+ }
+ $list_of_children .= $node->name . ',';
+ }
+ // add leading comma to deal with stray comma declarations
+ $list_of_children = ',' . rtrim($list_of_children, ',');
+ $okay =
+ preg_match(
+ '/^,?' . $this->_pcre_regex . '$/',
+ $list_of_children
+ );
+ return (bool)$okay;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php
new file mode 100644
index 000000000..a8a6cbdd2
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php
@@ -0,0 +1,38 @@
+ true, 'ul' => true, 'ol' => true);
+
+ public $whitespace;
+
+ /**
+ * @param array $children
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return array
+ */
+ public function validateChildren($children, $config, $context)
+ {
+ // Flag for subclasses
+ $this->whitespace = false;
+
+ // if there are no tokens, delete parent node
+ if (empty($children)) {
+ return false;
+ }
+
+ // if li is not allowed, delete parent node
+ if (!isset($config->getHTMLDefinition()->info['li'])) {
+ trigger_error("Cannot allow ul/ol without allowing li", E_USER_WARNING);
+ return false;
+ }
+
+ // the new set of children
+ $result = array();
+
+ // a little sanity check to make sure it's not ALL whitespace
+ $all_whitespace = true;
+
+ $current_li = null;
+
+ foreach ($children as $node) {
+ if (!empty($node->is_whitespace)) {
+ $result[] = $node;
+ continue;
+ }
+ $all_whitespace = false; // phew, we're not talking about whitespace
+
+ if ($node->name === 'li') {
+ // good
+ $current_li = $node;
+ $result[] = $node;
+ } else {
+ // we want to tuck this into the previous li
+ // Invariant: we expect the node to be ol/ul
+ // ToDo: Make this more robust in the case of not ol/ul
+ // by distinguishing between existing li and li created
+ // to handle non-list elements; non-list elements should
+ // not be appended to an existing li; only li created
+ // for non-list. This distinction is not currently made.
+ if ($current_li === null) {
+ $current_li = new HTMLPurifier_Node_Element('li');
+ $result[] = $current_li;
+ }
+ $current_li->children[] = $node;
+ $current_li->empty = false; // XXX fascinating! Check for this error elsewhere ToDo
+ }
+ }
+ if (empty($result)) {
+ return false;
+ }
+ if ($all_whitespace) {
+ return false;
+ }
+ return $result;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php
new file mode 100644
index 000000000..b9468063b
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php
@@ -0,0 +1,45 @@
+whitespace) {
+ return $children;
+ } else {
+ return array();
+ }
+ }
+ return $result;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php
new file mode 100644
index 000000000..0d1c8f5f3
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php
@@ -0,0 +1,118 @@
+ $x) {
+ $elements[$i] = true;
+ if (empty($i)) {
+ unset($elements[$i]);
+ } // remove blank
+ }
+ }
+ $this->elements = $elements;
+ }
+
+ /**
+ * @type bool
+ */
+ public $allow_empty = false;
+
+ /**
+ * @type string
+ */
+ public $type = 'required';
+
+ /**
+ * @param array $children
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return array
+ */
+ public function validateChildren($children, $config, $context)
+ {
+ // Flag for subclasses
+ $this->whitespace = false;
+
+ // if there are no tokens, delete parent node
+ if (empty($children)) {
+ return false;
+ }
+
+ // the new set of children
+ $result = array();
+
+ // whether or not parsed character data is allowed
+ // this controls whether or not we silently drop a tag
+ // or generate escaped HTML from it
+ $pcdata_allowed = isset($this->elements['#PCDATA']);
+
+ // a little sanity check to make sure it's not ALL whitespace
+ $all_whitespace = true;
+
+ $stack = array_reverse($children);
+ while (!empty($stack)) {
+ $node = array_pop($stack);
+ if (!empty($node->is_whitespace)) {
+ $result[] = $node;
+ continue;
+ }
+ $all_whitespace = false; // phew, we're not talking about whitespace
+
+ if (!isset($this->elements[$node->name])) {
+ // special case text
+ // XXX One of these ought to be redundant or something
+ if ($pcdata_allowed && $node instanceof HTMLPurifier_Node_Text) {
+ $result[] = $node;
+ continue;
+ }
+ // spill the child contents in
+ // ToDo: Make configurable
+ if ($node instanceof HTMLPurifier_Node_Element) {
+ for ($i = count($node->children) - 1; $i >= 0; $i--) {
+ $stack[] = $node->children[$i];
+ }
+ continue;
+ }
+ continue;
+ }
+ $result[] = $node;
+ }
+ if (empty($result)) {
+ return false;
+ }
+ if ($all_whitespace) {
+ $this->whitespace = true;
+ return false;
+ }
+ return $result;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php
new file mode 100644
index 000000000..3270a46e1
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php
@@ -0,0 +1,110 @@
+init($config);
+ return $this->fake_elements;
+ }
+
+ /**
+ * @param array $children
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return array
+ */
+ public function validateChildren($children, $config, $context)
+ {
+ $this->init($config);
+
+ // trick the parent class into thinking it allows more
+ $this->elements = $this->fake_elements;
+ $result = parent::validateChildren($children, $config, $context);
+ $this->elements = $this->real_elements;
+
+ if ($result === false) {
+ return array();
+ }
+ if ($result === true) {
+ $result = $children;
+ }
+
+ $def = $config->getHTMLDefinition();
+ $block_wrap_name = $def->info_block_wrapper;
+ $block_wrap = false;
+ $ret = array();
+
+ foreach ($result as $node) {
+ if ($block_wrap === false) {
+ if (($node instanceof HTMLPurifier_Node_Text && !$node->is_whitespace) ||
+ ($node instanceof HTMLPurifier_Node_Element && !isset($this->elements[$node->name]))) {
+ $block_wrap = new HTMLPurifier_Node_Element($def->info_block_wrapper);
+ $ret[] = $block_wrap;
+ }
+ } else {
+ if ($node instanceof HTMLPurifier_Node_Element && isset($this->elements[$node->name])) {
+ $block_wrap = false;
+
+ }
+ }
+ if ($block_wrap) {
+ $block_wrap->children[] = $node;
+ } else {
+ $ret[] = $node;
+ }
+ }
+ return $ret;
+ }
+
+ /**
+ * @param HTMLPurifier_Config $config
+ */
+ private function init($config)
+ {
+ if (!$this->init) {
+ $def = $config->getHTMLDefinition();
+ // allow all inline elements
+ $this->real_elements = $this->elements;
+ $this->fake_elements = $def->info_content_sets['Flow'];
+ $this->fake_elements['#PCDATA'] = true;
+ $this->init = true;
+ }
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php
new file mode 100644
index 000000000..67c7e9535
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php
@@ -0,0 +1,224 @@
+ true,
+ 'tbody' => true,
+ 'thead' => true,
+ 'tfoot' => true,
+ 'caption' => true,
+ 'colgroup' => true,
+ 'col' => true
+ );
+
+ public function __construct()
+ {
+ }
+
+ /**
+ * @param array $children
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return array
+ */
+ public function validateChildren($children, $config, $context)
+ {
+ if (empty($children)) {
+ return false;
+ }
+
+ // only one of these elements is allowed in a table
+ $caption = false;
+ $thead = false;
+ $tfoot = false;
+
+ // whitespace
+ $initial_ws = array();
+ $after_caption_ws = array();
+ $after_thead_ws = array();
+ $after_tfoot_ws = array();
+
+ // as many of these as you want
+ $cols = array();
+ $content = array();
+
+ $tbody_mode = false; // if true, then we need to wrap any stray
+ //
+ This directive turns on auto-paragraphing, where double newlines are
+ converted in to paragraphs whenever possible. Auto-paragraphing:
+
+
+ To prevent auto-paragraphing as a content-producer, refrain from using
+ double-newlines except to specify a new paragraph or in contexts where
+ it has special meaning (whitespace usually has no meaning except in
+ tags like
+ This directive can be used to add custom auto-format injectors.
+ Specify an array of injector names (class name minus the prefix)
+ or concrete implementations. Injector class must exist.
+
+ This directive turns on the in-text display of URIs in <a> tags, and disables
+ those links. For example, example becomes
+ example (http://example.com).
+
+ This directive turns on linkification, auto-linking http, ftp and
+ https URLs.
+ Location of configuration documentation to link to, let %s substitute
+ into the configuration's namespace and directive names sans the percent
+ sign.
+
+ Internal auto-formatter that converts configuration directives in
+ syntax %Namespace.Directive to links.
+ Given that an element has no contents, it will be removed by default, unless
+ this predicate dictates otherwise. The predicate can either be an associative
+ map from tag name to list of attributes that must be present for the element
+ to be considered preserved: thus, the default always preserves
+ When %AutoFormat.RemoveEmpty and %AutoFormat.RemoveEmpty.RemoveNbsp
+ are enabled, this directive defines what HTML elements should not be
+ removede if they have only a non-breaking space in them.
+
+ When enabled, HTML Purifier will treat any elements that contain only
+ non-breaking spaces as well as regular whitespace as empty, and remove
+ them when %AutoFormat.RemoveEmpty is enabled.
+
+ See %AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions for a list of elements
+ that don't have this behavior applied to them.
+
+ When enabled, HTML Purifier will attempt to remove empty elements that
+ contribute no semantic information to the document. The following types
+ of nodes will be removed:
+
+ Please be very careful when using this functionality; while it may not
+ seem that empty elements contain useful information, they can alter the
+ layout of a document given appropriate styling. This directive is most
+ useful when you are processing machine-generated HTML, please avoid using
+ it on regular user HTML.
+
+ Elements that contain only whitespace will be treated as empty. Non-breaking
+ spaces, however, do not count as whitespace. See
+ %AutoFormat.RemoveEmpty.RemoveNbsp for alternate behavior.
+
+ This algorithm is not perfect; you may still notice some empty tags,
+ particularly if a node had elements, but those elements were later removed
+ because they were not permitted in that context, or tags that, after
+ being auto-closed by another tag, where empty. This is for safety reasons
+ to prevent clever code from breaking validation. The general rule of thumb:
+ if a tag looked empty on the way in, it will get removed; if HTML Purifier
+ made it empty, it will stay.
+
+ This directive causes
+ By default, HTML Purifier removes duplicate CSS properties,
+ like
+ Allows you to manually specify a set of allowed fonts. If
+
+ If HTML Purifier's style attributes set is unsatisfactory for your needs,
+ you can overload it with your own list of tags to allow. Note that this
+ method is subtractive: it does its job by taking away from HTML Purifier
+ usual feature set, so you cannot add an attribute that HTML Purifier never
+ supported in the first place.
+
+ Warning: If another directive conflicts with the
+ elements here, that directive will win and override.
+
+ Revision identifier for your custom definition. See
+ %HTML.DefinitionRev for details.
+
+ This is the logical inverse of %CSS.AllowedProperties, and it will
+ override that directive or any other directive. If possible,
+ %CSS.AllowedProperties is recommended over this directive,
+ because it can sometimes be difficult to tell whether or not you've
+ forbidden all of the CSS properties you truly would like to disallow.
+
+ This parameter sets the maximum allowed length on
+ Whether or not to allow safe, proprietary CSS values.
+
+ Absolute path with no trailing slash to store serialized definitions in.
+ Default is within the
+ HTML Purifier library inside DefinitionCache/Serializer. This
+ path must be writable by the webserver.
+
+ Directory permissions of the files and directories created inside
+ the DefinitionCache/Serializer or other custom serializer path.
+
+ In HTML Purifier 4.8.0, this also supports
+ This directive enables aggressive pre-filter fixes HTML Purifier can
+ perform in order to ensure that open angled-brackets do not get killed
+ during parsing stage. Enabling this will result in two preg_replace_callback
+ calls and at least two preg_replace calls for every HTML document parsed;
+ if your users make very well-formed HTML, you can set this directive false.
+ This has no effect when DirectLex is used.
+
+ Notice: This directive's default turned from false to true
+ in HTML Purifier 3.2.0.
+
+ This directive enables aggressive pre-filter removal of
+ script tags. This is not necessary for security,
+ but it can help work around a bug in libxml where embedded
+ HTML elements inside script sections cause the parser to
+ choke. To revert to pre-4.9.0 behavior, set this to false.
+ This directive has no effect if %Core.Trusted is true,
+ %Core.RemoveScriptContents is false, or %Core.HiddenElements
+ does not contain script.
+
+ By RFC 1123, underscores are not permitted in host names.
+ (This is in contrast to the specification for DNS, RFC
+ 2181, which allows underscores.)
+ However, most browsers do the right thing when faced with
+ an underscore in the host name, and so some poorly written
+ websites are written with the expectation this should work.
+ Setting this parameter to true relaxes our allowed character
+ check so that underscores are permitted.
+
+ This directive allows parsing of many nested tags.
+ If you set true, relaxes any hardcoded limit from the parser.
+ However, in that case it may cause a Dos attack.
+ Be careful when enabling it.
+
+ Specifies the number of tokens the DirectLex line number tracking
+ implementations should process before attempting to resyncronize the
+ current line count by manually counting all previous new-lines. When
+ at 0, this functionality is disabled. Lower values will decrease
+ performance, and this is only strictly necessary if the counting
+ algorithm is buggy (in which case you should report it as a bug).
+ This has no effect when %Core.MaintainLineNumbers is disabled or DirectLex is
+ not being used.
+
+ This directive disables SGML-style exclusions, e.g. the exclusion of
+ Warning: this configuration option is no longer does anything as of 4.6.0. When true, a child is found that is not allowed in the context of the
+parent element will be transformed into text as if it were ASCII. When
+false, that element and all internal tags will be dropped, though text will
+be preserved. There is no option for dropping the element but preserving
+child nodes.
+ This directive is a lookup array of elements which should have their
+ contents removed when they are not allowed by the HTML definition.
+ For example, the contents of a
+ Prior to HTML Purifier 4.9.0, entities were decoded by performing
+ a global search replace for all entities whose decoded versions
+ did not have special meanings under HTML, and replaced them with
+ their decoded versions. We would match all entities, even if they did
+ not have a trailing semicolon, but only if there weren't any trailing
+ alphanumeric characters.
+
+ In HTML Purifier 4.9.0, we changed the behavior of entity parsing
+ to match entities that had missing trailing semicolons in less
+ cases, to more closely match HTML5 parsing behavior:
+
+ This flag reverts back to pre-HTML Purifier 4.9.0 behavior.
+
+ This parameter determines what lexer implementation can be used. The
+ valid values are:
+
+ If true, HTML Purifier will add line number information to all tokens.
+ This is useful when error reporting is turned on, but can result in
+ significant performance degradation and should not be used when
+ unnecessary. This directive must be used with the DirectLex lexer,
+ as the DOMLex lexer does not (yet) support this functionality.
+ If the value is null, an appropriate value will be selected based
+ on other configuration.
+
+ Whether or not to normalize newlines to the operating
+ system default. When
+ This directive enables pre-emptive URI checking in
+ This directive enables HTML Purifier to remove not only script tags
+ but all of their contents.
+
+ This directive can be used to add custom filters; it is nearly the
+ equivalent of the now deprecated
+ Whether or not to escape the dangerous characters <, > and &
+ as \3C, \3E and \26, respectively. This is can be safely set to false
+ if the contents of StyleBlocks will be placed in an external stylesheet,
+ where there is no risk of it being interpreted as HTML.
+
+ If you would like users to be able to define external stylesheets, but
+ only allow them to specify CSS declarations for a specific node and
+ prevent them from fiddling with other elements, use this directive.
+ It accepts any valid CSS selector, and will prepend this to any
+ CSS declaration extracted from the document. For example, if this
+ directive is set to
+ The comma shorthand may be used; consider the above example, with
+
+ Warning: It is possible for users to bypass this measure
+ using a naughty + selector. This is a bug in CSS Tidy 1.3, not HTML
+ Purifier, and I am working to get it fixed. Until then, HTML Purifier
+ performs a basic check to prevent this.
+
+ If left NULL, HTML Purifier will attempt to instantiate a
+ However, for trusted user input, you can set this to
+ This directive turns on the style block extraction filter, which removes
+
+ Sample usage:
+
+ Warning: It is possible for a user to mount an
+ imagecrash attack using this CSS. Counter-measures are difficult;
+ it is not simply enough to limit the range of CSS lengths (using
+ relative lengths with many nesting levels allows for large values
+ to be attained without actually specifying them in the stylesheet),
+ and the flexible nature of selectors makes it difficult to selectively
+ disable lengths on image tags (HTML Purifier, however, does disable
+ CSS width and height in inline styling). There are probably two effective
+ counter measures: an explicit width and height set to auto in all
+ images in your document (unlikely) or the disabling of width and
+ height (somewhat reasonable). Whether or not these measures should be
+ used is left to the reader.
+
+ Warning: Deprecated in favor of %HTML.SafeObject and
+ %Output.FlashCompat (turn both on to allow YouTube videos and other
+ Flash content).
+
+ This directive enables YouTube video embedding in HTML Purifier. Check
+ this document
+ on embedding videos for more information on what this filter does.
+
+ This is a preferred convenience directive that combines
+ %HTML.AllowedElements and %HTML.AllowedAttributes.
+ Specify elements and attributes that are allowed using:
+
+ Warning:
+ All of the constraints on the component directives are still enforced.
+ The syntax is a subset of TinyMCE's
+ If HTML Purifier's attribute set is unsatisfactory, overload it!
+ The syntax is "tag.attr" or "*.attr" for the global attributes
+ (style, id, class, dir, lang, xml:lang).
+
+ Warning: If another directive conflicts with the
+ elements here, that directive will win and override. For
+ example, %HTML.EnableAttrID will take precedence over *.id in this
+ directive. You must set that directive to true before you can use
+ IDs at all.
+
+ If HTML Purifier's tag set is unsatisfactory for your needs, you can
+ overload it with your own list of tags to allow. If you change
+ this, you probably also want to change %HTML.AllowedAttributes; see
+ also %HTML.Allowed which lets you set allowed elements and
+ attributes at the same time.
+
+ If you attempt to allow an element that HTML Purifier does not know
+ about, HTML Purifier will raise an error. You will need to manually
+ tell HTML Purifier about this element by using the
+ advanced customization features.
+
+ Warning: If another directive conflicts with the
+ elements here, that directive will win and override.
+
+ A doctype comes with a set of usual modules to use. Without having
+ to mucking about with the doctypes, you can quickly activate or
+ disable these modules by specifying which modules you wish to allow
+ with this directive. This is most useful for unit testing specific
+ modules, although end users may find it useful for their own ends.
+
+ If you specify a module that does not exist, the manager will silently
+ fail to use it, so be careful! User-defined modules are not affected
+ by this directive. Modules defined in %HTML.CoreModules are not
+ affected by this directive.
+
+ String name of element to wrap inline elements that are inside a block
+ context. This only occurs in the children of blockquote in strict mode.
+
+ Example: by default value,
+
+ Certain modularized doctypes (XHTML, namely), have certain modules
+ that must be included for the doctype to be an conforming document
+ type: put those modules here. By default, XHTML's core modules
+ are used. You can set this to a blank array to disable core module
+ protection, but this is not recommended.
+
+ Unique identifier for a custom-built HTML definition. If you edit
+ the raw version of the HTMLDefinition, introducing changes that the
+ configuration object does not reflect, you must specify this variable.
+ If you change your custom edits, you should change this directive, or
+ clear your cache. Example:
+
+ In the above example, the configuration is still at the defaults, but
+ using the advanced API, an extra attribute has been added. The
+ configuration object normally has no way of knowing that this change
+ has taken place, so it needs an extra directive: %HTML.DefinitionID.
+ If someone else attempts to use the default configuration, these two
+ pieces of code will not clobber each other in the cache, since one has
+ an extra directive attached to it.
+
+ You must specify a value to this directive to use the
+ advanced API features.
+
+ Revision identifier for your custom definition specified in
+ %HTML.DefinitionID. This serves the same purpose: uniquely identifying
+ your custom definition, but this one does so in a chronological
+ context: revision 3 is more up-to-date then revision 2. Thus, when
+ this gets incremented, the cache handling is smart enough to clean
+ up any older revisions of your definition as well as flush the
+ cache.
+
+ Whether or not to permit embedded Flash content from
+ %HTML.SafeObject to expand to the full screen. Corresponds to
+ the
+ While this directive is similar to %HTML.AllowedAttributes, for
+ forwards-compatibility with XML, this attribute has a different syntax. Instead of
+
+ Warning: This directive complements %HTML.ForbiddenElements,
+ accordingly, check
+ out that directive for a discussion of why you
+ should think twice before using this directive.
+
+ This was, perhaps, the most requested feature ever in HTML
+ Purifier. Please don't abuse it! This is the logical inverse of
+ %HTML.AllowedElements, and it will override that directive, or any
+ other directive.
+
+ If possible, %HTML.Allowed is recommended over this directive, because it
+ can sometimes be difficult to tell whether or not you've forbidden all of
+ the behavior you would like to disallow. If you forbid
+ Whether or not to permit form elements in the user input, regardless of
+ %HTML.Trusted value. Please be very careful when using this functionality, as
+ enabling forms in untrusted documents may allow for phishing attacks.
+
+ This directive controls the maximum number of pixels in the width and
+ height attributes in
+ String name of element that HTML fragment passed to library will be
+ inserted in. An interesting variation would be using span as the
+ parent element, meaning that only inline tags would be allowed.
+
+ Whether or not to allow proprietary elements and attributes in your
+ documents, as per
+ Whether or not to permit embed tags in documents, with a number of extra
+ security features added to prevent script execution. This is similar to
+ what websites like MySpace do to embed tags. Embed is a proprietary
+ element and will cause your website to stop validating; you should
+ see if you can use %Output.FlashCompat with %HTML.SafeObject instead
+ first.
+ Whether or not to permit iframe tags in untrusted documents. This
+ directive must be accompanied by a whitelist of permitted iframes,
+ such as %URI.SafeIframeRegexp, otherwise it will fatally error.
+ This directive has no effect on strict doctypes, as iframes are not
+ valid.
+
+ Whether or not to permit object tags in documents, with a number of extra
+ security features added to prevent script execution. This is similar to
+ what websites like MySpace do to object tags. You should also enable
+ %Output.FlashCompat in order to generate Internet Explorer
+ compatibility code for your object tags.
+
+ Whether or not to permit script tags to external scripts in documents.
+ Inline scripting is not allowed, and the script must match an explicit whitelist.
+ General level of cleanliness the Tidy module should enforce.
+There are four allowed values:
+ If true, HTML Purifier will protect against Internet Explorer's
+ mishandling of the
+ If true, HTML Purifier will generate Internet Explorer compatibility
+ code for all object code. This is highly recommended if you enable
+ %HTML.SafeObject.
+
+ Newline string to format final output with. If left null, HTML Purifier
+ will auto-detect the default newline type of the system and use that;
+ you can manually override it here. Remember, \r\n is Windows, \r
+ is Mac, and \n is Unix.
+
+ If true, HTML Purifier will sort attributes by name before writing them back
+ to the document, converting a tag like:
+ Determines whether or not to run Tidy on the final output for pretty
+ formatting reasons, such as indentation and wrap.
+
+ This can greatly improve readability for editors who are hand-editing
+ the HTML, but is by no means necessary as HTML Purifier has already
+ fixed all major errors the HTML may have had. Tidy is a non-default
+ extension, and this directive will silently fail if Tidy is not
+ available.
+
+ If you are looking to make the overall look of your page's source
+ better, I recommend running Tidy on the entire page rather than just
+ user-content (after all, the indentation relative to the containing
+ blocks will be incorrect).
+
+ The base URI is the URI of the document this purified HTML will be
+ inserted into. This information is important if HTML Purifier needs
+ to calculate absolute URIs from relative URIs, such as when %URI.MakeAbsolute
+ is on. You may use a non-absolute URI for this value, but behavior
+ may vary (%URI.MakeAbsolute deals nicely with both absolute and
+ relative paths, but forwards-compatibility is not guaranteed).
+ Warning: If set, the scheme on this URI
+ overrides the one specified by %URI.DefaultScheme.
+
+ Defines through what scheme the output will be served, in order to
+ select the proper object validator when no scheme information is present.
+
+ Starting with HTML Purifier 4.9.0, the default scheme can be null, in
+ which case we reject all URIs which do not have explicit schemes.
+
+ Unique identifier for a custom-built URI definition. If you want
+ to add custom URIFilters, you must specify this value.
+
+ Revision identifier for your custom definition. See
+ %HTML.DefinitionRev for details.
+
+ Disables all URIs in all forms. Not sure why you'd want to do that
+ (after all, the Internet's founded on the notion of a hyperlink).
+
+ Disables embedding resources, essentially meaning no pictures. You can
+ still link to them though. See %URI.DisableExternalResources for why
+ this might be a good idea.
+
+ Note: While this directive has been available since 1.3.0,
+ it didn't actually start doing anything until 4.2.0.
+
+ Defines the domain name of the server, so we can determine whether or
+ an absolute URI is from your website or not. Not strictly necessary,
+ as users should be using relative URIs to reference resources on your
+ website. It will, however, let you use absolute URIs to link to
+ subdomains of the domain you post here: i.e. example.com will allow
+ sub.example.com. However, higher up domains will still be excluded:
+ if you set %URI.Host to sub.example.com, example.com will be blocked.
+ Note: This directive overrides %URI.Base because
+ a given page may be on a sub-domain, but you wish HTML Purifier to be
+ more relaxed and allow some of the parent domains too.
+
+ Converts all URIs into absolute forms. This is useful when the HTML
+ being filtered assumes a specific base path, but will actually be
+ viewed in a different context (and setting an alternate base URI is
+ not possible). %URI.Base must be set for this directive to work.
+
+ Munges all browsable (usually http, https and ftp)
+ absolute URIs into another URI, usually a URI redirection service.
+ This directive accepts a URI, formatted with a
+ Uses for this directive:
+
+ Prior to HTML Purifier 3.1.1, this directive also enabled the munging
+ of browsable external resources, which could break things if your redirection
+ script was a splash page or used
+ You may want to also use %URI.MungeSecretKey along with this directive
+ in order to enforce what URIs your redirector script allows. Open
+ redirector scripts can be a security risk and negatively affect the
+ reputation of your domain name.
+
+ Starting with HTML Purifier 3.1.1, there is also these substitutions:
+
+ Admittedly, these letters are somewhat arbitrary; the only stipulation
+ was that they couldn't be a through f. r is for resource (I would have preferred
+ e, but you take what you can get), n is for name, m
+ was picked because it came after n (and I couldn't use a), p is for
+ property.
+
+ If true, any URI munging directives like %URI.Munge
+ will also apply to embedded resources, such as
+ Warning: It is strongly advised you use this in conjunction
+ %URI.MungeSecretKey to mitigate the security risk of an open redirector.
+
+ This directive enables secure checksum generation along with %URI.Munge.
+ It should be set to a secure key that is not shared with anyone else.
+ The checksum can be placed in the URI using %t. Use of this checksum
+ affords an additional level of protection by allowing a redirector
+ to check if a URI has passed through HTML Purifier with this line:
+
+ If the output is TRUE, the redirector script should accept the URI.
+
+ Please note that it would still be possible for an attacker to procure
+ secure hashes en-mass by abusing your website's Preview feature or the
+ like, but this service affords an additional level of protection
+ that should be combined with website blacklisting.
+
+ Remember this has no effect if %URI.Munge is not on.
+
+ A PCRE regular expression that will be matched against an iframe URI. This is
+ a relatively inflexible scheme, but works well enough for the most common
+ use-case of iframes: embedded video. This directive only has an effect if
+ %HTML.SafeIframe is enabled. Here are some example values:
+
+ Note that this directive does not give you enough granularity to, say, disable
+ all ' . $this->locale->getMessage('ErrorCollector: No errors') . ' tags?
+ if ($this->allowsElement('p')) {
+ if (empty($this->currentNesting) || strpos($text, "\n\n") !== false) {
+ // Note that we have differing behavior when dealing with text
+ // in the anonymous root node, or a node inside the document.
+ // If the text as a double-newline, the treatment is the same;
+ // if it doesn't, see the next if-block if you're in the document.
+
+ $i = $nesting = null;
+ if (!$this->forwardUntilEndToken($i, $current, $nesting) && $token->is_whitespace) {
+ // State 1.1: ... ^ (whitespace, then document end)
+ // ----
+ // This is a degenerate case
+ } else {
+ if (!$token->is_whitespace || $this->_isInline($current)) {
+ // State 1.2: PAR1
+ // ----
+
+ // State 1.3: PAR1\n\nPAR2
+ // ------------
+
+ // State 1.4: tag?
+ } elseif (!empty($this->currentNesting) &&
+ $this->currentNesting[count($this->currentNesting) - 1]->name == 'p') {
+ // State 3.1: ... PAR1
+ // ----
+
+ // State 3.2: ... PAR1\n\nPAR2
+ // ------------
+ $token = array();
+ $this->_splitText($text, $token);
+ // Abort!
+ } else {
+ // State 4.1: ...PAR1
+ // ----
+
+ // State 4.2: ...PAR1\n\nPAR2
+ // ------------
+ }
+ }
+
+ /**
+ * @param HTMLPurifier_Token $token
+ */
+ public function handleElement(&$token)
+ {
+ // We don't have to check if we're already in a tag for block
+ // tokens, because the tag would have been autoclosed by MakeWellFormed.
+ if ($this->allowsElement('p')) {
+ if (!empty($this->currentNesting)) {
+ if ($this->_isInline($token)) {
+ // State 1: PAR1 PAR1 is needed.
+ if ($this->_pLookAhead()) {
+ // State 1.3.1: tags.
+ }
+ }
+ }
+ } else {
+ // State 2.2:
+ // ---
+ }
+ }
+
+ /**
+ * Splits up a text in paragraph tokens and appends them
+ * to the result stream that will replace the original
+ * @param string $data String text data that will be processed
+ * into paragraphs
+ * @param HTMLPurifier_Token[] $result Reference to array of tokens that the
+ * tags will be appended onto
+ */
+ private function _splitText($data, &$result)
+ {
+ $raw_paragraphs = explode("\n\n", $data);
+ $paragraphs = array(); // without empty paragraphs
+ $needs_start = false;
+ $needs_end = false;
+
+ $c = count($raw_paragraphs);
+ if ($c == 1) {
+ // There were no double-newlines, abort quickly. In theory this
+ // should never happen.
+ $result[] = new HTMLPurifier_Token_Text($data);
+ return;
+ }
+ for ($i = 0; $i < $c; $i++) {
+ $par = $raw_paragraphs[$i];
+ if (trim($par) !== '') {
+ $paragraphs[] = $par;
+ } else {
+ if ($i == 0) {
+ // Double newline at the front
+ if (empty($result)) {
+ // The empty result indicates that the AutoParagraph
+ // injector did not add any start paragraph tokens.
+ // This means that we have been in a paragraph for
+ // a while, and the newline means we should start a new one.
+ $result[] = new HTMLPurifier_Token_End('p');
+ $result[] = new HTMLPurifier_Token_Text("\n\n");
+ // However, the start token should only be added if
+ // there is more processing to be done (i.e. there are
+ // real paragraphs in here). If there are none, the
+ // next start paragraph tag will be handled by the
+ // next call to the injector
+ $needs_start = true;
+ } else {
+ // We just started a new paragraph!
+ // Reinstate a double-newline for presentation's sake, since
+ // it was in the source code.
+ array_unshift($result, new HTMLPurifier_Token_Text("\n\n"));
+ }
+ } elseif ($i + 1 == $c) {
+ // Double newline at the end
+ // There should be a trailing tag.
+ * @return bool
+ */
+ private function _pLookAhead()
+ {
+ if ($this->currentToken instanceof HTMLPurifier_Token_Start) {
+ $nesting = 1;
+ } else {
+ $nesting = 0;
+ }
+ $ok = false;
+ $i = null;
+ while ($this->forwardUntilEndToken($i, $current, $nesting)) {
+ $result = $this->_checkNeedsP($current);
+ if ($result !== null) {
+ $ok = $result;
+ break;
+ }
+ }
+ return $ok;
+ }
+
+ /**
+ * Determines if a particular token requires an earlier inline token
+ * to get a paragraph. This should be used with _forwardUntilEndToken
+ * @param HTMLPurifier_Token $current
+ * @return bool
+ */
+ private function _checkNeedsP($current)
+ {
+ if ($current instanceof HTMLPurifier_Token_Start) {
+ if (!$this->_isInline($current)) {
+ //
+ * References:
+ *
+ * From the original documentation:
+ *
+ * This routine calculates the LOG(GAMMA) function for a positive real argument X.
+ * Computation is based on an algorithm outlined in references 1 and 2.
+ * The program uses rational functions that theoretically approximate LOG(GAMMA)
+ * to at least 18 significant decimal digits. The approximation for X > 12 is from
+ * reference 3, while approximations for X < 12.0 are similar to those in reference
+ * 1, but are unpublished. The accuracy achieved depends on the arithmetic system,
+ * the compiler, the intrinsic functions, and proper selection of the
+ * machine-dependent constants.
+ *
+ * Error returns: s with a .
+
+ $ws_accum =& $initial_ws;
+
+ foreach ($children as $node) {
+ if ($node instanceof HTMLPurifier_Node_Comment) {
+ $ws_accum[] = $node;
+ continue;
+ }
+ switch ($node->name) {
+ case 'tbody':
+ $tbody_mode = true;
+ // fall through
+ case 'tr':
+ $content[] = $node;
+ $ws_accum =& $content;
+ break;
+ case 'caption':
+ // there can only be one caption!
+ if ($caption !== false) break;
+ $caption = $node;
+ $ws_accum =& $after_caption_ws;
+ break;
+ case 'thead':
+ $tbody_mode = true;
+ // XXX This breaks rendering properties with
+ // Firefox, which never floats a to
+ // the top. Ever. (Our scheme will float the
+ // first to the top.) So maybe
+ // s that are not first should be
+ // turned into ? Very tricky, indeed.
+ if ($thead === false) {
+ $thead = $node;
+ $ws_accum =& $after_thead_ws;
+ } else {
+ // Oops, there's a second one! What
+ // should we do? Current behavior is to
+ // transmutate the first and last entries into
+ // tbody tags, and then put into content.
+ // Maybe a better idea is to *attach
+ // it* to the existing thead or tfoot?
+ // We don't do this, because Firefox
+ // doesn't float an extra tfoot to the
+ // bottom like it does for the first one.
+ $node->name = 'tbody';
+ $content[] = $node;
+ $ws_accum =& $content;
+ }
+ break;
+ case 'tfoot':
+ // see above for some aveats
+ $tbody_mode = true;
+ if ($tfoot === false) {
+ $tfoot = $node;
+ $ws_accum =& $after_tfoot_ws;
+ } else {
+ $node->name = 'tbody';
+ $content[] = $node;
+ $ws_accum =& $content;
+ }
+ break;
+ case 'colgroup':
+ case 'col':
+ $cols[] = $node;
+ $ws_accum =& $cols;
+ break;
+ case '#PCDATA':
+ // How is whitespace handled? We treat is as sticky to
+ // the *end* of the previous element. So all of the
+ // nonsense we have worked on is to keep things
+ // together.
+ if (!empty($node->is_whitespace)) {
+ $ws_accum[] = $node;
+ }
+ break;
+ }
+ }
+
+ if (empty($content) && $thead === false && $tfoot === false) {
+ return false;
+ }
+
+ $ret = $initial_ws;
+ if ($caption !== false) {
+ $ret[] = $caption;
+ $ret = array_merge($ret, $after_caption_ws);
+ }
+ if ($cols !== false) {
+ $ret = array_merge($ret, $cols);
+ }
+ if ($thead !== false) {
+ $ret[] = $thead;
+ $ret = array_merge($ret, $after_thead_ws);
+ }
+ if ($tfoot !== false) {
+ $ret[] = $tfoot;
+ $ret = array_merge($ret, $after_tfoot_ws);
+ }
+
+ if ($tbody_mode) {
+ // we have to shuffle tr into tbody
+ $current_tr_tbody = null;
+
+ foreach($content as $node) {
+ switch ($node->name) {
+ case 'tbody':
+ $current_tr_tbody = null;
+ $ret[] = $node;
+ break;
+ case 'tr':
+ if ($current_tr_tbody === null) {
+ $current_tr_tbody = new HTMLPurifier_Node_Element('tbody');
+ $ret[] = $current_tr_tbody;
+ }
+ $current_tr_tbody->children[] = $node;
+ break;
+ case '#PCDATA':
+ //assert($node->is_whitespace);
+ if ($current_tr_tbody === null) {
+ $ret[] = $node;
+ } else {
+ $current_tr_tbody->children[] = $node;
+ }
+ break;
+ }
+ }
+ } else {
+ $ret = array_merge($ret, $content);
+ }
+
+ return $ret;
+
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php
new file mode 100644
index 000000000..f7511ca41
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php
@@ -0,0 +1,920 @@
+defaultPlist;
+ $this->plist = new HTMLPurifier_PropertyList($parent);
+ $this->def = $definition; // keep a copy around for checking
+ $this->parser = new HTMLPurifier_VarParser_Flexible();
+ }
+
+ /**
+ * Convenience constructor that creates a config object based on a mixed var
+ * @param mixed $config Variable that defines the state of the config
+ * object. Can be: a HTMLPurifier_Config() object,
+ * an array of directives based on loadArray(),
+ * or a string filename of an ini file.
+ * @param HTMLPurifier_ConfigSchema $schema Schema object
+ * @return HTMLPurifier_Config Configured object
+ */
+ public static function create($config, $schema = null)
+ {
+ if ($config instanceof HTMLPurifier_Config) {
+ // pass-through
+ return $config;
+ }
+ if (!$schema) {
+ $ret = HTMLPurifier_Config::createDefault();
+ } else {
+ $ret = new HTMLPurifier_Config($schema);
+ }
+ if (is_string($config)) {
+ $ret->loadIni($config);
+ } elseif (is_array($config)) $ret->loadArray($config);
+ return $ret;
+ }
+
+ /**
+ * Creates a new config object that inherits from a previous one.
+ * @param HTMLPurifier_Config $config Configuration object to inherit from.
+ * @return HTMLPurifier_Config object with $config as its parent.
+ */
+ public static function inherit(HTMLPurifier_Config $config)
+ {
+ return new HTMLPurifier_Config($config->def, $config->plist);
+ }
+
+ /**
+ * Convenience constructor that creates a default configuration object.
+ * @return HTMLPurifier_Config default object.
+ */
+ public static function createDefault()
+ {
+ $definition = HTMLPurifier_ConfigSchema::instance();
+ $config = new HTMLPurifier_Config($definition);
+ return $config;
+ }
+
+ /**
+ * Retrieves a value from the configuration.
+ *
+ * @param string $key String key
+ * @param mixed $a
+ *
+ * @return mixed
+ */
+ public function get($key, $a = null)
+ {
+ if ($a !== null) {
+ $this->triggerError(
+ "Using deprecated API: use \$config->get('$key.$a') instead",
+ E_USER_WARNING
+ );
+ $key = "$key.$a";
+ }
+ if (!$this->finalized) {
+ $this->autoFinalize();
+ }
+ if (!isset($this->def->info[$key])) {
+ // can't add % due to SimpleTest bug
+ $this->triggerError(
+ 'Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
+ E_USER_WARNING
+ );
+ return;
+ }
+ if (isset($this->def->info[$key]->isAlias)) {
+ $d = $this->def->info[$key];
+ $this->triggerError(
+ 'Cannot get value from aliased directive, use real name ' . $d->key,
+ E_USER_ERROR
+ );
+ return;
+ }
+ if ($this->lock) {
+ list($ns) = explode('.', $key);
+ if ($ns !== $this->lock) {
+ $this->triggerError(
+ 'Cannot get value of namespace ' . $ns . ' when lock for ' .
+ $this->lock .
+ ' is active, this probably indicates a Definition setup method ' .
+ 'is accessing directives that are not within its namespace',
+ E_USER_ERROR
+ );
+ return;
+ }
+ }
+ return $this->plist->get($key);
+ }
+
+ /**
+ * Retrieves an array of directives to values from a given namespace
+ *
+ * @param string $namespace String namespace
+ *
+ * @return array
+ */
+ public function getBatch($namespace)
+ {
+ if (!$this->finalized) {
+ $this->autoFinalize();
+ }
+ $full = $this->getAll();
+ if (!isset($full[$namespace])) {
+ $this->triggerError(
+ 'Cannot retrieve undefined namespace ' .
+ htmlspecialchars($namespace),
+ E_USER_WARNING
+ );
+ return;
+ }
+ return $full[$namespace];
+ }
+
+ /**
+ * Returns a SHA-1 signature of a segment of the configuration object
+ * that uniquely identifies that particular configuration
+ *
+ * @param string $namespace Namespace to get serial for
+ *
+ * @return string
+ * @note Revision is handled specially and is removed from the batch
+ * before processing!
+ */
+ public function getBatchSerial($namespace)
+ {
+ if (empty($this->serials[$namespace])) {
+ $batch = $this->getBatch($namespace);
+ unset($batch['DefinitionRev']);
+ $this->serials[$namespace] = sha1(serialize($batch));
+ }
+ return $this->serials[$namespace];
+ }
+
+ /**
+ * Returns a SHA-1 signature for the entire configuration object
+ * that uniquely identifies that particular configuration
+ *
+ * @return string
+ */
+ public function getSerial()
+ {
+ if (empty($this->serial)) {
+ $this->serial = sha1(serialize($this->getAll()));
+ }
+ return $this->serial;
+ }
+
+ /**
+ * Retrieves all directives, organized by namespace
+ *
+ * @warning This is a pretty inefficient function, avoid if you can
+ */
+ public function getAll()
+ {
+ if (!$this->finalized) {
+ $this->autoFinalize();
+ }
+ $ret = array();
+ foreach ($this->plist->squash() as $name => $value) {
+ list($ns, $key) = explode('.', $name, 2);
+ $ret[$ns][$key] = $value;
+ }
+ return $ret;
+ }
+
+ /**
+ * Sets a value to configuration.
+ *
+ * @param string $key key
+ * @param mixed $value value
+ * @param mixed $a
+ */
+ public function set($key, $value, $a = null)
+ {
+ if (strpos($key, '.') === false) {
+ $namespace = $key;
+ $directive = $value;
+ $value = $a;
+ $key = "$key.$directive";
+ $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
+ } else {
+ list($namespace) = explode('.', $key);
+ }
+ if ($this->isFinalized('Cannot set directive after finalization')) {
+ return;
+ }
+ if (!isset($this->def->info[$key])) {
+ $this->triggerError(
+ 'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
+ E_USER_WARNING
+ );
+ return;
+ }
+ $def = $this->def->info[$key];
+
+ if (isset($def->isAlias)) {
+ if ($this->aliasMode) {
+ $this->triggerError(
+ 'Double-aliases not allowed, please fix '.
+ 'ConfigSchema bug with' . $key,
+ E_USER_ERROR
+ );
+ return;
+ }
+ $this->aliasMode = true;
+ $this->set($def->key, $value);
+ $this->aliasMode = false;
+ $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE);
+ return;
+ }
+
+ // Raw type might be negative when using the fully optimized form
+ // of stdClass, which indicates allow_null == true
+ $rtype = is_int($def) ? $def : $def->type;
+ if ($rtype < 0) {
+ $type = -$rtype;
+ $allow_null = true;
+ } else {
+ $type = $rtype;
+ $allow_null = isset($def->allow_null);
+ }
+
+ try {
+ $value = $this->parser->parse($value, $type, $allow_null);
+ } catch (HTMLPurifier_VarParserException $e) {
+ $this->triggerError(
+ 'Value for ' . $key . ' is of invalid type, should be ' .
+ HTMLPurifier_VarParser::getTypeName($type),
+ E_USER_WARNING
+ );
+ return;
+ }
+ if (is_string($value) && is_object($def)) {
+ // resolve value alias if defined
+ if (isset($def->aliases[$value])) {
+ $value = $def->aliases[$value];
+ }
+ // check to see if the value is allowed
+ if (isset($def->allowed) && !isset($def->allowed[$value])) {
+ $this->triggerError(
+ 'Value not supported, valid values are: ' .
+ $this->_listify($def->allowed),
+ E_USER_WARNING
+ );
+ return;
+ }
+ }
+ $this->plist->set($key, $value);
+
+ // reset definitions if the directives they depend on changed
+ // this is a very costly process, so it's discouraged
+ // with finalization
+ if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
+ $this->definitions[$namespace] = null;
+ }
+
+ $this->serials[$namespace] = false;
+ }
+
+ /**
+ * Convenience function for error reporting
+ *
+ * @param array $lookup
+ *
+ * @return string
+ */
+ private function _listify($lookup)
+ {
+ $list = array();
+ foreach ($lookup as $name => $b) {
+ $list[] = $name;
+ }
+ return implode(', ', $list);
+ }
+
+ /**
+ * Retrieves object reference to the HTML definition.
+ *
+ * @param bool $raw Return a copy that has not been setup yet. Must be
+ * called before it's been setup, otherwise won't work.
+ * @param bool $optimized If true, this method may return null, to
+ * indicate that a cached version of the modified
+ * definition object is available and no further edits
+ * are necessary. Consider using
+ * maybeGetRawHTMLDefinition, which is more explicitly
+ * named, instead.
+ *
+ * @return HTMLPurifier_HTMLDefinition|null
+ */
+ public function getHTMLDefinition($raw = false, $optimized = false)
+ {
+ return $this->getDefinition('HTML', $raw, $optimized);
+ }
+
+ /**
+ * Retrieves object reference to the CSS definition
+ *
+ * @param bool $raw Return a copy that has not been setup yet. Must be
+ * called before it's been setup, otherwise won't work.
+ * @param bool $optimized If true, this method may return null, to
+ * indicate that a cached version of the modified
+ * definition object is available and no further edits
+ * are necessary. Consider using
+ * maybeGetRawCSSDefinition, which is more explicitly
+ * named, instead.
+ *
+ * @return HTMLPurifier_CSSDefinition|null
+ */
+ public function getCSSDefinition($raw = false, $optimized = false)
+ {
+ return $this->getDefinition('CSS', $raw, $optimized);
+ }
+
+ /**
+ * Retrieves object reference to the URI definition
+ *
+ * @param bool $raw Return a copy that has not been setup yet. Must be
+ * called before it's been setup, otherwise won't work.
+ * @param bool $optimized If true, this method may return null, to
+ * indicate that a cached version of the modified
+ * definition object is available and no further edits
+ * are necessary. Consider using
+ * maybeGetRawURIDefinition, which is more explicitly
+ * named, instead.
+ *
+ * @return HTMLPurifier_URIDefinition|null
+ */
+ public function getURIDefinition($raw = false, $optimized = false)
+ {
+ return $this->getDefinition('URI', $raw, $optimized);
+ }
+
+ /**
+ * Retrieves a definition
+ *
+ * @param string $type Type of definition: HTML, CSS, etc
+ * @param bool $raw Whether or not definition should be returned raw
+ * @param bool $optimized Only has an effect when $raw is true. Whether
+ * or not to return null if the result is already present in
+ * the cache. This is off by default for backwards
+ * compatibility reasons, but you need to do things this
+ * way in order to ensure that caching is done properly.
+ * Check out enduser-customize.html for more details.
+ * We probably won't ever change this default, as much as the
+ * maybe semantics is the "right thing to do."
+ *
+ * @throws HTMLPurifier_Exception
+ * @return HTMLPurifier_Definition|null
+ */
+ public function getDefinition($type, $raw = false, $optimized = false)
+ {
+ if ($optimized && !$raw) {
+ throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false");
+ }
+ if (!$this->finalized) {
+ $this->autoFinalize();
+ }
+ // temporarily suspend locks, so we can handle recursive definition calls
+ $lock = $this->lock;
+ $this->lock = null;
+ $factory = HTMLPurifier_DefinitionCacheFactory::instance();
+ $cache = $factory->create($type, $this);
+ $this->lock = $lock;
+ if (!$raw) {
+ // full definition
+ // ---------------
+ // check if definition is in memory
+ if (!empty($this->definitions[$type])) {
+ $def = $this->definitions[$type];
+ // check if the definition is setup
+ if ($def->setup) {
+ return $def;
+ } else {
+ $def->setup($this);
+ if ($def->optimized) {
+ $cache->add($def, $this);
+ }
+ return $def;
+ }
+ }
+ // check if definition is in cache
+ $def = $cache->get($this);
+ if ($def) {
+ // definition in cache, save to memory and return it
+ $this->definitions[$type] = $def;
+ return $def;
+ }
+ // initialize it
+ $def = $this->initDefinition($type);
+ // set it up
+ $this->lock = $type;
+ $def->setup($this);
+ $this->lock = null;
+ // save in cache
+ $cache->add($def, $this);
+ // return it
+ return $def;
+ } else {
+ // raw definition
+ // --------------
+ // check preconditions
+ $def = null;
+ if ($optimized) {
+ if (is_null($this->get($type . '.DefinitionID'))) {
+ // fatally error out if definition ID not set
+ throw new HTMLPurifier_Exception(
+ "Cannot retrieve raw version without specifying %$type.DefinitionID"
+ );
+ }
+ }
+ if (!empty($this->definitions[$type])) {
+ $def = $this->definitions[$type];
+ if ($def->setup && !$optimized) {
+ $extra = $this->chatty ?
+ " (try moving this code block earlier in your initialization)" :
+ "";
+ throw new HTMLPurifier_Exception(
+ "Cannot retrieve raw definition after it has already been setup" .
+ $extra
+ );
+ }
+ if ($def->optimized === null) {
+ $extra = $this->chatty ? " (try flushing your cache)" : "";
+ throw new HTMLPurifier_Exception(
+ "Optimization status of definition is unknown" . $extra
+ );
+ }
+ if ($def->optimized !== $optimized) {
+ $msg = $optimized ? "optimized" : "unoptimized";
+ $extra = $this->chatty ?
+ " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)"
+ : "";
+ throw new HTMLPurifier_Exception(
+ "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra
+ );
+ }
+ }
+ // check if definition was in memory
+ if ($def) {
+ if ($def->setup) {
+ // invariant: $optimized === true (checked above)
+ return null;
+ } else {
+ return $def;
+ }
+ }
+ // if optimized, check if definition was in cache
+ // (because we do the memory check first, this formulation
+ // is prone to cache slamming, but I think
+ // guaranteeing that either /all/ of the raw
+ // setup code or /none/ of it is run is more important.)
+ if ($optimized) {
+ // This code path only gets run once; once we put
+ // something in $definitions (which is guaranteed by the
+ // trailing code), we always short-circuit above.
+ $def = $cache->get($this);
+ if ($def) {
+ // save the full definition for later, but don't
+ // return it yet
+ $this->definitions[$type] = $def;
+ return null;
+ }
+ }
+ // check invariants for creation
+ if (!$optimized) {
+ if (!is_null($this->get($type . '.DefinitionID'))) {
+ if ($this->chatty) {
+ $this->triggerError(
+ 'Due to a documentation error in previous version of HTML Purifier, your ' .
+ 'definitions are not being cached. If this is OK, you can remove the ' .
+ '%$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, ' .
+ 'modify your code to use maybeGetRawDefinition, and test if the returned ' .
+ 'value is null before making any edits (if it is null, that means that a ' .
+ 'cached version is available, and no raw operations are necessary). See ' .
+ '' .
+ 'Customize for more details',
+ E_USER_WARNING
+ );
+ } else {
+ $this->triggerError(
+ "Useless DefinitionID declaration",
+ E_USER_WARNING
+ );
+ }
+ }
+ }
+ // initialize it
+ $def = $this->initDefinition($type);
+ $def->optimized = $optimized;
+ return $def;
+ }
+ throw new HTMLPurifier_Exception("The impossible happened!");
+ }
+
+ /**
+ * Initialise definition
+ *
+ * @param string $type What type of definition to create
+ *
+ * @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition
+ * @throws HTMLPurifier_Exception
+ */
+ private function initDefinition($type)
+ {
+ // quick checks failed, let's create the object
+ if ($type == 'HTML') {
+ $def = new HTMLPurifier_HTMLDefinition();
+ } elseif ($type == 'CSS') {
+ $def = new HTMLPurifier_CSSDefinition();
+ } elseif ($type == 'URI') {
+ $def = new HTMLPurifier_URIDefinition();
+ } else {
+ throw new HTMLPurifier_Exception(
+ "Definition of $type type not supported"
+ );
+ }
+ $this->definitions[$type] = $def;
+ return $def;
+ }
+
+ public function maybeGetRawDefinition($name)
+ {
+ return $this->getDefinition($name, true, true);
+ }
+
+ /**
+ * @return HTMLPurifier_HTMLDefinition|null
+ */
+ public function maybeGetRawHTMLDefinition()
+ {
+ return $this->getDefinition('HTML', true, true);
+ }
+
+ /**
+ * @return HTMLPurifier_CSSDefinition|null
+ */
+ public function maybeGetRawCSSDefinition()
+ {
+ return $this->getDefinition('CSS', true, true);
+ }
+
+ /**
+ * @return HTMLPurifier_URIDefinition|null
+ */
+ public function maybeGetRawURIDefinition()
+ {
+ return $this->getDefinition('URI', true, true);
+ }
+
+ /**
+ * Loads configuration values from an array with the following structure:
+ * Namespace.Directive => Value
+ *
+ * @param array $config_array Configuration associative array
+ */
+ public function loadArray($config_array)
+ {
+ if ($this->isFinalized('Cannot load directives after finalization')) {
+ return;
+ }
+ foreach ($config_array as $key => $value) {
+ $key = str_replace('_', '.', $key);
+ if (strpos($key, '.') !== false) {
+ $this->set($key, $value);
+ } else {
+ $namespace = $key;
+ $namespace_values = $value;
+ foreach ($namespace_values as $directive => $value2) {
+ $this->set($namespace .'.'. $directive, $value2);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a list of array(namespace, directive) for all directives
+ * that are allowed in a web-form context as per an allowed
+ * namespaces/directives list.
+ *
+ * @param array $allowed List of allowed namespaces/directives
+ * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
+ *
+ * @return array
+ */
+ public static function getAllowedDirectivesForForm($allowed, $schema = null)
+ {
+ if (!$schema) {
+ $schema = HTMLPurifier_ConfigSchema::instance();
+ }
+ if ($allowed !== true) {
+ if (is_string($allowed)) {
+ $allowed = array($allowed);
+ }
+ $allowed_ns = array();
+ $allowed_directives = array();
+ $blacklisted_directives = array();
+ foreach ($allowed as $ns_or_directive) {
+ if (strpos($ns_or_directive, '.') !== false) {
+ // directive
+ if ($ns_or_directive[0] == '-') {
+ $blacklisted_directives[substr($ns_or_directive, 1)] = true;
+ } else {
+ $allowed_directives[$ns_or_directive] = true;
+ }
+ } else {
+ // namespace
+ $allowed_ns[$ns_or_directive] = true;
+ }
+ }
+ }
+ $ret = array();
+ foreach ($schema->info as $key => $def) {
+ list($ns, $directive) = explode('.', $key, 2);
+ if ($allowed !== true) {
+ if (isset($blacklisted_directives["$ns.$directive"])) {
+ continue;
+ }
+ if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) {
+ continue;
+ }
+ }
+ if (isset($def->isAlias)) {
+ continue;
+ }
+ if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') {
+ continue;
+ }
+ $ret[] = array($ns, $directive);
+ }
+ return $ret;
+ }
+
+ /**
+ * Loads configuration values from $_GET/$_POST that were posted
+ * via ConfigForm
+ *
+ * @param array $array $_GET or $_POST array to import
+ * @param string|bool $index Index/name that the config variables are in
+ * @param array|bool $allowed List of allowed namespaces/directives
+ * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
+ * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
+ *
+ * @return mixed
+ */
+ public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
+ {
+ $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
+ $config = HTMLPurifier_Config::create($ret, $schema);
+ return $config;
+ }
+
+ /**
+ * Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
+ *
+ * @param array $array $_GET or $_POST array to import
+ * @param string|bool $index Index/name that the config variables are in
+ * @param array|bool $allowed List of allowed namespaces/directives
+ * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
+ */
+ public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true)
+ {
+ $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
+ $this->loadArray($ret);
+ }
+
+ /**
+ * Prepares an array from a form into something usable for the more
+ * strict parts of HTMLPurifier_Config
+ *
+ * @param array $array $_GET or $_POST array to import
+ * @param string|bool $index Index/name that the config variables are in
+ * @param array|bool $allowed List of allowed namespaces/directives
+ * @param bool $mq_fix Boolean whether or not to enable magic quotes fix
+ * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy
+ *
+ * @return array
+ */
+ public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null)
+ {
+ if ($index !== false) {
+ $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
+ }
+ $mq = $mq_fix && version_compare(PHP_VERSION, '7.4.0', '<') && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
+
+ $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
+ $ret = array();
+ foreach ($allowed as $key) {
+ list($ns, $directive) = $key;
+ $skey = "$ns.$directive";
+ if (!empty($array["Null_$skey"])) {
+ $ret[$ns][$directive] = null;
+ continue;
+ }
+ if (!isset($array[$skey])) {
+ continue;
+ }
+ $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
+ $ret[$ns][$directive] = $value;
+ }
+ return $ret;
+ }
+
+ /**
+ * Loads configuration values from an ini file
+ *
+ * @param string $filename Name of ini file
+ */
+ public function loadIni($filename)
+ {
+ if ($this->isFinalized('Cannot load directives after finalization')) {
+ return;
+ }
+ $array = parse_ini_file($filename, true);
+ $this->loadArray($array);
+ }
+
+ /**
+ * Checks whether or not the configuration object is finalized.
+ *
+ * @param string|bool $error String error message, or false for no error
+ *
+ * @return bool
+ */
+ public function isFinalized($error = false)
+ {
+ if ($this->finalized && $error) {
+ $this->triggerError($error, E_USER_ERROR);
+ }
+ return $this->finalized;
+ }
+
+ /**
+ * Finalizes configuration only if auto finalize is on and not
+ * already finalized
+ */
+ public function autoFinalize()
+ {
+ if ($this->autoFinalize) {
+ $this->finalize();
+ } else {
+ $this->plist->squash(true);
+ }
+ }
+
+ /**
+ * Finalizes a configuration object, prohibiting further change
+ */
+ public function finalize()
+ {
+ $this->finalized = true;
+ $this->parser = null;
+ }
+
+ /**
+ * Produces a nicely formatted error message by supplying the
+ * stack frame information OUTSIDE of HTMLPurifier_Config.
+ *
+ * @param string $msg An error message
+ * @param int $no An error number
+ */
+ protected function triggerError($msg, $no)
+ {
+ // determine previous stack frame
+ $extra = '';
+ if ($this->chatty) {
+ $trace = debug_backtrace();
+ // zip(tail(trace), trace) -- but PHP is not Haskell har har
+ for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
+ // XXX this is not correct on some versions of HTML Purifier
+ if (isset($trace[$i + 1]['class']) && $trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
+ continue;
+ }
+ $frame = $trace[$i];
+ $extra = " invoked on line {$frame['line']} in file {$frame['file']}";
+ break;
+ }
+ }
+ trigger_error($msg . $extra, $no);
+ }
+
+ /**
+ * Returns a serialized form of the configuration object that can
+ * be reconstituted.
+ *
+ * @return string
+ */
+ public function serialize()
+ {
+ $this->getDefinition('HTML');
+ $this->getDefinition('CSS');
+ $this->getDefinition('URI');
+ return serialize($this);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php
new file mode 100644
index 000000000..c3fe8cd4a
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php
@@ -0,0 +1,176 @@
+ array(
+ * 'Directive' => new stdClass(),
+ * )
+ * )
+ *
+ * The stdClass may have the following properties:
+ *
+ * - If isAlias isn't set:
+ * - type: Integer type of directive, see HTMLPurifier_VarParser for definitions
+ * - allow_null: If set, this directive allows null values
+ * - aliases: If set, an associative array of value aliases to real values
+ * - allowed: If set, a lookup array of allowed (string) values
+ * - If isAlias is set:
+ * - namespace: Namespace this directive aliases to
+ * - name: Directive name this directive aliases to
+ *
+ * In certain degenerate cases, stdClass will actually be an integer. In
+ * that case, the value is equivalent to an stdClass with the type
+ * property set to the integer. If the integer is negative, type is
+ * equal to the absolute value of integer, and allow_null is true.
+ *
+ * This class is friendly with HTMLPurifier_Config. If you need introspection
+ * about the schema, you're better of using the ConfigSchema_Interchange,
+ * which uses more memory but has much richer information.
+ * @type array
+ */
+ public $info = array();
+
+ /**
+ * Application-wide singleton
+ * @type HTMLPurifier_ConfigSchema
+ */
+ protected static $singleton;
+
+ public function __construct()
+ {
+ $this->defaultPlist = new HTMLPurifier_PropertyList();
+ }
+
+ /**
+ * Unserializes the default ConfigSchema.
+ * @return HTMLPurifier_ConfigSchema
+ */
+ public static function makeFromSerial()
+ {
+ $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser');
+ $r = unserialize($contents);
+ if (!$r) {
+ $hash = sha1($contents);
+ trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR);
+ }
+ return $r;
+ }
+
+ /**
+ * Retrieves an instance of the application-wide configuration definition.
+ * @param HTMLPurifier_ConfigSchema $prototype
+ * @return HTMLPurifier_ConfigSchema
+ */
+ public static function instance($prototype = null)
+ {
+ if ($prototype !== null) {
+ HTMLPurifier_ConfigSchema::$singleton = $prototype;
+ } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) {
+ HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial();
+ }
+ return HTMLPurifier_ConfigSchema::$singleton;
+ }
+
+ /**
+ * Defines a directive for configuration
+ * @warning Will fail of directive's namespace is defined.
+ * @warning This method's signature is slightly different from the legacy
+ * define() static method! Beware!
+ * @param string $key Name of directive
+ * @param mixed $default Default value of directive
+ * @param string $type Allowed type of the directive. See
+ * HTMLPurifier_VarParser::$types for allowed values
+ * @param bool $allow_null Whether or not to allow null values
+ */
+ public function add($key, $default, $type, $allow_null)
+ {
+ $obj = new stdClass();
+ $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
+ if ($allow_null) {
+ $obj->allow_null = true;
+ }
+ $this->info[$key] = $obj;
+ $this->defaults[$key] = $default;
+ $this->defaultPlist->set($key, $default);
+ }
+
+ /**
+ * Defines a directive value alias.
+ *
+ * Directive value aliases are convenient for developers because it lets
+ * them set a directive to several values and get the same result.
+ * @param string $key Name of Directive
+ * @param array $aliases Hash of aliased values to the real alias
+ */
+ public function addValueAliases($key, $aliases)
+ {
+ if (!isset($this->info[$key]->aliases)) {
+ $this->info[$key]->aliases = array();
+ }
+ foreach ($aliases as $alias => $real) {
+ $this->info[$key]->aliases[$alias] = $real;
+ }
+ }
+
+ /**
+ * Defines a set of allowed values for a directive.
+ * @warning This is slightly different from the corresponding static
+ * method definition.
+ * @param string $key Name of directive
+ * @param array $allowed Lookup array of allowed values
+ */
+ public function addAllowedValues($key, $allowed)
+ {
+ $this->info[$key]->allowed = $allowed;
+ }
+
+ /**
+ * Defines a directive alias for backwards compatibility
+ * @param string $key Directive that will be aliased
+ * @param string $new_key Directive that the alias will be to
+ */
+ public function addAlias($key, $new_key)
+ {
+ $obj = new stdClass;
+ $obj->key = $new_key;
+ $obj->isAlias = true;
+ $this->info[$key] = $obj;
+ }
+
+ /**
+ * Replaces any stdClass that only has the type property with type integer.
+ */
+ public function postProcess()
+ {
+ foreach ($this->info as $key => $v) {
+ if (count((array) $v) == 1) {
+ $this->info[$key] = $v->type;
+ } elseif (count((array) $v) == 2 && isset($v->allow_null)) {
+ $this->info[$key] = -$v->type;
+ }
+ }
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php
new file mode 100644
index 000000000..d5906cd46
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php
@@ -0,0 +1,48 @@
+directives as $d) {
+ $schema->add(
+ $d->id->key,
+ $d->default,
+ $d->type,
+ $d->typeAllowsNull
+ );
+ if ($d->allowed !== null) {
+ $schema->addAllowedValues(
+ $d->id->key,
+ $d->allowed
+ );
+ }
+ foreach ($d->aliases as $alias) {
+ $schema->addAlias(
+ $alias->key,
+ $d->id->key
+ );
+ }
+ if ($d->valueAliases !== null) {
+ $schema->addValueAliases(
+ $d->id->key,
+ $d->valueAliases
+ );
+ }
+ }
+ $schema->postProcess();
+ return $schema;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php
new file mode 100644
index 000000000..5fa56f7dd
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php
@@ -0,0 +1,144 @@
+startElement('div');
+
+ $purifier = HTMLPurifier::getInstance();
+ $html = $purifier->purify($html);
+ $this->writeAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
+ $this->writeRaw($html);
+
+ $this->endElement(); // div
+ }
+
+ /**
+ * @param mixed $var
+ * @return string
+ */
+ protected function export($var)
+ {
+ if ($var === array()) {
+ return 'array()';
+ }
+ return var_export($var, true);
+ }
+
+ /**
+ * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+ */
+ public function build($interchange)
+ {
+ // global access, only use as last resort
+ $this->interchange = $interchange;
+
+ $this->setIndent(true);
+ $this->startDocument('1.0', 'UTF-8');
+ $this->startElement('configdoc');
+ $this->writeElement('title', $interchange->name);
+
+ foreach ($interchange->directives as $directive) {
+ $this->buildDirective($directive);
+ }
+
+ if ($this->namespace) {
+ $this->endElement();
+ } // namespace
+
+ $this->endElement(); // configdoc
+ $this->flush();
+ }
+
+ /**
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive
+ */
+ public function buildDirective($directive)
+ {
+ // Kludge, although I suppose having a notion of a "root namespace"
+ // certainly makes things look nicer when documentation is built.
+ // Depends on things being sorted.
+ if (!$this->namespace || $this->namespace !== $directive->id->getRootNamespace()) {
+ if ($this->namespace) {
+ $this->endElement();
+ } // namespace
+ $this->namespace = $directive->id->getRootNamespace();
+ $this->startElement('namespace');
+ $this->writeAttribute('id', $this->namespace);
+ $this->writeElement('name', $this->namespace);
+ }
+
+ $this->startElement('directive');
+ $this->writeAttribute('id', $directive->id->toString());
+
+ $this->writeElement('name', $directive->id->getDirective());
+
+ $this->startElement('aliases');
+ foreach ($directive->aliases as $alias) {
+ $this->writeElement('alias', $alias->toString());
+ }
+ $this->endElement(); // aliases
+
+ $this->startElement('constraints');
+ if ($directive->version) {
+ $this->writeElement('version', $directive->version);
+ }
+ $this->startElement('type');
+ if ($directive->typeAllowsNull) {
+ $this->writeAttribute('allow-null', 'yes');
+ }
+ $this->text($directive->type);
+ $this->endElement(); // type
+ if ($directive->allowed) {
+ $this->startElement('allowed');
+ foreach ($directive->allowed as $value => $x) {
+ $this->writeElement('value', $value);
+ }
+ $this->endElement(); // allowed
+ }
+ $this->writeElement('default', $this->export($directive->default));
+ $this->writeAttribute('xml:space', 'preserve');
+ if ($directive->external) {
+ $this->startElement('external');
+ foreach ($directive->external as $project) {
+ $this->writeElement('project', $project);
+ }
+ $this->endElement();
+ }
+ $this->endElement(); // constraints
+
+ if ($directive->deprecatedVersion) {
+ $this->startElement('deprecated');
+ $this->writeElement('version', $directive->deprecatedVersion);
+ $this->writeElement('use', $directive->deprecatedUse->toString());
+ $this->endElement(); // deprecated
+ }
+
+ $this->startElement('description');
+ $this->writeHTMLDiv($directive->description);
+ $this->endElement(); // description
+
+ $this->endElement(); // directive
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php
new file mode 100644
index 000000000..2671516c5
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php
@@ -0,0 +1,11 @@
+ array(directive info)
+ * @type HTMLPurifier_ConfigSchema_Interchange_Directive[]
+ */
+ public $directives = array();
+
+ /**
+ * Adds a directive array to $directives
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive
+ * @throws HTMLPurifier_ConfigSchema_Exception
+ */
+ public function addDirective($directive)
+ {
+ if (isset($this->directives[$i = $directive->id->toString()])) {
+ throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'");
+ }
+ $this->directives[$i] = $directive;
+ }
+
+ /**
+ * Convenience function to perform standard validation. Throws exception
+ * on failed validation.
+ */
+ public function validate()
+ {
+ $validator = new HTMLPurifier_ConfigSchema_Validator();
+ return $validator->validate($this);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php
new file mode 100644
index 000000000..127a39a67
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php
@@ -0,0 +1,89 @@
+ true).
+ * Null if all values are allowed.
+ * @type array
+ */
+ public $allowed;
+
+ /**
+ * List of aliases for the directive.
+ * e.g. array(new HTMLPurifier_ConfigSchema_Interchange_Id('Ns', 'Dir'))).
+ * @type HTMLPurifier_ConfigSchema_Interchange_Id[]
+ */
+ public $aliases = array();
+
+ /**
+ * Hash of value aliases, e.g. array('alt' => 'real'). Null if value
+ * aliasing is disabled (necessary for non-scalar types).
+ * @type array
+ */
+ public $valueAliases;
+
+ /**
+ * Version of HTML Purifier the directive was introduced, e.g. '1.3.1'.
+ * Null if the directive has always existed.
+ * @type string
+ */
+ public $version;
+
+ /**
+ * ID of directive that supercedes this old directive.
+ * Null if not deprecated.
+ * @type HTMLPurifier_ConfigSchema_Interchange_Id
+ */
+ public $deprecatedUse;
+
+ /**
+ * Version of HTML Purifier this directive was deprecated. Null if not
+ * deprecated.
+ * @type string
+ */
+ public $deprecatedVersion;
+
+ /**
+ * List of external projects this directive depends on, e.g. array('CSSTidy').
+ * @type array
+ */
+ public $external = array();
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php
new file mode 100644
index 000000000..126f09d95
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php
@@ -0,0 +1,58 @@
+key = $key;
+ }
+
+ /**
+ * @return string
+ * @warning This is NOT magic, to ensure that people don't abuse SPL and
+ * cause problems for PHP 5.0 support.
+ */
+ public function toString()
+ {
+ return $this->key;
+ }
+
+ /**
+ * @return string
+ */
+ public function getRootNamespace()
+ {
+ return substr($this->key, 0, strpos($this->key, "."));
+ }
+
+ /**
+ * @return string
+ */
+ public function getDirective()
+ {
+ return substr($this->key, strpos($this->key, ".") + 1);
+ }
+
+ /**
+ * @param string $id
+ * @return HTMLPurifier_ConfigSchema_Interchange_Id
+ */
+ public static function make($id)
+ {
+ return new HTMLPurifier_ConfigSchema_Interchange_Id($id);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php
new file mode 100644
index 000000000..655e6dd1b
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php
@@ -0,0 +1,226 @@
+varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native();
+ }
+
+ /**
+ * @param string $dir
+ * @return HTMLPurifier_ConfigSchema_Interchange
+ */
+ public static function buildFromDirectory($dir = null)
+ {
+ $builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder();
+ $interchange = new HTMLPurifier_ConfigSchema_Interchange();
+ return $builder->buildDir($interchange, $dir);
+ }
+
+ /**
+ * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+ * @param string $dir
+ * @return HTMLPurifier_ConfigSchema_Interchange
+ */
+ public function buildDir($interchange, $dir = null)
+ {
+ if (!$dir) {
+ $dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema';
+ }
+ if (file_exists($dir . '/info.ini')) {
+ $info = parse_ini_file($dir . '/info.ini');
+ $interchange->name = $info['name'];
+ }
+
+ $files = array();
+ $dh = opendir($dir);
+ while (false !== ($file = readdir($dh))) {
+ if (!$file || $file[0] == '.' || strrchr($file, '.') !== '.txt') {
+ continue;
+ }
+ $files[] = $file;
+ }
+ closedir($dh);
+
+ sort($files);
+ foreach ($files as $file) {
+ $this->buildFile($interchange, $dir . '/' . $file);
+ }
+ return $interchange;
+ }
+
+ /**
+ * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+ * @param string $file
+ */
+ public function buildFile($interchange, $file)
+ {
+ $parser = new HTMLPurifier_StringHashParser();
+ $this->build(
+ $interchange,
+ new HTMLPurifier_StringHash($parser->parseFile($file))
+ );
+ }
+
+ /**
+ * Builds an interchange object based on a hash.
+ * @param HTMLPurifier_ConfigSchema_Interchange $interchange HTMLPurifier_ConfigSchema_Interchange object to build
+ * @param HTMLPurifier_StringHash $hash source data
+ * @throws HTMLPurifier_ConfigSchema_Exception
+ */
+ public function build($interchange, $hash)
+ {
+ if (!$hash instanceof HTMLPurifier_StringHash) {
+ $hash = new HTMLPurifier_StringHash($hash);
+ }
+ if (!isset($hash['ID'])) {
+ throw new HTMLPurifier_ConfigSchema_Exception('Hash does not have any ID');
+ }
+ if (strpos($hash['ID'], '.') === false) {
+ if (count($hash) == 2 && isset($hash['DESCRIPTION'])) {
+ $hash->offsetGet('DESCRIPTION'); // prevent complaining
+ } else {
+ throw new HTMLPurifier_ConfigSchema_Exception('All directives must have a namespace');
+ }
+ } else {
+ $this->buildDirective($interchange, $hash);
+ }
+ $this->_findUnused($hash);
+ }
+
+ /**
+ * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+ * @param HTMLPurifier_StringHash $hash
+ * @throws HTMLPurifier_ConfigSchema_Exception
+ */
+ public function buildDirective($interchange, $hash)
+ {
+ $directive = new HTMLPurifier_ConfigSchema_Interchange_Directive();
+
+ // These are required elements:
+ $directive->id = $this->id($hash->offsetGet('ID'));
+ $id = $directive->id->toString(); // convenience
+
+ if (isset($hash['TYPE'])) {
+ $type = explode('/', $hash->offsetGet('TYPE'));
+ if (isset($type[1])) {
+ $directive->typeAllowsNull = true;
+ }
+ $directive->type = $type[0];
+ } else {
+ throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined");
+ }
+
+ if (isset($hash['DEFAULT'])) {
+ try {
+ $directive->default = $this->varParser->parse(
+ $hash->offsetGet('DEFAULT'),
+ $directive->type,
+ $directive->typeAllowsNull
+ );
+ } catch (HTMLPurifier_VarParserException $e) {
+ throw new HTMLPurifier_ConfigSchema_Exception($e->getMessage() . " in DEFAULT in directive hash '$id'");
+ }
+ }
+
+ if (isset($hash['DESCRIPTION'])) {
+ $directive->description = $hash->offsetGet('DESCRIPTION');
+ }
+
+ if (isset($hash['ALLOWED'])) {
+ $directive->allowed = $this->lookup($this->evalArray($hash->offsetGet('ALLOWED')));
+ }
+
+ if (isset($hash['VALUE-ALIASES'])) {
+ $directive->valueAliases = $this->evalArray($hash->offsetGet('VALUE-ALIASES'));
+ }
+
+ if (isset($hash['ALIASES'])) {
+ $raw_aliases = trim($hash->offsetGet('ALIASES'));
+ $aliases = preg_split('/\s*,\s*/', $raw_aliases);
+ foreach ($aliases as $alias) {
+ $directive->aliases[] = $this->id($alias);
+ }
+ }
+
+ if (isset($hash['VERSION'])) {
+ $directive->version = $hash->offsetGet('VERSION');
+ }
+
+ if (isset($hash['DEPRECATED-USE'])) {
+ $directive->deprecatedUse = $this->id($hash->offsetGet('DEPRECATED-USE'));
+ }
+
+ if (isset($hash['DEPRECATED-VERSION'])) {
+ $directive->deprecatedVersion = $hash->offsetGet('DEPRECATED-VERSION');
+ }
+
+ if (isset($hash['EXTERNAL'])) {
+ $directive->external = preg_split('/\s*,\s*/', trim($hash->offsetGet('EXTERNAL')));
+ }
+
+ $interchange->addDirective($directive);
+ }
+
+ /**
+ * Evaluates an array PHP code string without array() wrapper
+ * @param string $contents
+ */
+ protected function evalArray($contents)
+ {
+ return eval('return array(' . $contents . ');');
+ }
+
+ /**
+ * Converts an array list into a lookup array.
+ * @param array $array
+ * @return array
+ */
+ protected function lookup($array)
+ {
+ $ret = array();
+ foreach ($array as $val) {
+ $ret[$val] = true;
+ }
+ return $ret;
+ }
+
+ /**
+ * Convenience function that creates an HTMLPurifier_ConfigSchema_Interchange_Id
+ * object based on a string Id.
+ * @param string $id
+ * @return HTMLPurifier_ConfigSchema_Interchange_Id
+ */
+ protected function id($id)
+ {
+ return HTMLPurifier_ConfigSchema_Interchange_Id::make($id);
+ }
+
+ /**
+ * Triggers errors for any unused keys passed in the hash; such keys
+ * may indicate typos, missing values, etc.
+ * @param HTMLPurifier_StringHash $hash Hash to check.
+ */
+ protected function _findUnused($hash)
+ {
+ $accessed = $hash->getAccessed();
+ foreach ($hash as $k => $v) {
+ if (!isset($accessed[$k])) {
+ trigger_error("String hash key '$k' not used by builder", E_USER_NOTICE);
+ }
+ }
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php
new file mode 100644
index 000000000..fb3127788
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php
@@ -0,0 +1,248 @@
+parser = new HTMLPurifier_VarParser();
+ }
+
+ /**
+ * Validates a fully-formed interchange object.
+ * @param HTMLPurifier_ConfigSchema_Interchange $interchange
+ * @return bool
+ */
+ public function validate($interchange)
+ {
+ $this->interchange = $interchange;
+ $this->aliases = array();
+ // PHP is a bit lax with integer <=> string conversions in
+ // arrays, so we don't use the identical !== comparison
+ foreach ($interchange->directives as $i => $directive) {
+ $id = $directive->id->toString();
+ if ($i != $id) {
+ $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'");
+ }
+ $this->validateDirective($directive);
+ }
+ return true;
+ }
+
+ /**
+ * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object.
+ * @param HTMLPurifier_ConfigSchema_Interchange_Id $id
+ */
+ public function validateId($id)
+ {
+ $id_string = $id->toString();
+ $this->context[] = "id '$id_string'";
+ if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) {
+ // handled by InterchangeBuilder
+ $this->error(false, 'is not an instance of HTMLPurifier_ConfigSchema_Interchange_Id');
+ }
+ // keys are now unconstrained (we might want to narrow down to A-Za-z0-9.)
+ // we probably should check that it has at least one namespace
+ $this->with($id, 'key')
+ ->assertNotEmpty()
+ ->assertIsString(); // implicit assertIsString handled by InterchangeBuilder
+ array_pop($this->context);
+ }
+
+ /**
+ * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object.
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
+ */
+ public function validateDirective($d)
+ {
+ $id = $d->id->toString();
+ $this->context[] = "directive '$id'";
+ $this->validateId($d->id);
+
+ $this->with($d, 'description')
+ ->assertNotEmpty();
+
+ // BEGIN - handled by InterchangeBuilder
+ $this->with($d, 'type')
+ ->assertNotEmpty();
+ $this->with($d, 'typeAllowsNull')
+ ->assertIsBool();
+ try {
+ // This also tests validity of $d->type
+ $this->parser->parse($d->default, $d->type, $d->typeAllowsNull);
+ } catch (HTMLPurifier_VarParserException $e) {
+ $this->error('default', 'had error: ' . $e->getMessage());
+ }
+ // END - handled by InterchangeBuilder
+
+ if (!is_null($d->allowed) || !empty($d->valueAliases)) {
+ // allowed and valueAliases require that we be dealing with
+ // strings, so check for that early.
+ $d_int = HTMLPurifier_VarParser::$types[$d->type];
+ if (!isset(HTMLPurifier_VarParser::$stringTypes[$d_int])) {
+ $this->error('type', 'must be a string type when used with allowed or value aliases');
+ }
+ }
+
+ $this->validateDirectiveAllowed($d);
+ $this->validateDirectiveValueAliases($d);
+ $this->validateDirectiveAliases($d);
+
+ array_pop($this->context);
+ }
+
+ /**
+ * Extra validation if $allowed member variable of
+ * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
+ */
+ public function validateDirectiveAllowed($d)
+ {
+ if (is_null($d->allowed)) {
+ return;
+ }
+ $this->with($d, 'allowed')
+ ->assertNotEmpty()
+ ->assertIsLookup(); // handled by InterchangeBuilder
+ if (is_string($d->default) && !isset($d->allowed[$d->default])) {
+ $this->error('default', 'must be an allowed value');
+ }
+ $this->context[] = 'allowed';
+ foreach ($d->allowed as $val => $x) {
+ if (!is_string($val)) {
+ $this->error("value $val", 'must be a string');
+ }
+ }
+ array_pop($this->context);
+ }
+
+ /**
+ * Extra validation if $valueAliases member variable of
+ * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
+ */
+ public function validateDirectiveValueAliases($d)
+ {
+ if (is_null($d->valueAliases)) {
+ return;
+ }
+ $this->with($d, 'valueAliases')
+ ->assertIsArray(); // handled by InterchangeBuilder
+ $this->context[] = 'valueAliases';
+ foreach ($d->valueAliases as $alias => $real) {
+ if (!is_string($alias)) {
+ $this->error("alias $alias", 'must be a string');
+ }
+ if (!is_string($real)) {
+ $this->error("alias target $real from alias '$alias'", 'must be a string');
+ }
+ if ($alias === $real) {
+ $this->error("alias '$alias'", "must not be an alias to itself");
+ }
+ }
+ if (!is_null($d->allowed)) {
+ foreach ($d->valueAliases as $alias => $real) {
+ if (isset($d->allowed[$alias])) {
+ $this->error("alias '$alias'", 'must not be an allowed value');
+ } elseif (!isset($d->allowed[$real])) {
+ $this->error("alias '$alias'", 'must be an alias to an allowed value');
+ }
+ }
+ }
+ array_pop($this->context);
+ }
+
+ /**
+ * Extra validation if $aliases member variable of
+ * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
+ * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d
+ */
+ public function validateDirectiveAliases($d)
+ {
+ $this->with($d, 'aliases')
+ ->assertIsArray(); // handled by InterchangeBuilder
+ $this->context[] = 'aliases';
+ foreach ($d->aliases as $alias) {
+ $this->validateId($alias);
+ $s = $alias->toString();
+ if (isset($this->interchange->directives[$s])) {
+ $this->error("alias '$s'", 'collides with another directive');
+ }
+ if (isset($this->aliases[$s])) {
+ $other_directive = $this->aliases[$s];
+ $this->error("alias '$s'", "collides with alias for directive '$other_directive'");
+ }
+ $this->aliases[$s] = $d->id->toString();
+ }
+ array_pop($this->context);
+ }
+
+ // protected helper functions
+
+ /**
+ * Convenience function for generating HTMLPurifier_ConfigSchema_ValidatorAtom
+ * for validating simple member variables of objects.
+ * @param $obj
+ * @param $member
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ protected function with($obj, $member)
+ {
+ return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member);
+ }
+
+ /**
+ * Emits an error, providing helpful context.
+ * @throws HTMLPurifier_ConfigSchema_Exception
+ */
+ protected function error($target, $msg)
+ {
+ if ($target !== false) {
+ $prefix = ucfirst($target) . ' in ' . $this->getFormattedContext();
+ } else {
+ $prefix = ucfirst($this->getFormattedContext());
+ }
+ throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg));
+ }
+
+ /**
+ * Returns a formatted context string.
+ * @return string
+ */
+ protected function getFormattedContext()
+ {
+ return implode(' in ', array_reverse($this->context));
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php
new file mode 100644
index 000000000..c9aa3644a
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php
@@ -0,0 +1,130 @@
+context = $context;
+ $this->obj = $obj;
+ $this->member = $member;
+ $this->contents =& $obj->$member;
+ }
+
+ /**
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ public function assertIsString()
+ {
+ if (!is_string($this->contents)) {
+ $this->error('must be a string');
+ }
+ return $this;
+ }
+
+ /**
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ public function assertIsBool()
+ {
+ if (!is_bool($this->contents)) {
+ $this->error('must be a boolean');
+ }
+ return $this;
+ }
+
+ /**
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ public function assertIsArray()
+ {
+ if (!is_array($this->contents)) {
+ $this->error('must be an array');
+ }
+ return $this;
+ }
+
+ /**
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ public function assertNotNull()
+ {
+ if ($this->contents === null) {
+ $this->error('must not be null');
+ }
+ return $this;
+ }
+
+ /**
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ public function assertAlnum()
+ {
+ $this->assertIsString();
+ if (!ctype_alnum($this->contents)) {
+ $this->error('must be alphanumeric');
+ }
+ return $this;
+ }
+
+ /**
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ public function assertNotEmpty()
+ {
+ if (empty($this->contents)) {
+ $this->error('must not be empty');
+ }
+ return $this;
+ }
+
+ /**
+ * @return HTMLPurifier_ConfigSchema_ValidatorAtom
+ */
+ public function assertIsLookup()
+ {
+ $this->assertIsArray();
+ foreach ($this->contents as $v) {
+ if ($v !== true) {
+ $this->error('must be a lookup array');
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * @param string $msg
+ * @throws HTMLPurifier_ConfigSchema_Exception
+ */
+ protected function error($msg)
+ {
+ throw new HTMLPurifier_ConfigSchema_Exception(ucfirst($this->member) . ' in ' . $this->context . ' ' . $msg);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser
new file mode 100644
index 000000000..a5426c736
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser
@@ -0,0 +1 @@
+O:25:"HTMLPurifier_ConfigSchema":3:{s:8:"defaults";a:127:{s:19:"Attr.AllowedClasses";N;s:24:"Attr.AllowedFrameTargets";a:0:{}s:15:"Attr.AllowedRel";a:0:{}s:15:"Attr.AllowedRev";a:0:{}s:18:"Attr.ClassUseCDATA";N;s:20:"Attr.DefaultImageAlt";N;s:24:"Attr.DefaultInvalidImage";s:0:"";s:27:"Attr.DefaultInvalidImageAlt";s:13:"Invalid image";s:19:"Attr.DefaultTextDir";s:3:"ltr";s:13:"Attr.EnableID";b:0;s:21:"Attr.ForbiddenClasses";a:0:{}s:13:"Attr.ID.HTML5";N;s:16:"Attr.IDBlacklist";a:0:{}s:22:"Attr.IDBlacklistRegexp";N;s:13:"Attr.IDPrefix";s:0:"";s:18:"Attr.IDPrefixLocal";s:0:"";s:24:"AutoFormat.AutoParagraph";b:0;s:17:"AutoFormat.Custom";a:0:{}s:25:"AutoFormat.DisplayLinkURI";b:0;s:18:"AutoFormat.Linkify";b:0;s:33:"AutoFormat.PurifierLinkify.DocURL";s:3:"#%s";s:26:"AutoFormat.PurifierLinkify";b:0;s:32:"AutoFormat.RemoveEmpty.Predicate";a:4:{s:8:"colgroup";a:0:{}s:2:"th";a:0:{}s:2:"td";a:0:{}s:6:"iframe";a:1:{i:0;s:3:"src";}}s:44:"AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions";a:2:{s:2:"td";b:1;s:2:"th";b:1;}s:33:"AutoFormat.RemoveEmpty.RemoveNbsp";b:0;s:22:"AutoFormat.RemoveEmpty";b:0;s:39:"AutoFormat.RemoveSpansWithoutAttributes";b:0;s:19:"CSS.AllowDuplicates";b:0;s:18:"CSS.AllowImportant";b:0;s:15:"CSS.AllowTricky";b:0;s:16:"CSS.AllowedFonts";N;s:21:"CSS.AllowedProperties";N;s:17:"CSS.DefinitionRev";i:1;s:23:"CSS.ForbiddenProperties";a:0:{}s:16:"CSS.MaxImgLength";s:6:"1200px";s:15:"CSS.Proprietary";b:0;s:11:"CSS.Trusted";b:0;s:20:"Cache.DefinitionImpl";s:10:"Serializer";s:20:"Cache.SerializerPath";N;s:27:"Cache.SerializerPermissions";i:493;s:22:"Core.AggressivelyFixLt";b:1;s:29:"Core.AggressivelyRemoveScript";b:1;s:28:"Core.AllowHostnameUnderscore";b:0;s:23:"Core.AllowParseManyTags";b:0;s:18:"Core.CollectErrors";b:0;s:18:"Core.ColorKeywords";a:148:{s:9:"aliceblue";s:7:"#F0F8FF";s:12:"antiquewhite";s:7:"#FAEBD7";s:4:"aqua";s:7:"#00FFFF";s:10:"aquamarine";s:7:"#7FFFD4";s:5:"azure";s:7:"#F0FFFF";s:5:"beige";s:7:"#F5F5DC";s:6:"bisque";s:7:"#FFE4C4";s:5:"black";s:7:"#000000";s:14:"blanchedalmond";s:7:"#FFEBCD";s:4:"blue";s:7:"#0000FF";s:10:"blueviolet";s:7:"#8A2BE2";s:5:"brown";s:7:"#A52A2A";s:9:"burlywood";s:7:"#DEB887";s:9:"cadetblue";s:7:"#5F9EA0";s:10:"chartreuse";s:7:"#7FFF00";s:9:"chocolate";s:7:"#D2691E";s:5:"coral";s:7:"#FF7F50";s:14:"cornflowerblue";s:7:"#6495ED";s:8:"cornsilk";s:7:"#FFF8DC";s:7:"crimson";s:7:"#DC143C";s:4:"cyan";s:7:"#00FFFF";s:8:"darkblue";s:7:"#00008B";s:8:"darkcyan";s:7:"#008B8B";s:13:"darkgoldenrod";s:7:"#B8860B";s:8:"darkgray";s:7:"#A9A9A9";s:8:"darkgrey";s:7:"#A9A9A9";s:9:"darkgreen";s:7:"#006400";s:9:"darkkhaki";s:7:"#BDB76B";s:11:"darkmagenta";s:7:"#8B008B";s:14:"darkolivegreen";s:7:"#556B2F";s:10:"darkorange";s:7:"#FF8C00";s:10:"darkorchid";s:7:"#9932CC";s:7:"darkred";s:7:"#8B0000";s:10:"darksalmon";s:7:"#E9967A";s:12:"darkseagreen";s:7:"#8FBC8F";s:13:"darkslateblue";s:7:"#483D8B";s:13:"darkslategray";s:7:"#2F4F4F";s:13:"darkslategrey";s:7:"#2F4F4F";s:13:"darkturquoise";s:7:"#00CED1";s:10:"darkviolet";s:7:"#9400D3";s:8:"deeppink";s:7:"#FF1493";s:11:"deepskyblue";s:7:"#00BFFF";s:7:"dimgray";s:7:"#696969";s:7:"dimgrey";s:7:"#696969";s:10:"dodgerblue";s:7:"#1E90FF";s:9:"firebrick";s:7:"#B22222";s:11:"floralwhite";s:7:"#FFFAF0";s:11:"forestgreen";s:7:"#228B22";s:7:"fuchsia";s:7:"#FF00FF";s:9:"gainsboro";s:7:"#DCDCDC";s:10:"ghostwhite";s:7:"#F8F8FF";s:4:"gold";s:7:"#FFD700";s:9:"goldenrod";s:7:"#DAA520";s:4:"gray";s:7:"#808080";s:4:"grey";s:7:"#808080";s:5:"green";s:7:"#008000";s:11:"greenyellow";s:7:"#ADFF2F";s:8:"honeydew";s:7:"#F0FFF0";s:7:"hotpink";s:7:"#FF69B4";s:9:"indianred";s:7:"#CD5C5C";s:6:"indigo";s:7:"#4B0082";s:5:"ivory";s:7:"#FFFFF0";s:5:"khaki";s:7:"#F0E68C";s:8:"lavender";s:7:"#E6E6FA";s:13:"lavenderblush";s:7:"#FFF0F5";s:9:"lawngreen";s:7:"#7CFC00";s:12:"lemonchiffon";s:7:"#FFFACD";s:9:"lightblue";s:7:"#ADD8E6";s:10:"lightcoral";s:7:"#F08080";s:9:"lightcyan";s:7:"#E0FFFF";s:20:"lightgoldenrodyellow";s:7:"#FAFAD2";s:9:"lightgray";s:7:"#D3D3D3";s:9:"lightgrey";s:7:"#D3D3D3";s:10:"lightgreen";s:7:"#90EE90";s:9:"lightpink";s:7:"#FFB6C1";s:11:"lightsalmon";s:7:"#FFA07A";s:13:"lightseagreen";s:7:"#20B2AA";s:12:"lightskyblue";s:7:"#87CEFA";s:14:"lightslategray";s:7:"#778899";s:14:"lightslategrey";s:7:"#778899";s:14:"lightsteelblue";s:7:"#B0C4DE";s:11:"lightyellow";s:7:"#FFFFE0";s:4:"lime";s:7:"#00FF00";s:9:"limegreen";s:7:"#32CD32";s:5:"linen";s:7:"#FAF0E6";s:7:"magenta";s:7:"#FF00FF";s:6:"maroon";s:7:"#800000";s:16:"mediumaquamarine";s:7:"#66CDAA";s:10:"mediumblue";s:7:"#0000CD";s:12:"mediumorchid";s:7:"#BA55D3";s:12:"mediumpurple";s:7:"#9370DB";s:14:"mediumseagreen";s:7:"#3CB371";s:15:"mediumslateblue";s:7:"#7B68EE";s:17:"mediumspringgreen";s:7:"#00FA9A";s:15:"mediumturquoise";s:7:"#48D1CC";s:15:"mediumvioletred";s:7:"#C71585";s:12:"midnightblue";s:7:"#191970";s:9:"mintcream";s:7:"#F5FFFA";s:9:"mistyrose";s:7:"#FFE4E1";s:8:"moccasin";s:7:"#FFE4B5";s:11:"navajowhite";s:7:"#FFDEAD";s:4:"navy";s:7:"#000080";s:7:"oldlace";s:7:"#FDF5E6";s:5:"olive";s:7:"#808000";s:9:"olivedrab";s:7:"#6B8E23";s:6:"orange";s:7:"#FFA500";s:9:"orangered";s:7:"#FF4500";s:6:"orchid";s:7:"#DA70D6";s:13:"palegoldenrod";s:7:"#EEE8AA";s:9:"palegreen";s:7:"#98FB98";s:13:"paleturquoise";s:7:"#AFEEEE";s:13:"palevioletred";s:7:"#DB7093";s:10:"papayawhip";s:7:"#FFEFD5";s:9:"peachpuff";s:7:"#FFDAB9";s:4:"peru";s:7:"#CD853F";s:4:"pink";s:7:"#FFC0CB";s:4:"plum";s:7:"#DDA0DD";s:10:"powderblue";s:7:"#B0E0E6";s:6:"purple";s:7:"#800080";s:13:"rebeccapurple";s:7:"#663399";s:3:"red";s:7:"#FF0000";s:9:"rosybrown";s:7:"#BC8F8F";s:9:"royalblue";s:7:"#4169E1";s:11:"saddlebrown";s:7:"#8B4513";s:6:"salmon";s:7:"#FA8072";s:10:"sandybrown";s:7:"#F4A460";s:8:"seagreen";s:7:"#2E8B57";s:8:"seashell";s:7:"#FFF5EE";s:6:"sienna";s:7:"#A0522D";s:6:"silver";s:7:"#C0C0C0";s:7:"skyblue";s:7:"#87CEEB";s:9:"slateblue";s:7:"#6A5ACD";s:9:"slategray";s:7:"#708090";s:9:"slategrey";s:7:"#708090";s:4:"snow";s:7:"#FFFAFA";s:11:"springgreen";s:7:"#00FF7F";s:9:"steelblue";s:7:"#4682B4";s:3:"tan";s:7:"#D2B48C";s:4:"teal";s:7:"#008080";s:7:"thistle";s:7:"#D8BFD8";s:6:"tomato";s:7:"#FF6347";s:9:"turquoise";s:7:"#40E0D0";s:6:"violet";s:7:"#EE82EE";s:5:"wheat";s:7:"#F5DEB3";s:5:"white";s:7:"#FFFFFF";s:10:"whitesmoke";s:7:"#F5F5F5";s:6:"yellow";s:7:"#FFFF00";s:11:"yellowgreen";s:7:"#9ACD32";}s:30:"Core.ConvertDocumentToFragment";b:1;s:36:"Core.DirectLexLineNumberSyncInterval";i:0;s:20:"Core.DisableExcludes";b:0;s:15:"Core.EnableIDNA";b:0;s:13:"Core.Encoding";s:5:"utf-8";s:26:"Core.EscapeInvalidChildren";b:0;s:22:"Core.EscapeInvalidTags";b:0;s:29:"Core.EscapeNonASCIICharacters";b:0;s:19:"Core.HiddenElements";a:2:{s:6:"script";b:1;s:5:"style";b:1;}s:13:"Core.Language";s:2:"en";s:24:"Core.LegacyEntityDecoder";b:0;s:14:"Core.LexerImpl";N;s:24:"Core.MaintainLineNumbers";N;s:22:"Core.NormalizeNewlines";b:1;s:21:"Core.RemoveInvalidImg";b:1;s:33:"Core.RemoveProcessingInstructions";b:0;s:25:"Core.RemoveScriptContents";N;s:13:"Filter.Custom";a:0:{}s:34:"Filter.ExtractStyleBlocks.Escaping";b:1;s:31:"Filter.ExtractStyleBlocks.Scope";N;s:34:"Filter.ExtractStyleBlocks.TidyImpl";N;s:25:"Filter.ExtractStyleBlocks";b:0;s:14:"Filter.YouTube";b:0;s:12:"HTML.Allowed";N;s:22:"HTML.AllowedAttributes";N;s:20:"HTML.AllowedComments";a:0:{}s:26:"HTML.AllowedCommentsRegexp";N;s:20:"HTML.AllowedElements";N;s:19:"HTML.AllowedModules";N;s:23:"HTML.Attr.Name.UseCDATA";b:0;s:17:"HTML.BlockWrapper";s:1:"p";s:16:"HTML.CoreModules";a:7:{s:9:"Structure";b:1;s:4:"Text";b:1;s:9:"Hypertext";b:1;s:4:"List";b:1;s:22:"NonXMLCommonAttributes";b:1;s:19:"XMLCommonAttributes";b:1;s:16:"CommonAttributes";b:1;}s:18:"HTML.CustomDoctype";N;s:17:"HTML.DefinitionID";N;s:18:"HTML.DefinitionRev";i:1;s:12:"HTML.Doctype";N;s:25:"HTML.FlashAllowFullScreen";b:0;s:24:"HTML.ForbiddenAttributes";a:0:{}s:22:"HTML.ForbiddenElements";a:0:{}s:10:"HTML.Forms";b:0;s:17:"HTML.MaxImgLength";i:1200;s:13:"HTML.Nofollow";b:0;s:11:"HTML.Parent";s:3:"div";s:16:"HTML.Proprietary";b:0;s:14:"HTML.SafeEmbed";b:0;s:15:"HTML.SafeIframe";b:0;s:15:"HTML.SafeObject";b:0;s:18:"HTML.SafeScripting";a:0:{}s:11:"HTML.Strict";b:0;s:16:"HTML.TargetBlank";b:0;s:19:"HTML.TargetNoopener";b:1;s:21:"HTML.TargetNoreferrer";b:1;s:12:"HTML.TidyAdd";a:0:{}s:14:"HTML.TidyLevel";s:6:"medium";s:15:"HTML.TidyRemove";a:0:{}s:12:"HTML.Trusted";b:0;s:10:"HTML.XHTML";b:1;s:28:"Output.CommentScriptContents";b:1;s:19:"Output.FixInnerHTML";b:1;s:18:"Output.FlashCompat";b:0;s:14:"Output.Newline";N;s:15:"Output.SortAttr";b:0;s:17:"Output.TidyFormat";b:0;s:17:"Test.ForceNoIconv";b:0;s:18:"URI.AllowedSchemes";a:7:{s:4:"http";b:1;s:5:"https";b:1;s:6:"mailto";b:1;s:3:"ftp";b:1;s:4:"nntp";b:1;s:4:"news";b:1;s:3:"tel";b:1;}s:8:"URI.Base";N;s:17:"URI.DefaultScheme";s:4:"http";s:16:"URI.DefinitionID";N;s:17:"URI.DefinitionRev";i:1;s:11:"URI.Disable";b:0;s:19:"URI.DisableExternal";b:0;s:28:"URI.DisableExternalResources";b:0;s:20:"URI.DisableResources";b:0;s:8:"URI.Host";N;s:17:"URI.HostBlacklist";a:0:{}s:16:"URI.MakeAbsolute";b:0;s:9:"URI.Munge";N;s:18:"URI.MungeResources";b:0;s:18:"URI.MungeSecretKey";N;s:26:"URI.OverrideAllowedSchemes";b:1;s:20:"URI.SafeIframeRegexp";N;}s:12:"defaultPlist";O:25:"HTMLPurifier_PropertyList":3:{s:7:" * data";a:127:{s:19:"Attr.AllowedClasses";N;s:24:"Attr.AllowedFrameTargets";a:0:{}s:15:"Attr.AllowedRel";a:0:{}s:15:"Attr.AllowedRev";a:0:{}s:18:"Attr.ClassUseCDATA";N;s:20:"Attr.DefaultImageAlt";N;s:24:"Attr.DefaultInvalidImage";s:0:"";s:27:"Attr.DefaultInvalidImageAlt";s:13:"Invalid image";s:19:"Attr.DefaultTextDir";s:3:"ltr";s:13:"Attr.EnableID";b:0;s:21:"Attr.ForbiddenClasses";a:0:{}s:13:"Attr.ID.HTML5";N;s:16:"Attr.IDBlacklist";a:0:{}s:22:"Attr.IDBlacklistRegexp";N;s:13:"Attr.IDPrefix";s:0:"";s:18:"Attr.IDPrefixLocal";s:0:"";s:24:"AutoFormat.AutoParagraph";b:0;s:17:"AutoFormat.Custom";a:0:{}s:25:"AutoFormat.DisplayLinkURI";b:0;s:18:"AutoFormat.Linkify";b:0;s:33:"AutoFormat.PurifierLinkify.DocURL";s:3:"#%s";s:26:"AutoFormat.PurifierLinkify";b:0;s:32:"AutoFormat.RemoveEmpty.Predicate";a:4:{s:8:"colgroup";a:0:{}s:2:"th";a:0:{}s:2:"td";a:0:{}s:6:"iframe";a:1:{i:0;s:3:"src";}}s:44:"AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions";a:2:{s:2:"td";b:1;s:2:"th";b:1;}s:33:"AutoFormat.RemoveEmpty.RemoveNbsp";b:0;s:22:"AutoFormat.RemoveEmpty";b:0;s:39:"AutoFormat.RemoveSpansWithoutAttributes";b:0;s:19:"CSS.AllowDuplicates";b:0;s:18:"CSS.AllowImportant";b:0;s:15:"CSS.AllowTricky";b:0;s:16:"CSS.AllowedFonts";N;s:21:"CSS.AllowedProperties";N;s:17:"CSS.DefinitionRev";i:1;s:23:"CSS.ForbiddenProperties";a:0:{}s:16:"CSS.MaxImgLength";s:6:"1200px";s:15:"CSS.Proprietary";b:0;s:11:"CSS.Trusted";b:0;s:20:"Cache.DefinitionImpl";s:10:"Serializer";s:20:"Cache.SerializerPath";N;s:27:"Cache.SerializerPermissions";i:493;s:22:"Core.AggressivelyFixLt";b:1;s:29:"Core.AggressivelyRemoveScript";b:1;s:28:"Core.AllowHostnameUnderscore";b:0;s:23:"Core.AllowParseManyTags";b:0;s:18:"Core.CollectErrors";b:0;s:18:"Core.ColorKeywords";a:148:{s:9:"aliceblue";s:7:"#F0F8FF";s:12:"antiquewhite";s:7:"#FAEBD7";s:4:"aqua";s:7:"#00FFFF";s:10:"aquamarine";s:7:"#7FFFD4";s:5:"azure";s:7:"#F0FFFF";s:5:"beige";s:7:"#F5F5DC";s:6:"bisque";s:7:"#FFE4C4";s:5:"black";s:7:"#000000";s:14:"blanchedalmond";s:7:"#FFEBCD";s:4:"blue";s:7:"#0000FF";s:10:"blueviolet";s:7:"#8A2BE2";s:5:"brown";s:7:"#A52A2A";s:9:"burlywood";s:7:"#DEB887";s:9:"cadetblue";s:7:"#5F9EA0";s:10:"chartreuse";s:7:"#7FFF00";s:9:"chocolate";s:7:"#D2691E";s:5:"coral";s:7:"#FF7F50";s:14:"cornflowerblue";s:7:"#6495ED";s:8:"cornsilk";s:7:"#FFF8DC";s:7:"crimson";s:7:"#DC143C";s:4:"cyan";s:7:"#00FFFF";s:8:"darkblue";s:7:"#00008B";s:8:"darkcyan";s:7:"#008B8B";s:13:"darkgoldenrod";s:7:"#B8860B";s:8:"darkgray";s:7:"#A9A9A9";s:8:"darkgrey";s:7:"#A9A9A9";s:9:"darkgreen";s:7:"#006400";s:9:"darkkhaki";s:7:"#BDB76B";s:11:"darkmagenta";s:7:"#8B008B";s:14:"darkolivegreen";s:7:"#556B2F";s:10:"darkorange";s:7:"#FF8C00";s:10:"darkorchid";s:7:"#9932CC";s:7:"darkred";s:7:"#8B0000";s:10:"darksalmon";s:7:"#E9967A";s:12:"darkseagreen";s:7:"#8FBC8F";s:13:"darkslateblue";s:7:"#483D8B";s:13:"darkslategray";s:7:"#2F4F4F";s:13:"darkslategrey";s:7:"#2F4F4F";s:13:"darkturquoise";s:7:"#00CED1";s:10:"darkviolet";s:7:"#9400D3";s:8:"deeppink";s:7:"#FF1493";s:11:"deepskyblue";s:7:"#00BFFF";s:7:"dimgray";s:7:"#696969";s:7:"dimgrey";s:7:"#696969";s:10:"dodgerblue";s:7:"#1E90FF";s:9:"firebrick";s:7:"#B22222";s:11:"floralwhite";s:7:"#FFFAF0";s:11:"forestgreen";s:7:"#228B22";s:7:"fuchsia";s:7:"#FF00FF";s:9:"gainsboro";s:7:"#DCDCDC";s:10:"ghostwhite";s:7:"#F8F8FF";s:4:"gold";s:7:"#FFD700";s:9:"goldenrod";s:7:"#DAA520";s:4:"gray";s:7:"#808080";s:4:"grey";s:7:"#808080";s:5:"green";s:7:"#008000";s:11:"greenyellow";s:7:"#ADFF2F";s:8:"honeydew";s:7:"#F0FFF0";s:7:"hotpink";s:7:"#FF69B4";s:9:"indianred";s:7:"#CD5C5C";s:6:"indigo";s:7:"#4B0082";s:5:"ivory";s:7:"#FFFFF0";s:5:"khaki";s:7:"#F0E68C";s:8:"lavender";s:7:"#E6E6FA";s:13:"lavenderblush";s:7:"#FFF0F5";s:9:"lawngreen";s:7:"#7CFC00";s:12:"lemonchiffon";s:7:"#FFFACD";s:9:"lightblue";s:7:"#ADD8E6";s:10:"lightcoral";s:7:"#F08080";s:9:"lightcyan";s:7:"#E0FFFF";s:20:"lightgoldenrodyellow";s:7:"#FAFAD2";s:9:"lightgray";s:7:"#D3D3D3";s:9:"lightgrey";s:7:"#D3D3D3";s:10:"lightgreen";s:7:"#90EE90";s:9:"lightpink";s:7:"#FFB6C1";s:11:"lightsalmon";s:7:"#FFA07A";s:13:"lightseagreen";s:7:"#20B2AA";s:12:"lightskyblue";s:7:"#87CEFA";s:14:"lightslategray";s:7:"#778899";s:14:"lightslategrey";s:7:"#778899";s:14:"lightsteelblue";s:7:"#B0C4DE";s:11:"lightyellow";s:7:"#FFFFE0";s:4:"lime";s:7:"#00FF00";s:9:"limegreen";s:7:"#32CD32";s:5:"linen";s:7:"#FAF0E6";s:7:"magenta";s:7:"#FF00FF";s:6:"maroon";s:7:"#800000";s:16:"mediumaquamarine";s:7:"#66CDAA";s:10:"mediumblue";s:7:"#0000CD";s:12:"mediumorchid";s:7:"#BA55D3";s:12:"mediumpurple";s:7:"#9370DB";s:14:"mediumseagreen";s:7:"#3CB371";s:15:"mediumslateblue";s:7:"#7B68EE";s:17:"mediumspringgreen";s:7:"#00FA9A";s:15:"mediumturquoise";s:7:"#48D1CC";s:15:"mediumvioletred";s:7:"#C71585";s:12:"midnightblue";s:7:"#191970";s:9:"mintcream";s:7:"#F5FFFA";s:9:"mistyrose";s:7:"#FFE4E1";s:8:"moccasin";s:7:"#FFE4B5";s:11:"navajowhite";s:7:"#FFDEAD";s:4:"navy";s:7:"#000080";s:7:"oldlace";s:7:"#FDF5E6";s:5:"olive";s:7:"#808000";s:9:"olivedrab";s:7:"#6B8E23";s:6:"orange";s:7:"#FFA500";s:9:"orangered";s:7:"#FF4500";s:6:"orchid";s:7:"#DA70D6";s:13:"palegoldenrod";s:7:"#EEE8AA";s:9:"palegreen";s:7:"#98FB98";s:13:"paleturquoise";s:7:"#AFEEEE";s:13:"palevioletred";s:7:"#DB7093";s:10:"papayawhip";s:7:"#FFEFD5";s:9:"peachpuff";s:7:"#FFDAB9";s:4:"peru";s:7:"#CD853F";s:4:"pink";s:7:"#FFC0CB";s:4:"plum";s:7:"#DDA0DD";s:10:"powderblue";s:7:"#B0E0E6";s:6:"purple";s:7:"#800080";s:13:"rebeccapurple";s:7:"#663399";s:3:"red";s:7:"#FF0000";s:9:"rosybrown";s:7:"#BC8F8F";s:9:"royalblue";s:7:"#4169E1";s:11:"saddlebrown";s:7:"#8B4513";s:6:"salmon";s:7:"#FA8072";s:10:"sandybrown";s:7:"#F4A460";s:8:"seagreen";s:7:"#2E8B57";s:8:"seashell";s:7:"#FFF5EE";s:6:"sienna";s:7:"#A0522D";s:6:"silver";s:7:"#C0C0C0";s:7:"skyblue";s:7:"#87CEEB";s:9:"slateblue";s:7:"#6A5ACD";s:9:"slategray";s:7:"#708090";s:9:"slategrey";s:7:"#708090";s:4:"snow";s:7:"#FFFAFA";s:11:"springgreen";s:7:"#00FF7F";s:9:"steelblue";s:7:"#4682B4";s:3:"tan";s:7:"#D2B48C";s:4:"teal";s:7:"#008080";s:7:"thistle";s:7:"#D8BFD8";s:6:"tomato";s:7:"#FF6347";s:9:"turquoise";s:7:"#40E0D0";s:6:"violet";s:7:"#EE82EE";s:5:"wheat";s:7:"#F5DEB3";s:5:"white";s:7:"#FFFFFF";s:10:"whitesmoke";s:7:"#F5F5F5";s:6:"yellow";s:7:"#FFFF00";s:11:"yellowgreen";s:7:"#9ACD32";}s:30:"Core.ConvertDocumentToFragment";b:1;s:36:"Core.DirectLexLineNumberSyncInterval";i:0;s:20:"Core.DisableExcludes";b:0;s:15:"Core.EnableIDNA";b:0;s:13:"Core.Encoding";s:5:"utf-8";s:26:"Core.EscapeInvalidChildren";b:0;s:22:"Core.EscapeInvalidTags";b:0;s:29:"Core.EscapeNonASCIICharacters";b:0;s:19:"Core.HiddenElements";a:2:{s:6:"script";b:1;s:5:"style";b:1;}s:13:"Core.Language";s:2:"en";s:24:"Core.LegacyEntityDecoder";b:0;s:14:"Core.LexerImpl";N;s:24:"Core.MaintainLineNumbers";N;s:22:"Core.NormalizeNewlines";b:1;s:21:"Core.RemoveInvalidImg";b:1;s:33:"Core.RemoveProcessingInstructions";b:0;s:25:"Core.RemoveScriptContents";N;s:13:"Filter.Custom";a:0:{}s:34:"Filter.ExtractStyleBlocks.Escaping";b:1;s:31:"Filter.ExtractStyleBlocks.Scope";N;s:34:"Filter.ExtractStyleBlocks.TidyImpl";N;s:25:"Filter.ExtractStyleBlocks";b:0;s:14:"Filter.YouTube";b:0;s:12:"HTML.Allowed";N;s:22:"HTML.AllowedAttributes";N;s:20:"HTML.AllowedComments";a:0:{}s:26:"HTML.AllowedCommentsRegexp";N;s:20:"HTML.AllowedElements";N;s:19:"HTML.AllowedModules";N;s:23:"HTML.Attr.Name.UseCDATA";b:0;s:17:"HTML.BlockWrapper";s:1:"p";s:16:"HTML.CoreModules";a:7:{s:9:"Structure";b:1;s:4:"Text";b:1;s:9:"Hypertext";b:1;s:4:"List";b:1;s:22:"NonXMLCommonAttributes";b:1;s:19:"XMLCommonAttributes";b:1;s:16:"CommonAttributes";b:1;}s:18:"HTML.CustomDoctype";N;s:17:"HTML.DefinitionID";N;s:18:"HTML.DefinitionRev";i:1;s:12:"HTML.Doctype";N;s:25:"HTML.FlashAllowFullScreen";b:0;s:24:"HTML.ForbiddenAttributes";a:0:{}s:22:"HTML.ForbiddenElements";a:0:{}s:10:"HTML.Forms";b:0;s:17:"HTML.MaxImgLength";i:1200;s:13:"HTML.Nofollow";b:0;s:11:"HTML.Parent";s:3:"div";s:16:"HTML.Proprietary";b:0;s:14:"HTML.SafeEmbed";b:0;s:15:"HTML.SafeIframe";b:0;s:15:"HTML.SafeObject";b:0;s:18:"HTML.SafeScripting";a:0:{}s:11:"HTML.Strict";b:0;s:16:"HTML.TargetBlank";b:0;s:19:"HTML.TargetNoopener";b:1;s:21:"HTML.TargetNoreferrer";b:1;s:12:"HTML.TidyAdd";a:0:{}s:14:"HTML.TidyLevel";s:6:"medium";s:15:"HTML.TidyRemove";a:0:{}s:12:"HTML.Trusted";b:0;s:10:"HTML.XHTML";b:1;s:28:"Output.CommentScriptContents";b:1;s:19:"Output.FixInnerHTML";b:1;s:18:"Output.FlashCompat";b:0;s:14:"Output.Newline";N;s:15:"Output.SortAttr";b:0;s:17:"Output.TidyFormat";b:0;s:17:"Test.ForceNoIconv";b:0;s:18:"URI.AllowedSchemes";a:7:{s:4:"http";b:1;s:5:"https";b:1;s:6:"mailto";b:1;s:3:"ftp";b:1;s:4:"nntp";b:1;s:4:"news";b:1;s:3:"tel";b:1;}s:8:"URI.Base";N;s:17:"URI.DefaultScheme";s:4:"http";s:16:"URI.DefinitionID";N;s:17:"URI.DefinitionRev";i:1;s:11:"URI.Disable";b:0;s:19:"URI.DisableExternal";b:0;s:28:"URI.DisableExternalResources";b:0;s:20:"URI.DisableResources";b:0;s:8:"URI.Host";N;s:17:"URI.HostBlacklist";a:0:{}s:16:"URI.MakeAbsolute";b:0;s:9:"URI.Munge";N;s:18:"URI.MungeResources";b:0;s:18:"URI.MungeSecretKey";N;s:26:"URI.OverrideAllowedSchemes";b:1;s:20:"URI.SafeIframeRegexp";N;}s:9:" * parent";N;s:8:" * cache";N;}s:4:"info";a:140:{s:19:"Attr.AllowedClasses";i:-8;s:24:"Attr.AllowedFrameTargets";i:8;s:15:"Attr.AllowedRel";i:8;s:15:"Attr.AllowedRev";i:8;s:18:"Attr.ClassUseCDATA";i:-7;s:20:"Attr.DefaultImageAlt";i:-1;s:24:"Attr.DefaultInvalidImage";i:1;s:27:"Attr.DefaultInvalidImageAlt";i:1;s:19:"Attr.DefaultTextDir";O:8:"stdClass":2:{s:4:"type";i:1;s:7:"allowed";a:2:{s:3:"ltr";b:1;s:3:"rtl";b:1;}}s:13:"Attr.EnableID";i:7;s:17:"HTML.EnableAttrID";O:8:"stdClass":2:{s:3:"key";s:13:"Attr.EnableID";s:7:"isAlias";b:1;}s:21:"Attr.ForbiddenClasses";i:8;s:13:"Attr.ID.HTML5";i:-7;s:16:"Attr.IDBlacklist";i:9;s:22:"Attr.IDBlacklistRegexp";i:-1;s:13:"Attr.IDPrefix";i:1;s:18:"Attr.IDPrefixLocal";i:1;s:24:"AutoFormat.AutoParagraph";i:7;s:17:"AutoFormat.Custom";i:9;s:25:"AutoFormat.DisplayLinkURI";i:7;s:18:"AutoFormat.Linkify";i:7;s:33:"AutoFormat.PurifierLinkify.DocURL";i:1;s:37:"AutoFormatParam.PurifierLinkifyDocURL";O:8:"stdClass":2:{s:3:"key";s:33:"AutoFormat.PurifierLinkify.DocURL";s:7:"isAlias";b:1;}s:26:"AutoFormat.PurifierLinkify";i:7;s:32:"AutoFormat.RemoveEmpty.Predicate";i:10;s:44:"AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions";i:8;s:33:"AutoFormat.RemoveEmpty.RemoveNbsp";i:7;s:22:"AutoFormat.RemoveEmpty";i:7;s:39:"AutoFormat.RemoveSpansWithoutAttributes";i:7;s:19:"CSS.AllowDuplicates";i:7;s:18:"CSS.AllowImportant";i:7;s:15:"CSS.AllowTricky";i:7;s:16:"CSS.AllowedFonts";i:-8;s:21:"CSS.AllowedProperties";i:-8;s:17:"CSS.DefinitionRev";i:5;s:23:"CSS.ForbiddenProperties";i:8;s:16:"CSS.MaxImgLength";i:-1;s:15:"CSS.Proprietary";i:7;s:11:"CSS.Trusted";i:7;s:20:"Cache.DefinitionImpl";i:-1;s:20:"Core.DefinitionCache";O:8:"stdClass":2:{s:3:"key";s:20:"Cache.DefinitionImpl";s:7:"isAlias";b:1;}s:20:"Cache.SerializerPath";i:-1;s:27:"Cache.SerializerPermissions";i:-5;s:22:"Core.AggressivelyFixLt";i:7;s:29:"Core.AggressivelyRemoveScript";i:7;s:28:"Core.AllowHostnameUnderscore";i:7;s:23:"Core.AllowParseManyTags";i:7;s:18:"Core.CollectErrors";i:7;s:18:"Core.ColorKeywords";i:10;s:30:"Core.ConvertDocumentToFragment";i:7;s:24:"Core.AcceptFullDocuments";O:8:"stdClass":2:{s:3:"key";s:30:"Core.ConvertDocumentToFragment";s:7:"isAlias";b:1;}s:36:"Core.DirectLexLineNumberSyncInterval";i:5;s:20:"Core.DisableExcludes";i:7;s:15:"Core.EnableIDNA";i:7;s:13:"Core.Encoding";i:2;s:26:"Core.EscapeInvalidChildren";i:7;s:22:"Core.EscapeInvalidTags";i:7;s:29:"Core.EscapeNonASCIICharacters";i:7;s:19:"Core.HiddenElements";i:8;s:13:"Core.Language";i:1;s:24:"Core.LegacyEntityDecoder";i:7;s:14:"Core.LexerImpl";i:-11;s:24:"Core.MaintainLineNumbers";i:-7;s:22:"Core.NormalizeNewlines";i:7;s:21:"Core.RemoveInvalidImg";i:7;s:33:"Core.RemoveProcessingInstructions";i:7;s:25:"Core.RemoveScriptContents";i:-7;s:13:"Filter.Custom";i:9;s:34:"Filter.ExtractStyleBlocks.Escaping";i:7;s:33:"Filter.ExtractStyleBlocksEscaping";O:8:"stdClass":2:{s:3:"key";s:34:"Filter.ExtractStyleBlocks.Escaping";s:7:"isAlias";b:1;}s:38:"FilterParam.ExtractStyleBlocksEscaping";O:8:"stdClass":2:{s:3:"key";s:34:"Filter.ExtractStyleBlocks.Escaping";s:7:"isAlias";b:1;}s:31:"Filter.ExtractStyleBlocks.Scope";i:-1;s:30:"Filter.ExtractStyleBlocksScope";O:8:"stdClass":2:{s:3:"key";s:31:"Filter.ExtractStyleBlocks.Scope";s:7:"isAlias";b:1;}s:35:"FilterParam.ExtractStyleBlocksScope";O:8:"stdClass":2:{s:3:"key";s:31:"Filter.ExtractStyleBlocks.Scope";s:7:"isAlias";b:1;}s:34:"Filter.ExtractStyleBlocks.TidyImpl";i:-11;s:38:"FilterParam.ExtractStyleBlocksTidyImpl";O:8:"stdClass":2:{s:3:"key";s:34:"Filter.ExtractStyleBlocks.TidyImpl";s:7:"isAlias";b:1;}s:25:"Filter.ExtractStyleBlocks";i:7;s:14:"Filter.YouTube";i:7;s:12:"HTML.Allowed";i:-4;s:22:"HTML.AllowedAttributes";i:-8;s:20:"HTML.AllowedComments";i:8;s:26:"HTML.AllowedCommentsRegexp";i:-1;s:20:"HTML.AllowedElements";i:-8;s:19:"HTML.AllowedModules";i:-8;s:23:"HTML.Attr.Name.UseCDATA";i:7;s:17:"HTML.BlockWrapper";i:1;s:16:"HTML.CoreModules";i:8;s:18:"HTML.CustomDoctype";i:-1;s:17:"HTML.DefinitionID";i:-1;s:18:"HTML.DefinitionRev";i:5;s:12:"HTML.Doctype";O:8:"stdClass":3:{s:4:"type";i:1;s:10:"allow_null";b:1;s:7:"allowed";a:5:{s:22:"HTML 4.01 Transitional";b:1;s:16:"HTML 4.01 Strict";b:1;s:22:"XHTML 1.0 Transitional";b:1;s:16:"XHTML 1.0 Strict";b:1;s:9:"XHTML 1.1";b:1;}}s:25:"HTML.FlashAllowFullScreen";i:7;s:24:"HTML.ForbiddenAttributes";i:8;s:22:"HTML.ForbiddenElements";i:8;s:10:"HTML.Forms";i:7;s:17:"HTML.MaxImgLength";i:-5;s:13:"HTML.Nofollow";i:7;s:11:"HTML.Parent";i:1;s:16:"HTML.Proprietary";i:7;s:14:"HTML.SafeEmbed";i:7;s:15:"HTML.SafeIframe";i:7;s:15:"HTML.SafeObject";i:7;s:18:"HTML.SafeScripting";i:8;s:11:"HTML.Strict";i:7;s:16:"HTML.TargetBlank";i:7;s:19:"HTML.TargetNoopener";i:7;s:21:"HTML.TargetNoreferrer";i:7;s:12:"HTML.TidyAdd";i:8;s:14:"HTML.TidyLevel";O:8:"stdClass":2:{s:4:"type";i:1;s:7:"allowed";a:4:{s:4:"none";b:1;s:5:"light";b:1;s:6:"medium";b:1;s:5:"heavy";b:1;}}s:15:"HTML.TidyRemove";i:8;s:12:"HTML.Trusted";i:7;s:10:"HTML.XHTML";i:7;s:10:"Core.XHTML";O:8:"stdClass":2:{s:3:"key";s:10:"HTML.XHTML";s:7:"isAlias";b:1;}s:28:"Output.CommentScriptContents";i:7;s:26:"Core.CommentScriptContents";O:8:"stdClass":2:{s:3:"key";s:28:"Output.CommentScriptContents";s:7:"isAlias";b:1;}s:19:"Output.FixInnerHTML";i:7;s:18:"Output.FlashCompat";i:7;s:14:"Output.Newline";i:-1;s:15:"Output.SortAttr";i:7;s:17:"Output.TidyFormat";i:7;s:15:"Core.TidyFormat";O:8:"stdClass":2:{s:3:"key";s:17:"Output.TidyFormat";s:7:"isAlias";b:1;}s:17:"Test.ForceNoIconv";i:7;s:18:"URI.AllowedSchemes";i:8;s:8:"URI.Base";i:-1;s:17:"URI.DefaultScheme";i:-1;s:16:"URI.DefinitionID";i:-1;s:17:"URI.DefinitionRev";i:5;s:11:"URI.Disable";i:7;s:15:"Attr.DisableURI";O:8:"stdClass":2:{s:3:"key";s:11:"URI.Disable";s:7:"isAlias";b:1;}s:19:"URI.DisableExternal";i:7;s:28:"URI.DisableExternalResources";i:7;s:20:"URI.DisableResources";i:7;s:8:"URI.Host";i:-1;s:17:"URI.HostBlacklist";i:9;s:16:"URI.MakeAbsolute";i:7;s:9:"URI.Munge";i:-1;s:18:"URI.MungeResources";i:7;s:18:"URI.MungeSecretKey";i:-1;s:26:"URI.OverrideAllowedSchemes";i:7;s:20:"URI.SafeIframeRegexp";i:-1;}}
\ No newline at end of file
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt
new file mode 100644
index 000000000..0517fed0a
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt
@@ -0,0 +1,8 @@
+Attr.AllowedClasses
+TYPE: lookup/null
+VERSION: 4.0.0
+DEFAULT: null
+--DESCRIPTION--
+List of allowed class values in the class attribute. By default, this is null,
+which means all classes are allowed.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt
new file mode 100644
index 000000000..249edd647
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt
@@ -0,0 +1,12 @@
+Attr.AllowedFrameTargets
+TYPE: lookup
+DEFAULT: array()
+--DESCRIPTION--
+Lookup table of all allowed link frame targets. Some commonly used link
+targets include _blank, _self, _parent and _top. Values should be
+lowercase, as validation will be done in a case-sensitive manner despite
+W3C's recommendation. XHTML 1.0 Strict does not permit the target attribute
+so this directive will have no effect in that doctype. XHTML 1.1 does not
+enable the Target module by default, you will have to manually enable it
+(see the module documentation for more details.)
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt
new file mode 100644
index 000000000..9a8fa6a2e
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt
@@ -0,0 +1,9 @@
+Attr.AllowedRel
+TYPE: lookup
+VERSION: 1.6.0
+DEFAULT: array()
+--DESCRIPTION--
+List of allowed forward document relationships in the rel attribute. Common
+values may be nofollow or print. By default, this is empty, meaning that no
+document relationships are allowed.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt
new file mode 100644
index 000000000..b01788348
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt
@@ -0,0 +1,9 @@
+Attr.AllowedRev
+TYPE: lookup
+VERSION: 1.6.0
+DEFAULT: array()
+--DESCRIPTION--
+List of allowed reverse document relationships in the rev attribute. This
+attribute is a bit of an edge-case; if you don't know what it is for, stay
+away.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt
new file mode 100644
index 000000000..e774b823b
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt
@@ -0,0 +1,19 @@
+Attr.ClassUseCDATA
+TYPE: bool/null
+DEFAULT: null
+VERSION: 4.0.0
+--DESCRIPTION--
+If null, class will auto-detect the doctype and, if matching XHTML 1.1 or
+XHTML 2.0, will use the restrictive NMTOKENS specification of class. Otherwise,
+it will use a relaxed CDATA definition. If true, the relaxed CDATA definition
+is forced; if false, the NMTOKENS definition is forced. To get behavior
+of HTML Purifier prior to 4.0.0, set this directive to false.
+
+Some rational behind the auto-detection:
+in previous versions of HTML Purifier, it was assumed that the form of
+class was NMTOKENS, as specified by the XHTML Modularization (representing
+XHTML 1.1 and XHTML 2.0). The DTDs for HTML 4.01 and XHTML 1.0, however
+specify class as CDATA. HTML 5 effectively defines it as CDATA, but
+with the additional constraint that each name should be unique (this is not
+explicitly outlined in previous specifications).
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt
new file mode 100644
index 000000000..533165e17
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt
@@ -0,0 +1,11 @@
+Attr.DefaultImageAlt
+TYPE: string/null
+DEFAULT: null
+VERSION: 3.2.0
+--DESCRIPTION--
+This is the content of the alt tag of an image if the user had not
+previously specified an alt attribute. This applies to all images without
+a valid alt attribute, as opposed to %Attr.DefaultInvalidImageAlt, which
+only applies to invalid images, and overrides in the case of an invalid image.
+Default behavior with null is to use the basename of the src tag for the alt.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt
new file mode 100644
index 000000000..9eb7e3846
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt
@@ -0,0 +1,9 @@
+Attr.DefaultInvalidImage
+TYPE: string
+DEFAULT: ''
+--DESCRIPTION--
+This is the default image an img tag will be pointed to if it does not have
+a valid src attribute. In future versions, we may allow the image tag to
+be removed completely, but due to design issues, this is not possible right
+now.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt
new file mode 100644
index 000000000..2f17bf477
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt
@@ -0,0 +1,8 @@
+Attr.DefaultInvalidImageAlt
+TYPE: string
+DEFAULT: 'Invalid image'
+--DESCRIPTION--
+This is the content of the alt tag of an invalid image if the user had not
+previously specified an alt attribute. It has no effect when the image is
+valid but there was no alt attribute present.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt
new file mode 100644
index 000000000..52654b53a
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt
@@ -0,0 +1,10 @@
+Attr.DefaultTextDir
+TYPE: string
+DEFAULT: 'ltr'
+--DESCRIPTION--
+Defines the default text direction (ltr or rtl) of the document being
+parsed. This generally is the same as the value of the dir attribute in
+HTML, or ltr if that is not specified.
+--ALLOWED--
+'ltr', 'rtl'
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt
new file mode 100644
index 000000000..6440d2103
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt
@@ -0,0 +1,16 @@
+Attr.EnableID
+TYPE: bool
+DEFAULT: false
+VERSION: 1.2.0
+--DESCRIPTION--
+Allows the ID attribute in HTML. This is disabled by default due to the
+fact that without proper configuration user input can easily break the
+validation of a webpage by specifying an ID that is already on the
+surrounding HTML. If you don't mind throwing caution to the wind, enable
+this directive, but I strongly recommend you also consider blacklisting IDs
+you use (%Attr.IDBlacklist) or prefixing all user supplied IDs
+(%Attr.IDPrefix). When set to true HTML Purifier reverts to the behavior of
+pre-1.2.0 versions.
+--ALIASES--
+HTML.EnableAttrID
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt
new file mode 100644
index 000000000..f31d226f5
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt
@@ -0,0 +1,8 @@
+Attr.ForbiddenClasses
+TYPE: lookup
+VERSION: 4.0.0
+DEFAULT: array()
+--DESCRIPTION--
+List of forbidden class values in the class attribute. By default, this is
+empty, which means that no classes are forbidden. See also %Attr.AllowedClasses.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt
new file mode 100644
index 000000000..735d4b7a1
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ID.HTML5.txt
@@ -0,0 +1,10 @@
+Attr.ID.HTML5
+TYPE: bool/null
+DEFAULT: null
+VERSION: 4.8.0
+--DESCRIPTION--
+In HTML5, restrictions on the format of the id attribute have been significantly
+relaxed, such that any string is valid so long as it contains no spaces and
+is at least one character. In lieu of a general HTML5 compatibility flag,
+set this configuration directive to true to use the relaxed rules.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt
new file mode 100644
index 000000000..5f2b5e3d2
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt
@@ -0,0 +1,5 @@
+Attr.IDBlacklist
+TYPE: list
+DEFAULT: array()
+DESCRIPTION: Array of IDs not allowed in the document.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt
new file mode 100644
index 000000000..6f5824586
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt
@@ -0,0 +1,9 @@
+Attr.IDBlacklistRegexp
+TYPE: string/null
+VERSION: 1.6.0
+DEFAULT: NULL
+--DESCRIPTION--
+PCRE regular expression to be matched against all IDs. If the expression is
+matches, the ID is rejected. Use this with care: may cause significant
+degradation. ID matching is done after all other validation.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt
new file mode 100644
index 000000000..cc49d43fd
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt
@@ -0,0 +1,12 @@
+Attr.IDPrefix
+TYPE: string
+VERSION: 1.2.0
+DEFAULT: ''
+--DESCRIPTION--
+String to prefix to IDs. If you have no idea what IDs your pages may use,
+you may opt to simply add a prefix to all user-submitted ID attributes so
+that they are still usable, but will not conflict with core page IDs.
+Example: setting the directive to 'user_' will result in a user submitted
+'foo' to become 'user_foo' Be sure to set %HTML.EnableAttrID to true
+before using this.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt
new file mode 100644
index 000000000..2c5924a7a
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt
@@ -0,0 +1,14 @@
+Attr.IDPrefixLocal
+TYPE: string
+VERSION: 1.2.0
+DEFAULT: ''
+--DESCRIPTION--
+Temporary prefix for IDs used in conjunction with %Attr.IDPrefix. If you
+need to allow multiple sets of user content on web page, you may need to
+have a seperate prefix that changes with each iteration. This way,
+seperately submitted user content displayed on the same page doesn't
+clobber each other. Ideal values are unique identifiers for the content it
+represents (i.e. the id of the row in the database). Be sure to add a
+seperator (like an underscore) at the end. Warning: this directive will
+not work unless %Attr.IDPrefix is set to a non-empty value!
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt
new file mode 100644
index 000000000..d5caa1bb9
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt
@@ -0,0 +1,31 @@
+AutoFormat.AutoParagraph
+TYPE: bool
+VERSION: 2.0.1
+DEFAULT: false
+--DESCRIPTION--
+
+
+
+p
tags must be allowed for this directive to take effect.
+ We do not use br
tags for paragraphing, as that is
+ semantically incorrect.
+pre
, so this should not be difficult.) To prevent
+ the paragraphing of inline text adjacent to block elements, wrap them
+ in div
tags (the behavior is slightly different outside of
+ the root node.)
+a
tags with the href
attribute
+ must be allowed.
+a
tags
+ with the href
attribute must be allowed.
+colgroup
,
+ th
and td
, and also iframe
if it
+ has a src
.
+
+<a></a>
but not
+ <br />
), and
+
+
colgroup
element, orid
or name
attribute,
+ when those attributes are permitted on those elements.
+ span
tags without any attributes
+ to be removed. It will also remove spans that had all attributes
+ removed during processing.
+color:red; color:blue
. If this is set to
+ true, duplicate properties are allowed.
+display:none;
is considered a tricky property that
+will only be allowed if this directive is set to true.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt
new file mode 100644
index 000000000..3fd465406
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt
@@ -0,0 +1,12 @@
+CSS.AllowedFonts
+TYPE: lookup/null
+VERSION: 4.3.0
+DEFAULT: NULL
+--DESCRIPTION--
+NULL
, all fonts are allowed. This directive
+ affects generic names (serif, sans-serif, monospace, cursive,
+ fantasy) as well as specific font families.
+img
tags,
+ effectively the width
and height
properties.
+ Only absolute units of measurement (in, pt, pc, mm, cm) and pixels (px) are allowed. This is
+ in place to prevent imagecrash attacks, disable with null at your own risk.
+ This directive is similar to %HTML.MaxImgLength, and both should be
+ concurrently edited, although there are
+ subtle differences in the input format (the CSS max is a number with
+ a unit).
+NULL
,
+ which means that no chmod'ing or directory creation shall
+ occur.
+<object>
in any descendant of a
+ <pre>
tag. Disabling excludes will allow some
+ invalid documents to pass through HTML Purifier, but HTML Purifier
+ will also be less likely to accidentally remove large documents during
+ processing.
+script
tag are not
+ normally shown in a document, so if script tags are to be removed,
+ their contents should be removed to. This is opposed to a b
+ tag, which defines some presentational changes but does not hide its
+ contents.
+
+
+
+Original Text Attribute
+¥ ¥ ¥
+¥ ¥ ¥
+¥a ¥a ¥a
+¥= ¥= ¥=
+
+
+Original Text Attribute
+¥ ¥ ¥
+¥ ¥ ¥
+¥a ¥a ¥a
+¥= ¥= ¥=
+
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt
new file mode 100644
index 000000000..eb841a759
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt
@@ -0,0 +1,16 @@
+Core.MaintainLineNumbers
+TYPE: bool/null
+VERSION: 2.0.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+HTMLPurifier_Lexer
.
+ I may remove this option simply because I don't expect anyone
+ to use it.
+ false
, HTML Purifier
+ will attempt to preserve mixed newline files.
+img
+ tags, as the attribute validation strategy is not authorized to
+ remove elements from the document. Revert to pre-1.3.0 behavior by setting to false.
+<? ...
+?>
, remove it out-right. This may be useful if the HTML
+you are validating contains XML processing instruction gunk, however,
+it can also be user-unfriendly for people attempting to post PHP
+snippets.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt
new file mode 100644
index 000000000..a4cd966df
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt
@@ -0,0 +1,12 @@
+Core.RemoveScriptContents
+TYPE: bool/null
+DEFAULT: NULL
+VERSION: 2.0.0
+DEPRECATED-VERSION: 2.1.0
+DEPRECATED-USE: Core.HiddenElements
+--DESCRIPTION--
+HTMLPurifier->addFilter()
+ method. Specify an array of concrete implementations.
+#user-content
and a user uses the
+ selector a:hover
, the final selector will be
+ #user-content a:hover
.
+#user-content, #user-content2
, the final selector will
+ be #user-content a:hover, #user-content2 a:hover
.
+csstidy
+ class to use for internal cleaning. This will usually be good enough.
+false
to
+ disable cleaning. In addition, you can supply your own concrete implementation
+ of Tidy's interface to use, although I don't know why you'd want to do that.
+style
blocks from input HTML, cleans them up with CSSTidy,
+ and places them in the StyleBlocks
context variable, for further
+ use by you, usually to be placed in an external stylesheet, or a
+ style
block in the head
of your document.
+';
+?>
+
+
+
+
+element1[attr1|attr2],element2...
. For example,
+ if you would like to only allow paragraphs and links, specify
+ a[href],p
. You can specify attributes that apply
+ to all elements using an asterisk, e.g. *[lang]
.
+ You can also use newlines instead of commas to separate elements.
+valid_elements
+ whitelist: directly copy-pasting it here will probably result in
+ broken whitelists. If %HTML.AllowedElements or %HTML.AllowedAttributes
+ are set, this directive has no effect.
+^regex$
, otherwise you may accept
+comments that you did not mean to! In particular, the regex /foo|bar/
+is probably not sufficiently strict, since it also allows foobar
.
+See also %HTML.AllowedComments (these directives are union'ed together,
+so a comment is considered valid if any directive deems it valid.)
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt
new file mode 100644
index 000000000..1d3fa7907
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt
@@ -0,0 +1,23 @@
+HTML.AllowedElements
+TYPE: lookup/null
+VERSION: 1.3.0
+DEFAULT: NULL
+--DESCRIPTION--
+<blockquote>Foo</blockquote>
would become
+ <blockquote><p>Foo</p></blockquote>
.
+ The <p>
tags can be replaced with whatever you desire,
+ as long as it is a block level element.
+
+$config = HTMLPurifier_Config::createDefault();
+$config->set('HTML', 'DefinitionID', '1');
+$def = $config->getHTMLDefinition();
+$def->addAttribute('a', 'tabindex', 'Number');
+
+allowFullScreen
parameter.
+tag.attr
, use tag@attr
. To disallow href
+ attributes in a
tags, set this directive to
+ a@href
. You can also disallow an attribute globally with
+ attr
or *@attr
(either syntax is fine; the latter
+ is provided for consistency with %HTML.AllowedAttributes).
+img
+ with the expectation of preventing images on your site, you'll be in for
+ a nasty surprise when people start using the background-image
+ CSS property.
+img
tags. This is
+ in place to prevent imagecrash attacks, disable with null at your own risk.
+ This directive is similar to %CSS.MaxImgLength, and both should be
+ concurrently edited, although there are
+ subtle differences in the input format (the HTML max is an integer).
+HTMLPurifier_HTMLModule_Proprietary
.
+ Warning: This can cause your documents to stop
+ validating!
+target=blank
attributes are added to all outgoing links.
+(This includes links from an HTTPS version of a page to an HTTP version.)
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt
new file mode 100644
index 000000000..dd514c0de
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoopener.txt
@@ -0,0 +1,10 @@
+--# vim: et sw=4 sts=4
+HTML.TargetNoopener
+TYPE: bool
+VERSION: 4.8.0
+DEFAULT: TRUE
+--DESCRIPTION--
+If enabled, noopener rel attributes are added to links which have
+a target attribute associated with them. This prevents malicious
+destinations from overwriting the original window.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt
new file mode 100644
index 000000000..cb5a0b0e5
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetNoreferrer.txt
@@ -0,0 +1,9 @@
+HTML.TargetNoreferrer
+TYPE: bool
+VERSION: 4.8.0
+DEFAULT: TRUE
+--DESCRIPTION--
+If enabled, noreferrer rel attributes are added to links which have
+a target attribute associated with them. This prevents malicious
+destinations from overwriting the original window.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt
new file mode 100644
index 000000000..b4c271b7f
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt
@@ -0,0 +1,8 @@
+HTML.TidyAdd
+TYPE: lookup
+VERSION: 2.0.0
+DEFAULT: array()
+--DESCRIPTION--
+
+Fixes to add to the default set of Tidy fixes as per your level.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt
new file mode 100644
index 000000000..4186ccd0d
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt
@@ -0,0 +1,24 @@
+HTML.TidyLevel
+TYPE: string
+VERSION: 2.0.0
+DEFAULT: 'medium'
+--DESCRIPTION--
+
+
+
+
+--ALLOWED--
+'none', 'light', 'medium', 'heavy'
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt
new file mode 100644
index 000000000..996762bd1
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt
@@ -0,0 +1,8 @@
+HTML.TidyRemove
+TYPE: lookup
+VERSION: 2.0.0
+DEFAULT: array()
+--DESCRIPTION--
+
+Fixes to remove from the default set of Tidy fixes as per your level.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt
new file mode 100644
index 000000000..1db9237e9
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt
@@ -0,0 +1,9 @@
+HTML.Trusted
+TYPE: bool
+VERSION: 2.0.0
+DEFAULT: false
+--DESCRIPTION--
+Indicates whether or not the user input is trusted or not. If the input is
+trusted, a more expansive set of allowed tags and attributes will be used.
+See also %CSS.Trusted.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt
new file mode 100644
index 000000000..2a47e384f
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt
@@ -0,0 +1,11 @@
+HTML.XHTML
+TYPE: bool
+DEFAULT: true
+VERSION: 1.1.0
+DEPRECATED-VERSION: 1.7.0
+DEPRECATED-USE: HTML.Doctype
+--DESCRIPTION--
+Determines whether or not output is XHTML 1.0 or HTML 4.01 flavor.
+--ALIASES--
+Core.XHTML
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt
new file mode 100644
index 000000000..08921fde7
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt
@@ -0,0 +1,10 @@
+Output.CommentScriptContents
+TYPE: bool
+VERSION: 2.0.0
+DEFAULT: true
+--DESCRIPTION--
+Determines whether or not HTML Purifier should attempt to fix up the
+contents of script tags for legacy browsers with comments.
+--ALIASES--
+Core.CommentScriptContents
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt
new file mode 100644
index 000000000..d6f0d9f29
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt
@@ -0,0 +1,15 @@
+Output.FixInnerHTML
+TYPE: bool
+VERSION: 4.3.0
+DEFAULT: true
+--DESCRIPTION--
+innerHTML
attribute by appending
+ a space to any attribute that does not contain angled brackets, spaces
+ or quotes, but contains a backtick. This slightly changes the
+ semantics of any given attribute, so if this is unacceptable and
+ you do not use innerHTML
on any of your pages, you can
+ turn this directive off.
+<el b="" a="" c="" />
+ to <el a="" b="" c="" />
. This is a workaround for
+ a bug in FCKeditor which causes it to swap attributes order, adding noise
+ to text diffs. If you're not seeing this bug, chances are, you don't need
+ this directive.
+data
and file
+URI schemes, but they are not enabled by default.
+--# vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt
new file mode 100644
index 000000000..876f0680c
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt
@@ -0,0 +1,17 @@
+URI.Base
+TYPE: string/null
+VERSION: 2.1.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+%s
where
+ the url-encoded original URI should be inserted (sample:
+ http://www.google.com/url?q=%s
).
+
+
+meta
tags. To revert to
+ previous behavior, please use %URI.MungeResources.
+
+
+
+
+
+
+
+ Key
+ Description
+ Example
+ <a href="">
+
+ %r
+ 1 - The URI embeds a resource
+
(blank) - The URI is merely a link
+
+
+ %n
+ The name of the tag this URI came from
+ a
+
+
+ %m
+ The name of the attribute this URI came from
+ href
+
+
+
+%p
+ The name of the CSS property this URI came from, or blank if irrelevant
+
+ <img src="">
.
+ Be careful enabling this directive if you have a redirector script
+ that does not use the Location
HTTP header; all of your images
+ and other embedded resources will break.
+$checksum === hash_hmac("sha256", $url, $secret_key)
+
+
+
+%^http://www.youtube.com/embed/%
- Allow YouTube videos%^http://player.vimeo.com/video/%
- Allow Vimeo videos%^http://(www.youtube.com/embed/|player.vimeo.com/video/)%
- Allow bothautoplay
videos. Pipe up on the HTML Purifier forums if this
+ is a capability you want.
+
';
+ }
+
+ }
+
+ private function _renderStruct(&$ret, $struct, $line = null, $col = null)
+ {
+ $stack = array($struct);
+ $context_stack = array(array());
+ while ($current = array_pop($stack)) {
+ $context = array_pop($context_stack);
+ foreach ($current->errors as $error) {
+ list($severity, $msg) = $error;
+ $string = '';
+ $string .= '';
+ //$string .= '';
+ //$string .= '
';
+ $ret[] = $string;
+ }
+ foreach ($current->children as $array) {
+ $context[] = $current;
+ $stack = array_merge($stack, array_reverse($array, true));
+ for ($i = count($array); $i > 0; $i--) {
+ $context_stack[] = $context;
+ }
+ }
+ }
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php
new file mode 100644
index 000000000..cf869d321
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php
@@ -0,0 +1,74 @@
+children[$type][$id])) {
+ $this->children[$type][$id] = new HTMLPurifier_ErrorStruct();
+ $this->children[$type][$id]->type = $type;
+ }
+ return $this->children[$type][$id];
+ }
+
+ /**
+ * @param int $severity
+ * @param string $message
+ */
+ public function addError($severity, $message)
+ {
+ $this->errors[] = array($severity, $message);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php
new file mode 100644
index 000000000..be85b4c56
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php
@@ -0,0 +1,12 @@
+preFilter,
+ * 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter,
+ * 1->postFilter.
+ *
+ * @note Methods are not declared abstract as it is perfectly legitimate
+ * for an implementation not to want anything to happen on a step
+ */
+
+class HTMLPurifier_Filter
+{
+
+ /**
+ * Name of the filter for identification purposes.
+ * @type string
+ */
+ public $name;
+
+ /**
+ * Pre-processor function, handles HTML before HTML Purifier
+ * @param string $html
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return string
+ */
+ public function preFilter($html, $config, $context)
+ {
+ return $html;
+ }
+
+ /**
+ * Post-processor function, handles HTML after HTML Purifier
+ * @param string $html
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return string
+ */
+ public function postFilter($html, $config, $context)
+ {
+ return $html;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php
new file mode 100644
index 000000000..6f8e7790e
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php
@@ -0,0 +1,345 @@
+ blocks from input HTML, cleans them up
+ * using CSSTidy, and then places them in $purifier->context->get('StyleBlocks')
+ * so they can be used elsewhere in the document.
+ *
+ * @note
+ * See tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php for
+ * sample usage.
+ *
+ * @note
+ * This filter can also be used on stylesheets not included in the
+ * document--something purists would probably prefer. Just directly
+ * call HTMLPurifier_Filter_ExtractStyleBlocks->cleanCSS()
+ */
+class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter
+{
+ /**
+ * @type string
+ */
+ public $name = 'ExtractStyleBlocks';
+
+ /**
+ * @type array
+ */
+ private $_styleMatches = array();
+
+ /**
+ * @type csstidy
+ */
+ private $_tidy;
+
+ /**
+ * @type HTMLPurifier_AttrDef_HTML_ID
+ */
+ private $_id_attrdef;
+
+ /**
+ * @type HTMLPurifier_AttrDef_CSS_Ident
+ */
+ private $_class_attrdef;
+
+ /**
+ * @type HTMLPurifier_AttrDef_Enum
+ */
+ private $_enum_attrdef;
+
+ public function __construct()
+ {
+ $this->_tidy = new csstidy();
+ $this->_tidy->set_cfg('lowercase_s', false);
+ $this->_id_attrdef = new HTMLPurifier_AttrDef_HTML_ID(true);
+ $this->_class_attrdef = new HTMLPurifier_AttrDef_CSS_Ident();
+ $this->_enum_attrdef = new HTMLPurifier_AttrDef_Enum(
+ array(
+ 'first-child',
+ 'link',
+ 'visited',
+ 'active',
+ 'hover',
+ 'focus'
+ )
+ );
+ }
+
+ /**
+ * Save the contents of CSS blocks to style matches
+ * @param array $matches preg_replace style $matches array
+ */
+ protected function styleCallback($matches)
+ {
+ $this->_styleMatches[] = $matches[1];
+ }
+
+ /**
+ * Removes inline
+ // we must not grab foo in a font-family prop).
+ if ($config->get('Filter.ExtractStyleBlocks.Escaping')) {
+ $css = str_replace(
+ array('<', '>', '&'),
+ array('\3C ', '\3E ', '\26 '),
+ $css
+ );
+ }
+ return $css;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php
new file mode 100644
index 000000000..276d8362f
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php
@@ -0,0 +1,65 @@
+]+>.+?' .
+ '(?:http:)?//www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?#s';
+ $pre_replace = ' ';
+ return preg_replace($pre_regex, $pre_replace, $html);
+ }
+
+ /**
+ * @param string $html
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return string
+ */
+ public function postFilter($html, $config, $context)
+ {
+ $post_regex = '# #';
+ return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html);
+ }
+
+ /**
+ * @param $url
+ * @return string
+ */
+ protected function armorUrl($url)
+ {
+ return str_replace('--', '--', $url);
+ }
+
+ /**
+ * @param array $matches
+ * @return string
+ */
+ protected function postFilterCallback($matches)
+ {
+ $url = $this->armorUrl($matches[1]);
+ return '';
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php
new file mode 100644
index 000000000..eb56e2dfa
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php
@@ -0,0 +1,286 @@
+ tags.
+ * @type bool
+ */
+ private $_scriptFix = false;
+
+ /**
+ * Cache of HTMLDefinition during HTML output to determine whether or
+ * not attributes should be minimized.
+ * @type HTMLPurifier_HTMLDefinition
+ */
+ private $_def;
+
+ /**
+ * Cache of %Output.SortAttr.
+ * @type bool
+ */
+ private $_sortAttr;
+
+ /**
+ * Cache of %Output.FlashCompat.
+ * @type bool
+ */
+ private $_flashCompat;
+
+ /**
+ * Cache of %Output.FixInnerHTML.
+ * @type bool
+ */
+ private $_innerHTMLFix;
+
+ /**
+ * Stack for keeping track of object information when outputting IE
+ * compatibility code.
+ * @type array
+ */
+ private $_flashStack = array();
+
+ /**
+ * Configuration for the generator
+ * @type HTMLPurifier_Config
+ */
+ protected $config;
+
+ /**
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ */
+ public function __construct($config, $context)
+ {
+ $this->config = $config;
+ $this->_scriptFix = $config->get('Output.CommentScriptContents');
+ $this->_innerHTMLFix = $config->get('Output.FixInnerHTML');
+ $this->_sortAttr = $config->get('Output.SortAttr');
+ $this->_flashCompat = $config->get('Output.FlashCompat');
+ $this->_def = $config->getHTMLDefinition();
+ $this->_xhtml = $this->_def->doctype->xml;
+ }
+
+ /**
+ * Generates HTML from an array of tokens.
+ * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token
+ * @return string Generated HTML
+ */
+ public function generateFromTokens($tokens)
+ {
+ if (!$tokens) {
+ return '';
+ }
+
+ // Basic algorithm
+ $html = '';
+ for ($i = 0, $size = count($tokens); $i < $size; $i++) {
+ if ($this->_scriptFix && $tokens[$i]->name === 'script'
+ && $i + 2 < $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) {
+ // script special case
+ // the contents of the script block must be ONE token
+ // for this to work.
+ $html .= $this->generateFromToken($tokens[$i++]);
+ $html .= $this->generateScriptFromToken($tokens[$i++]);
+ }
+ $html .= $this->generateFromToken($tokens[$i]);
+ }
+
+ // Tidy cleanup
+ if (extension_loaded('tidy') && $this->config->get('Output.TidyFormat')) {
+ $tidy = new Tidy;
+ $tidy->parseString(
+ $html,
+ array(
+ 'indent'=> true,
+ 'output-xhtml' => $this->_xhtml,
+ 'show-body-only' => true,
+ 'indent-spaces' => 2,
+ 'wrap' => 68,
+ ),
+ 'utf8'
+ );
+ $tidy->cleanRepair();
+ $html = (string) $tidy; // explicit cast necessary
+ }
+
+ // Normalize newlines to system defined value
+ if ($this->config->get('Core.NormalizeNewlines')) {
+ $nl = $this->config->get('Output.Newline');
+ if ($nl === null) {
+ $nl = PHP_EOL;
+ }
+ if ($nl !== "\n") {
+ $html = str_replace("\n", $nl, $html);
+ }
+ }
+ return $html;
+ }
+
+ /**
+ * Generates HTML from a single token.
+ * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
+ * @return string Generated HTML
+ */
+ public function generateFromToken($token)
+ {
+ if (!$token instanceof HTMLPurifier_Token) {
+ trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING);
+ return '';
+
+ } elseif ($token instanceof HTMLPurifier_Token_Start) {
+ $attr = $this->generateAttributes($token->attr, $token->name);
+ if ($this->_flashCompat) {
+ if ($token->name == "object") {
+ $flash = new stdClass();
+ $flash->attr = $token->attr;
+ $flash->param = array();
+ $this->_flashStack[] = $flash;
+ }
+ }
+ return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>';
+
+ } elseif ($token instanceof HTMLPurifier_Token_End) {
+ $_extra = '';
+ if ($this->_flashCompat) {
+ if ($token->name == "object" && !empty($this->_flashStack)) {
+ // doesn't do anything for now
+ }
+ }
+ return $_extra . '' . $token->name . '>';
+
+ } elseif ($token instanceof HTMLPurifier_Token_Empty) {
+ if ($this->_flashCompat && $token->name == "param" && !empty($this->_flashStack)) {
+ $this->_flashStack[count($this->_flashStack)-1]->param[$token->attr['name']] = $token->attr['value'];
+ }
+ $attr = $this->generateAttributes($token->attr, $token->name);
+ return '<' . $token->name . ($attr ? ' ' : '') . $attr .
+ ( $this->_xhtml ? ' /': '' ) //
v.
+ . '>';
+
+ } elseif ($token instanceof HTMLPurifier_Token_Text) {
+ return $this->escape($token->data, ENT_NOQUOTES);
+
+ } elseif ($token instanceof HTMLPurifier_Token_Comment) {
+ return '';
+ } else {
+ return '';
+
+ }
+ }
+
+ /**
+ * Special case processor for the contents of script tags
+ * @param HTMLPurifier_Token $token HTMLPurifier_Token object.
+ * @return string
+ * @warning This runs into problems if there's already a literal
+ * --> somewhere inside the script contents.
+ */
+ public function generateScriptFromToken($token)
+ {
+ if (!$token instanceof HTMLPurifier_Token_Text) {
+ return $this->generateFromToken($token);
+ }
+ // Thanks
+ // becomes
+ //
+ // becomes
+ //
+ // Fortunately, all we need to do is trigger an appropriate
+ // quoting style, which we do by adding an extra space.
+ // This also is consistent with the W3C spec, which states
+ // that user agents may ignore leading or trailing
+ // whitespace (in fact, most don't, at least for attributes
+ // like alt, but an extra space at the end is barely
+ // noticeable). Still, we have a configuration knob for
+ // this, since this transformation is not necesary if you
+ // don't process user input with innerHTML or you don't plan
+ // on supporting Internet Explorer.
+ if ($this->_innerHTMLFix) {
+ if (strpos($value, '`') !== false) {
+ // check if correct quoting style would not already be
+ // triggered
+ if (strcspn($value, '"\' <>') === strlen($value)) {
+ // protect!
+ $value .= ' ';
+ }
+ }
+ }
+ $html .= $key.'="'.$this->escape($value).'" ';
+ }
+ return rtrim($html);
+ }
+
+ /**
+ * Escapes raw text data.
+ * @todo This really ought to be protected, but until we have a facility
+ * for properly generating HTML here w/o using tokens, it stays
+ * public.
+ * @param string $string String data to escape for HTML.
+ * @param int $quote Quoting style, like htmlspecialchars. ENT_NOQUOTES is
+ * permissible for non-attribute output.
+ * @return string escaped data.
+ */
+ public function escape($string, $quote = null)
+ {
+ // Workaround for APC bug on Mac Leopard reported by sidepodcast
+ // http://htmlpurifier.org/phorum/read.php?3,4823,4846
+ if ($quote === null) {
+ $quote = ENT_COMPAT;
+ }
+ return htmlspecialchars($string, $quote, 'UTF-8');
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php
new file mode 100644
index 000000000..9b7b334dd
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php
@@ -0,0 +1,493 @@
+getAnonymousModule();
+ if (!isset($module->info[$element_name])) {
+ $element = $module->addBlankElement($element_name);
+ } else {
+ $element = $module->info[$element_name];
+ }
+ $element->attr[$attr_name] = $def;
+ }
+
+ /**
+ * Adds a custom element to your HTML definition
+ * @see HTMLPurifier_HTMLModule::addElement() for detailed
+ * parameter and return value descriptions.
+ */
+ public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array())
+ {
+ $module = $this->getAnonymousModule();
+ // assume that if the user is calling this, the element
+ // is safe. This may not be a good idea
+ $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes);
+ return $element;
+ }
+
+ /**
+ * Adds a blank element to your HTML definition, for overriding
+ * existing behavior
+ * @param string $element_name
+ * @return HTMLPurifier_ElementDef
+ * @see HTMLPurifier_HTMLModule::addBlankElement() for detailed
+ * parameter and return value descriptions.
+ */
+ public function addBlankElement($element_name)
+ {
+ $module = $this->getAnonymousModule();
+ $element = $module->addBlankElement($element_name);
+ return $element;
+ }
+
+ /**
+ * Retrieves a reference to the anonymous module, so you can
+ * bust out advanced features without having to make your own
+ * module.
+ * @return HTMLPurifier_HTMLModule
+ */
+ public function getAnonymousModule()
+ {
+ if (!$this->_anonModule) {
+ $this->_anonModule = new HTMLPurifier_HTMLModule();
+ $this->_anonModule->name = 'Anonymous';
+ }
+ return $this->_anonModule;
+ }
+
+ private $_anonModule = null;
+
+ // PUBLIC BUT INTERNAL VARIABLES --------------------------------------
+
+ /**
+ * @type string
+ */
+ public $type = 'HTML';
+
+ /**
+ * @type HTMLPurifier_HTMLModuleManager
+ */
+ public $manager;
+
+ /**
+ * Performs low-cost, preliminary initialization.
+ */
+ public function __construct()
+ {
+ $this->manager = new HTMLPurifier_HTMLModuleManager();
+ }
+
+ /**
+ * @param HTMLPurifier_Config $config
+ */
+ protected function doSetup($config)
+ {
+ $this->processModules($config);
+ $this->setupConfigStuff($config);
+ unset($this->manager);
+
+ // cleanup some of the element definitions
+ foreach ($this->info as $k => $v) {
+ unset($this->info[$k]->content_model);
+ unset($this->info[$k]->content_model_type);
+ }
+ }
+
+ /**
+ * Extract out the information from the manager
+ * @param HTMLPurifier_Config $config
+ */
+ protected function processModules($config)
+ {
+ if ($this->_anonModule) {
+ // for user specific changes
+ // this is late-loaded so we don't have to deal with PHP4
+ // reference wonky-ness
+ $this->manager->addModule($this->_anonModule);
+ unset($this->_anonModule);
+ }
+
+ $this->manager->setup($config);
+ $this->doctype = $this->manager->doctype;
+
+ foreach ($this->manager->modules as $module) {
+ foreach ($module->info_tag_transform as $k => $v) {
+ if ($v === false) {
+ unset($this->info_tag_transform[$k]);
+ } else {
+ $this->info_tag_transform[$k] = $v;
+ }
+ }
+ foreach ($module->info_attr_transform_pre as $k => $v) {
+ if ($v === false) {
+ unset($this->info_attr_transform_pre[$k]);
+ } else {
+ $this->info_attr_transform_pre[$k] = $v;
+ }
+ }
+ foreach ($module->info_attr_transform_post as $k => $v) {
+ if ($v === false) {
+ unset($this->info_attr_transform_post[$k]);
+ } else {
+ $this->info_attr_transform_post[$k] = $v;
+ }
+ }
+ foreach ($module->info_injector as $k => $v) {
+ if ($v === false) {
+ unset($this->info_injector[$k]);
+ } else {
+ $this->info_injector[$k] = $v;
+ }
+ }
+ }
+ $this->info = $this->manager->getElements();
+ $this->info_content_sets = $this->manager->contentSets->lookup;
+ }
+
+ /**
+ * Sets up stuff based on config. We need a better way of doing this.
+ * @param HTMLPurifier_Config $config
+ */
+ protected function setupConfigStuff($config)
+ {
+ $block_wrapper = $config->get('HTML.BlockWrapper');
+ if (isset($this->info_content_sets['Block'][$block_wrapper])) {
+ $this->info_block_wrapper = $block_wrapper;
+ } else {
+ trigger_error(
+ 'Cannot use non-block element as block wrapper',
+ E_USER_ERROR
+ );
+ }
+
+ $parent = $config->get('HTML.Parent');
+ $def = $this->manager->getElement($parent, true);
+ if ($def) {
+ $this->info_parent = $parent;
+ $this->info_parent_def = $def;
+ } else {
+ trigger_error(
+ 'Cannot use unrecognized element as parent',
+ E_USER_ERROR
+ );
+ $this->info_parent_def = $this->manager->getElement($this->info_parent, true);
+ }
+
+ // support template text
+ $support = "(for information on implementing this, see the support forums) ";
+
+ // setup allowed elements -----------------------------------------
+
+ $allowed_elements = $config->get('HTML.AllowedElements');
+ $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early
+
+ if (!is_array($allowed_elements) && !is_array($allowed_attributes)) {
+ $allowed = $config->get('HTML.Allowed');
+ if (is_string($allowed)) {
+ list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed);
+ }
+ }
+
+ if (is_array($allowed_elements)) {
+ foreach ($this->info as $name => $d) {
+ if (!isset($allowed_elements[$name])) {
+ unset($this->info[$name]);
+ }
+ unset($allowed_elements[$name]);
+ }
+ // emit errors
+ foreach ($allowed_elements as $element => $d) {
+ $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful!
+ trigger_error("Element '$element' is not supported $support", E_USER_WARNING);
+ }
+ }
+
+ // setup allowed attributes ---------------------------------------
+
+ $allowed_attributes_mutable = $allowed_attributes; // by copy!
+ if (is_array($allowed_attributes)) {
+ // This actually doesn't do anything, since we went away from
+ // global attributes. It's possible that userland code uses
+ // it, but HTMLModuleManager doesn't!
+ foreach ($this->info_global_attr as $attr => $x) {
+ $keys = array($attr, "*@$attr", "*.$attr");
+ $delete = true;
+ foreach ($keys as $key) {
+ if ($delete && isset($allowed_attributes[$key])) {
+ $delete = false;
+ }
+ if (isset($allowed_attributes_mutable[$key])) {
+ unset($allowed_attributes_mutable[$key]);
+ }
+ }
+ if ($delete) {
+ unset($this->info_global_attr[$attr]);
+ }
+ }
+
+ foreach ($this->info as $tag => $info) {
+ foreach ($info->attr as $attr => $x) {
+ $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr");
+ $delete = true;
+ foreach ($keys as $key) {
+ if ($delete && isset($allowed_attributes[$key])) {
+ $delete = false;
+ }
+ if (isset($allowed_attributes_mutable[$key])) {
+ unset($allowed_attributes_mutable[$key]);
+ }
+ }
+ if ($delete) {
+ if ($this->info[$tag]->attr[$attr]->required) {
+ trigger_error(
+ "Required attribute '$attr' in element '$tag' " .
+ "was not allowed, which means '$tag' will not be allowed either",
+ E_USER_WARNING
+ );
+ }
+ unset($this->info[$tag]->attr[$attr]);
+ }
+ }
+ }
+ // emit errors
+ foreach ($allowed_attributes_mutable as $elattr => $d) {
+ $bits = preg_split('/[.@]/', $elattr, 2);
+ $c = count($bits);
+ switch ($c) {
+ case 2:
+ if ($bits[0] !== '*') {
+ $element = htmlspecialchars($bits[0]);
+ $attribute = htmlspecialchars($bits[1]);
+ if (!isset($this->info[$element])) {
+ trigger_error(
+ "Cannot allow attribute '$attribute' if element " .
+ "'$element' is not allowed/supported $support"
+ );
+ } else {
+ trigger_error(
+ "Attribute '$attribute' in element '$element' not supported $support",
+ E_USER_WARNING
+ );
+ }
+ break;
+ }
+ // otherwise fall through
+ case 1:
+ $attribute = htmlspecialchars($bits[0]);
+ trigger_error(
+ "Global attribute '$attribute' is not ".
+ "supported in any elements $support",
+ E_USER_WARNING
+ );
+ break;
+ }
+ }
+ }
+
+ // setup forbidden elements ---------------------------------------
+
+ $forbidden_elements = $config->get('HTML.ForbiddenElements');
+ $forbidden_attributes = $config->get('HTML.ForbiddenAttributes');
+
+ foreach ($this->info as $tag => $info) {
+ if (isset($forbidden_elements[$tag])) {
+ unset($this->info[$tag]);
+ continue;
+ }
+ foreach ($info->attr as $attr => $x) {
+ if (isset($forbidden_attributes["$tag@$attr"]) ||
+ isset($forbidden_attributes["*@$attr"]) ||
+ isset($forbidden_attributes[$attr])
+ ) {
+ unset($this->info[$tag]->attr[$attr]);
+ continue;
+ } elseif (isset($forbidden_attributes["$tag.$attr"])) { // this segment might get removed eventually
+ // $tag.$attr are not user supplied, so no worries!
+ trigger_error(
+ "Error with $tag.$attr: tag.attr syntax not supported for " .
+ "HTML.ForbiddenAttributes; use tag@attr instead",
+ E_USER_WARNING
+ );
+ }
+ }
+ }
+ foreach ($forbidden_attributes as $key => $v) {
+ if (strlen($key) < 2) {
+ continue;
+ }
+ if ($key[0] != '*') {
+ continue;
+ }
+ if ($key[1] == '.') {
+ trigger_error(
+ "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead",
+ E_USER_WARNING
+ );
+ }
+ }
+
+ // setup injectors -----------------------------------------------------
+ foreach ($this->info_injector as $i => $injector) {
+ if ($injector->checkNeeded($config) !== false) {
+ // remove injector that does not have it's required
+ // elements/attributes present, and is thus not needed.
+ unset($this->info_injector[$i]);
+ }
+ }
+ }
+
+ /**
+ * Parses a TinyMCE-flavored Allowed Elements and Attributes list into
+ * separate lists for processing. Format is element[attr1|attr2],element2...
+ * @warning Although it's largely drawn from TinyMCE's implementation,
+ * it is different, and you'll probably have to modify your lists
+ * @param array $list String list to parse
+ * @return array
+ * @todo Give this its own class, probably static interface
+ */
+ public function parseTinyMCEAllowedList($list)
+ {
+ $list = str_replace(array(' ', "\t"), '', $list);
+
+ $elements = array();
+ $attributes = array();
+
+ $chunks = preg_split('/(,|[\n\r]+)/', $list);
+ foreach ($chunks as $chunk) {
+ if (empty($chunk)) {
+ continue;
+ }
+ // remove TinyMCE element control characters
+ if (!strpos($chunk, '[')) {
+ $element = $chunk;
+ $attr = false;
+ } else {
+ list($element, $attr) = explode('[', $chunk);
+ }
+ if ($element !== '*') {
+ $elements[$element] = true;
+ }
+ if (!$attr) {
+ continue;
+ }
+ $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ]
+ $attr = explode('|', $attr);
+ foreach ($attr as $key) {
+ $attributes["$element.$key"] = true;
+ }
+ }
+ return array($elements, $attributes);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php
new file mode 100644
index 000000000..9dbb98729
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php
@@ -0,0 +1,285 @@
+info, since the object's data is only info,
+ * with extra behavior associated with it.
+ * @type array
+ */
+ public $attr_collections = array();
+
+ /**
+ * Associative array of deprecated tag name to HTMLPurifier_TagTransform.
+ * @type array
+ */
+ public $info_tag_transform = array();
+
+ /**
+ * List of HTMLPurifier_AttrTransform to be performed before validation.
+ * @type array
+ */
+ public $info_attr_transform_pre = array();
+
+ /**
+ * List of HTMLPurifier_AttrTransform to be performed after validation.
+ * @type array
+ */
+ public $info_attr_transform_post = array();
+
+ /**
+ * List of HTMLPurifier_Injector to be performed during well-formedness fixing.
+ * An injector will only be invoked if all of it's pre-requisites are met;
+ * if an injector fails setup, there will be no error; it will simply be
+ * silently disabled.
+ * @type array
+ */
+ public $info_injector = array();
+
+ /**
+ * Boolean flag that indicates whether or not getChildDef is implemented.
+ * For optimization reasons: may save a call to a function. Be sure
+ * to set it if you do implement getChildDef(), otherwise it will have
+ * no effect!
+ * @type bool
+ */
+ public $defines_child_def = false;
+
+ /**
+ * Boolean flag whether or not this module is safe. If it is not safe, all
+ * of its members are unsafe. Modules are safe by default (this might be
+ * slightly dangerous, but it doesn't make much sense to force HTML Purifier,
+ * which is based off of safe HTML, to explicitly say, "This is safe," even
+ * though there are modules which are "unsafe")
+ *
+ * @type bool
+ * @note Previously, safety could be applied at an element level granularity.
+ * We've removed this ability, so in order to add "unsafe" elements
+ * or attributes, a dedicated module with this property set to false
+ * must be used.
+ */
+ public $safe = true;
+
+ /**
+ * Retrieves a proper HTMLPurifier_ChildDef subclass based on
+ * content_model and content_model_type member variables of
+ * the HTMLPurifier_ElementDef class. There is a similar function
+ * in HTMLPurifier_HTMLDefinition.
+ * @param HTMLPurifier_ElementDef $def
+ * @return HTMLPurifier_ChildDef subclass
+ */
+ public function getChildDef($def)
+ {
+ return false;
+ }
+
+ // -- Convenience -----------------------------------------------------
+
+ /**
+ * Convenience function that sets up a new element
+ * @param string $element Name of element to add
+ * @param string|bool $type What content set should element be registered to?
+ * Set as false to skip this step.
+ * @param string|HTMLPurifier_ChildDef $contents Allowed children in form of:
+ * "$content_model_type: $content_model"
+ * @param array|string $attr_includes What attribute collections to register to
+ * element?
+ * @param array $attr What unique attributes does the element define?
+ * @see HTMLPurifier_ElementDef:: for in-depth descriptions of these parameters.
+ * @return HTMLPurifier_ElementDef Created element definition object, so you
+ * can set advanced parameters
+ */
+ public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array())
+ {
+ $this->elements[] = $element;
+ // parse content_model
+ list($content_model_type, $content_model) = $this->parseContents($contents);
+ // merge in attribute inclusions
+ $this->mergeInAttrIncludes($attr, $attr_includes);
+ // add element to content sets
+ if ($type) {
+ $this->addElementToContentSet($element, $type);
+ }
+ // create element
+ $this->info[$element] = HTMLPurifier_ElementDef::create(
+ $content_model,
+ $content_model_type,
+ $attr
+ );
+ // literal object $contents means direct child manipulation
+ if (!is_string($contents)) {
+ $this->info[$element]->child = $contents;
+ }
+ return $this->info[$element];
+ }
+
+ /**
+ * Convenience function that creates a totally blank, non-standalone
+ * element.
+ * @param string $element Name of element to create
+ * @return HTMLPurifier_ElementDef Created element
+ */
+ public function addBlankElement($element)
+ {
+ if (!isset($this->info[$element])) {
+ $this->elements[] = $element;
+ $this->info[$element] = new HTMLPurifier_ElementDef();
+ $this->info[$element]->standalone = false;
+ } else {
+ trigger_error("Definition for $element already exists in module, cannot redefine");
+ }
+ return $this->info[$element];
+ }
+
+ /**
+ * Convenience function that registers an element to a content set
+ * @param string $element Element to register
+ * @param string $type Name content set (warning: case sensitive, usually upper-case
+ * first letter)
+ */
+ public function addElementToContentSet($element, $type)
+ {
+ if (!isset($this->content_sets[$type])) {
+ $this->content_sets[$type] = '';
+ } else {
+ $this->content_sets[$type] .= ' | ';
+ }
+ $this->content_sets[$type] .= $element;
+ }
+
+ /**
+ * Convenience function that transforms single-string contents
+ * into separate content model and content model type
+ * @param string $contents Allowed children in form of:
+ * "$content_model_type: $content_model"
+ * @return array
+ * @note If contents is an object, an array of two nulls will be
+ * returned, and the callee needs to take the original $contents
+ * and use it directly.
+ */
+ public function parseContents($contents)
+ {
+ if (!is_string($contents)) {
+ return array(null, null);
+ } // defer
+ switch ($contents) {
+ // check for shorthand content model forms
+ case 'Empty':
+ return array('empty', '');
+ case 'Inline':
+ return array('optional', 'Inline | #PCDATA');
+ case 'Flow':
+ return array('optional', 'Flow | #PCDATA');
+ }
+ list($content_model_type, $content_model) = explode(':', $contents);
+ $content_model_type = strtolower(trim($content_model_type));
+ $content_model = trim($content_model);
+ return array($content_model_type, $content_model);
+ }
+
+ /**
+ * Convenience function that merges a list of attribute includes into
+ * an attribute array.
+ * @param array $attr Reference to attr array to modify
+ * @param array $attr_includes Array of includes / string include to merge in
+ */
+ public function mergeInAttrIncludes(&$attr, $attr_includes)
+ {
+ if (!is_array($attr_includes)) {
+ if (empty($attr_includes)) {
+ $attr_includes = array();
+ } else {
+ $attr_includes = array($attr_includes);
+ }
+ }
+ $attr[0] = $attr_includes;
+ }
+
+ /**
+ * Convenience function that generates a lookup table with boolean
+ * true as value.
+ * @param string $list List of values to turn into a lookup
+ * @note You can also pass an arbitrary number of arguments in
+ * place of the regular argument
+ * @return array array equivalent of list
+ */
+ public function makeLookup($list)
+ {
+ $args = func_get_args();
+ if (is_string($list)) {
+ $list = $args;
+ }
+ $ret = array();
+ foreach ($list as $value) {
+ if (is_null($value)) {
+ continue;
+ }
+ $ret[$value] = true;
+ }
+ return $ret;
+ }
+
+ /**
+ * Lazy load construction of the module after determining whether
+ * or not it's needed, and also when a finalized configuration object
+ * is available.
+ * @param HTMLPurifier_Config $config
+ */
+ public function setup($config)
+ {
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php
new file mode 100644
index 000000000..1e67c790d
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php
@@ -0,0 +1,44 @@
+ array('dir' => false)
+ );
+
+ /**
+ * @param HTMLPurifier_Config $config
+ */
+ public function setup($config)
+ {
+ $bdo = $this->addElement(
+ 'bdo',
+ 'Inline',
+ 'Inline',
+ array('Core', 'Lang'),
+ array(
+ 'dir' => 'Enum#ltr,rtl', // required
+ // The Abstract Module specification has the attribute
+ // inclusions wrong for bdo: bdo allows Lang
+ )
+ );
+ $bdo->attr_transform_post[] = new HTMLPurifier_AttrTransform_BdoDir();
+
+ $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl';
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php
new file mode 100644
index 000000000..7220c14cc
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php
@@ -0,0 +1,32 @@
+ array(
+ 0 => array('Style'),
+ // 'xml:space' => false,
+ 'class' => 'Class',
+ 'id' => 'ID',
+ 'title' => 'CDATA',
+ 'contenteditable' => 'ContentEditable',
+ ),
+ 'Lang' => array(),
+ 'I18N' => array(
+ 0 => array('Lang'), // proprietary, for xml:lang/lang
+ ),
+ 'Common' => array(
+ 0 => array('Core', 'I18N')
+ )
+ );
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php
new file mode 100644
index 000000000..a9042a357
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php
@@ -0,0 +1,55 @@
+ 'URI',
+ // 'datetime' => 'Datetime', // not implemented
+ );
+ $this->addElement('del', 'Inline', $contents, 'Common', $attr);
+ $this->addElement('ins', 'Inline', $contents, 'Common', $attr);
+ }
+
+ // HTML 4.01 specifies that ins/del must not contain block
+ // elements when used in an inline context, chameleon is
+ // a complicated workaround to acheive this effect
+
+ // Inline context ! Block context (exclamation mark is
+ // separator, see getChildDef for parsing)
+
+ /**
+ * @type bool
+ */
+ public $defines_child_def = true;
+
+ /**
+ * @param HTMLPurifier_ElementDef $def
+ * @return HTMLPurifier_ChildDef_Chameleon
+ */
+ public function getChildDef($def)
+ {
+ if ($def->content_model_type != 'chameleon') {
+ return false;
+ }
+ $value = explode('!', $def->content_model);
+ return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php
new file mode 100644
index 000000000..eb0edcffd
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php
@@ -0,0 +1,194 @@
+ 'Form',
+ 'Inline' => 'Formctrl',
+ );
+
+ /**
+ * @param HTMLPurifier_Config $config
+ */
+ public function setup($config)
+ {
+ if ($config->get('HTML.Forms')) {
+ $this->safe = true;
+ }
+
+ $form = $this->addElement(
+ 'form',
+ 'Form',
+ 'Required: Heading | List | Block | fieldset',
+ 'Common',
+ array(
+ 'accept' => 'ContentTypes',
+ 'accept-charset' => 'Charsets',
+ 'action*' => 'URI',
+ 'method' => 'Enum#get,post',
+ // really ContentType, but these two are the only ones used today
+ 'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data',
+ )
+ );
+ $form->excludes = array('form' => true);
+
+ $input = $this->addElement(
+ 'input',
+ 'Formctrl',
+ 'Empty',
+ 'Common',
+ array(
+ 'accept' => 'ContentTypes',
+ 'accesskey' => 'Character',
+ 'alt' => 'Text',
+ 'checked' => 'Bool#checked',
+ 'disabled' => 'Bool#disabled',
+ 'maxlength' => 'Number',
+ 'name' => 'CDATA',
+ 'readonly' => 'Bool#readonly',
+ 'size' => 'Number',
+ 'src' => 'URI#embedded',
+ 'tabindex' => 'Number',
+ 'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image',
+ 'value' => 'CDATA',
+ )
+ );
+ $input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input();
+
+ $this->addElement(
+ 'select',
+ 'Formctrl',
+ 'Required: optgroup | option',
+ 'Common',
+ array(
+ 'disabled' => 'Bool#disabled',
+ 'multiple' => 'Bool#multiple',
+ 'name' => 'CDATA',
+ 'size' => 'Number',
+ 'tabindex' => 'Number',
+ )
+ );
+
+ $this->addElement(
+ 'option',
+ false,
+ 'Optional: #PCDATA',
+ 'Common',
+ array(
+ 'disabled' => 'Bool#disabled',
+ 'label' => 'Text',
+ 'selected' => 'Bool#selected',
+ 'value' => 'CDATA',
+ )
+ );
+ // It's illegal for there to be more than one selected, but not
+ // be multiple. Also, no selected means undefined behavior. This might
+ // be difficult to implement; perhaps an injector, or a context variable.
+
+ $textarea = $this->addElement(
+ 'textarea',
+ 'Formctrl',
+ 'Optional: #PCDATA',
+ 'Common',
+ array(
+ 'accesskey' => 'Character',
+ 'cols*' => 'Number',
+ 'disabled' => 'Bool#disabled',
+ 'name' => 'CDATA',
+ 'readonly' => 'Bool#readonly',
+ 'rows*' => 'Number',
+ 'tabindex' => 'Number',
+ )
+ );
+ $textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea();
+
+ $button = $this->addElement(
+ 'button',
+ 'Formctrl',
+ 'Optional: #PCDATA | Heading | List | Block | Inline',
+ 'Common',
+ array(
+ 'accesskey' => 'Character',
+ 'disabled' => 'Bool#disabled',
+ 'name' => 'CDATA',
+ 'tabindex' => 'Number',
+ 'type' => 'Enum#button,submit,reset',
+ 'value' => 'CDATA',
+ )
+ );
+
+ // For exclusions, ideally we'd specify content sets, not literal elements
+ $button->excludes = $this->makeLookup(
+ 'form',
+ 'fieldset', // Form
+ 'input',
+ 'select',
+ 'textarea',
+ 'label',
+ 'button', // Formctrl
+ 'a', // as per HTML 4.01 spec, this is omitted by modularization
+ 'isindex',
+ 'iframe' // legacy items
+ );
+
+ // Extra exclusion: img usemap="" is not permitted within this element.
+ // We'll omit this for now, since we don't have any good way of
+ // indicating it yet.
+
+ // This is HIGHLY user-unfriendly; we need a custom child-def for this
+ $this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common');
+
+ $label = $this->addElement(
+ 'label',
+ 'Formctrl',
+ 'Optional: #PCDATA | Inline',
+ 'Common',
+ array(
+ 'accesskey' => 'Character',
+ // 'for' => 'IDREF', // IDREF not implemented, cannot allow
+ )
+ );
+ $label->excludes = array('label' => true);
+
+ $this->addElement(
+ 'legend',
+ false,
+ 'Optional: #PCDATA | Inline',
+ 'Common',
+ array(
+ 'accesskey' => 'Character',
+ )
+ );
+
+ $this->addElement(
+ 'optgroup',
+ false,
+ 'Required: option',
+ 'Common',
+ array(
+ 'disabled' => 'Bool#disabled',
+ 'label*' => 'Text',
+ )
+ );
+ // Don't forget an injector for
+ // --
+ }
+ }
+ } else {
+ // State 2:
+ // ---
+ // State 1.1.3:
, so
+ // any later token processing scripts must convert improperly
+ // classified EmptyTags from StartTags.
+ $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1);
+ if ($is_self_closing) {
+ $strlen_segment--;
+ $segment = substr($segment, 0, $strlen_segment);
+ }
+
+ // Check if there are any attributes
+ $position_first_space = strcspn($segment, $this->_whitespace);
+
+ if ($position_first_space >= $strlen_segment) {
+ if ($is_self_closing) {
+ $token = new HTMLPurifier_Token_Empty($segment);
+ } else {
+ $token = new HTMLPurifier_Token_Start($segment);
+ }
+ if ($maintain_line_numbers) {
+ $token->rawPosition($current_line, $current_col);
+ $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
+ }
+ $array[] = $token;
+ $inside_tag = false;
+ $cursor = $position_next_gt + 1;
+ continue;
+ }
+
+ // Grab out all the data
+ $type = substr($segment, 0, $position_first_space);
+ $attribute_string =
+ trim(
+ substr(
+ $segment,
+ $position_first_space
+ )
+ );
+ if ($attribute_string) {
+ $attr = $this->parseAttributeString(
+ $attribute_string,
+ $config,
+ $context
+ );
+ } else {
+ $attr = array();
+ }
+
+ if ($is_self_closing) {
+ $token = new HTMLPurifier_Token_Empty($type, $attr);
+ } else {
+ $token = new HTMLPurifier_Token_Start($type, $attr);
+ }
+ if ($maintain_line_numbers) {
+ $token->rawPosition($current_line, $current_col);
+ $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
+ }
+ $array[] = $token;
+ $cursor = $position_next_gt + 1;
+ $inside_tag = false;
+ continue;
+ } else {
+ // inside tag, but there's no ending > sign
+ if ($e) {
+ $e->send(E_WARNING, 'Lexer: Missing gt');
+ }
+ $token = new
+ HTMLPurifier_Token_Text(
+ '<' .
+ $this->parseText(
+ substr($html, $cursor), $config
+ )
+ );
+ if ($maintain_line_numbers) {
+ $token->rawPosition($current_line, $current_col);
+ }
+ // no cursor scroll? Hmm...
+ $array[] = $token;
+ break;
+ }
+ break;
+ }
+
+ $context->destroy('CurrentLine');
+ $context->destroy('CurrentCol');
+ return $array;
+ }
+
+ /**
+ * PHP 5.0.x compatible substr_count that implements offset and length
+ * @param string $haystack
+ * @param string $needle
+ * @param int $offset
+ * @param int $length
+ * @return int
+ */
+ protected function substrCount($haystack, $needle, $offset, $length)
+ {
+ static $oldVersion;
+ if ($oldVersion === null) {
+ $oldVersion = version_compare(PHP_VERSION, '5.1', '<');
+ }
+ if ($oldVersion) {
+ $haystack = substr($haystack, $offset, $length);
+ return substr_count($haystack, $needle);
+ } else {
+ return substr_count($haystack, $needle, $offset, $length);
+ }
+ }
+
+ /**
+ * Takes the inside of an HTML tag and makes an assoc array of attributes.
+ *
+ * @param string $string Inside of tag excluding name.
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return array Assoc array of attributes.
+ */
+ public function parseAttributeString($string, $config, $context)
+ {
+ $string = (string)$string; // quick typecast
+
+ if ($string == '') {
+ return array();
+ } // no attributes
+
+ $e = false;
+ if ($config->get('Core.CollectErrors')) {
+ $e =& $context->get('ErrorCollector');
+ }
+
+ // let's see if we can abort as quickly as possible
+ // one equal sign, no spaces => one attribute
+ $num_equal = substr_count($string, '=');
+ $has_space = strpos($string, ' ');
+ if ($num_equal === 0 && !$has_space) {
+ // bool attribute
+ return array($string => $string);
+ } elseif ($num_equal === 1 && !$has_space) {
+ // only one attribute
+ list($key, $quoted_value) = explode('=', $string);
+ $quoted_value = trim($quoted_value);
+ if (!$key) {
+ if ($e) {
+ $e->send(E_ERROR, 'Lexer: Missing attribute key');
+ }
+ return array();
+ }
+ if (!$quoted_value) {
+ return array($key => '');
+ }
+ $first_char = @$quoted_value[0];
+ $last_char = @$quoted_value[strlen($quoted_value) - 1];
+
+ $same_quote = ($first_char == $last_char);
+ $open_quote = ($first_char == '"' || $first_char == "'");
+
+ if ($same_quote && $open_quote) {
+ // well behaved
+ $value = substr($quoted_value, 1, strlen($quoted_value) - 2);
+ } else {
+ // not well behaved
+ if ($open_quote) {
+ if ($e) {
+ $e->send(E_ERROR, 'Lexer: Missing end quote');
+ }
+ $value = substr($quoted_value, 1);
+ } else {
+ $value = $quoted_value;
+ }
+ }
+ if ($value === false) {
+ $value = '';
+ }
+ return array($key => $this->parseAttr($value, $config));
+ }
+
+ // setup loop environment
+ $array = array(); // return assoc array of attributes
+ $cursor = 0; // current position in string (moves forward)
+ $size = strlen($string); // size of the string (stays the same)
+
+ // if we have unquoted attributes, the parser expects a terminating
+ // space, so let's guarantee that there's always a terminating space.
+ $string .= ' ';
+
+ $old_cursor = -1;
+ while ($cursor < $size) {
+ if ($old_cursor >= $cursor) {
+ throw new Exception("Infinite loop detected");
+ }
+ $old_cursor = $cursor;
+
+ $cursor += ($value = strspn($string, $this->_whitespace, $cursor));
+ // grab the key
+
+ $key_begin = $cursor; //we're currently at the start of the key
+
+ // scroll past all characters that are the key (not whitespace or =)
+ $cursor += strcspn($string, $this->_whitespace . '=', $cursor);
+
+ $key_end = $cursor; // now at the end of the key
+
+ $key = substr($string, $key_begin, $key_end - $key_begin);
+
+ if (!$key) {
+ if ($e) {
+ $e->send(E_ERROR, 'Lexer: Missing attribute key');
+ }
+ $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop
+ continue; // empty key
+ }
+
+ // scroll past all whitespace
+ $cursor += strspn($string, $this->_whitespace, $cursor);
+
+ if ($cursor >= $size) {
+ $array[$key] = $key;
+ break;
+ }
+
+ // if the next character is an equal sign, we've got a regular
+ // pair, otherwise, it's a bool attribute
+ $first_char = @$string[$cursor];
+
+ if ($first_char == '=') {
+ // key="value"
+
+ $cursor++;
+ $cursor += strspn($string, $this->_whitespace, $cursor);
+
+ if ($cursor === false) {
+ $array[$key] = '';
+ break;
+ }
+
+ // we might be in front of a quote right now
+
+ $char = @$string[$cursor];
+
+ if ($char == '"' || $char == "'") {
+ // it's quoted, end bound is $char
+ $cursor++;
+ $value_begin = $cursor;
+ $cursor = strpos($string, $char, $cursor);
+ $value_end = $cursor;
+ } else {
+ // it's not quoted, end bound is whitespace
+ $value_begin = $cursor;
+ $cursor += strcspn($string, $this->_whitespace, $cursor);
+ $value_end = $cursor;
+ }
+
+ // we reached a premature end
+ if ($cursor === false) {
+ $cursor = $size;
+ $value_end = $cursor;
+ }
+
+ $value = substr($string, $value_begin, $value_end - $value_begin);
+ if ($value === false) {
+ $value = '';
+ }
+ $array[$key] = $this->parseAttr($value, $config);
+ $cursor++;
+ } else {
+ // boolattr
+ if ($key !== '') {
+ $array[$key] = $key;
+ } else {
+ // purely theoretical
+ if ($e) {
+ $e->send(E_ERROR, 'Lexer: Missing attribute key');
+ }
+ }
+ }
+ }
+ return $array;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php
new file mode 100644
index 000000000..1564f283d
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php
@@ -0,0 +1,4788 @@
+normalize($html, $config, $context);
+ $new_html = $this->wrapHTML($new_html, $config, $context, false /* no div */);
+ try {
+ $parser = new HTML5($new_html);
+ $doc = $parser->save();
+ } catch (DOMException $e) {
+ // Uh oh, it failed. Punt to DirectLex.
+ $lexer = new HTMLPurifier_Lexer_DirectLex();
+ $context->register('PH5PError', $e); // save the error, so we can detect it
+ return $lexer->tokenizeHTML($html, $config, $context); // use original HTML
+ }
+ $tokens = array();
+ $this->tokenizeDOM(
+ $doc->getElementsByTagName('html')->item(0)-> //
+ getElementsByTagName('body')->item(0) //
+ ,
+ $tokens, $config
+ );
+ return $tokens;
+ }
+}
+
+/*
+
+Copyright 2007 Jeroen van der Meer
'; printZipper($zipper, $token);//printTokens($this->stack);
+ //flush();
+
+ // quick-check: if it's not a tag, no need to process
+ if (empty($token->is_tag)) {
+ if ($token instanceof HTMLPurifier_Token_Text) {
+ foreach ($this->injectors as $i => $injector) {
+ if (isset($token->skip[$i])) {
+ // See Note [Injector skips]
+ continue;
+ }
+ if ($token->rewind !== null && $token->rewind !== $i) {
+ continue;
+ }
+ // XXX fuckup
+ $r = $token;
+ $injector->handleText($r);
+ $token = $this->processToken($r, $i);
+ $reprocess = true;
+ break;
+ }
+ }
+ // another possibility is a comment
+ continue;
+ }
+
+ if (isset($definition->info[$token->name])) {
+ $type = $definition->info[$token->name]->child->type;
+ } else {
+ $type = false; // Type is unknown, treat accordingly
+ }
+
+ // quick tag checks: anything that's *not* an end tag
+ $ok = false;
+ if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) {
+ // claims to be a start tag but is empty
+ $token = new HTMLPurifier_Token_Empty(
+ $token->name,
+ $token->attr,
+ $token->line,
+ $token->col,
+ $token->armor
+ );
+ $ok = true;
+ } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) {
+ // claims to be empty but really is a start tag
+ // NB: this assignment is required
+ $old_token = $token;
+ $token = new HTMLPurifier_Token_End($token->name);
+ $token = $this->insertBefore(
+ new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor)
+ );
+ // punt (since we had to modify the input stream in a non-trivial way)
+ $reprocess = true;
+ continue;
+ } elseif ($token instanceof HTMLPurifier_Token_Empty) {
+ // real empty token
+ $ok = true;
+ } elseif ($token instanceof HTMLPurifier_Token_Start) {
+ // start tag
+
+ // ...unless they also have to close their parent
+ if (!empty($this->stack)) {
+
+ // Performance note: you might think that it's rather
+ // inefficient, recalculating the autoclose information
+ // for every tag that a token closes (since when we
+ // do an autoclose, we push a new token into the
+ // stream and then /process/ that, before
+ // re-processing this token.) But this is
+ // necessary, because an injector can make an
+ // arbitrary transformations to the autoclosing
+ // tokens we introduce, so things may have changed
+ // in the meantime. Also, doing the inefficient thing is
+ // "easy" to reason about (for certain perverse definitions
+ // of "easy")
+
+ $parent = array_pop($this->stack);
+ $this->stack[] = $parent;
+
+ $parent_def = null;
+ $parent_elements = null;
+ $autoclose = false;
+ if (isset($definition->info[$parent->name])) {
+ $parent_def = $definition->info[$parent->name];
+ $parent_elements = $parent_def->child->getAllowedElements($config);
+ $autoclose = !isset($parent_elements[$token->name]);
+ }
+
+ if ($autoclose && $definition->info[$token->name]->wrap) {
+ // Check if an element can be wrapped by another
+ // element to make it valid in a context (for
+ // example,
+ $c = count($skipped_tags);
+ if ($e) {
+ for ($j = $c - 1; $j > 0; $j--) {
+ // notice we exclude $j == 0, i.e. the current ending tag, from
+ // the errors... [TagClosedSuppress]
+ if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) {
+ $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]);
+ }
+ }
+ }
+
+ // insert tags, in FORWARD $j order: c,b,a with
+ $replace = array($token);
+ for ($j = 1; $j < $c; $j++) {
+ // ...as well as from the insertions
+ $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name);
+ $new_token->start = $skipped_tags[$j];
+ array_unshift($replace, $new_token);
+ if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) {
+ // [TagClosedAuto]
+ $element = clone $skipped_tags[$j];
+ $element->carryover = true;
+ $element->armor['MakeWellFormed_TagClosedError'] = true;
+ $replace[] = $element;
+ }
+ }
+ $token = $this->processToken($replace);
+ $reprocess = true;
+ continue;
+ }
+
+ $context->destroy('CurrentToken');
+ $context->destroy('CurrentNesting');
+ $context->destroy('InputZipper');
+
+ unset($this->injectors, $this->stack, $this->tokens);
+ return $zipper->toArray($token);
+ }
+
+ /**
+ * Processes arbitrary token values for complicated substitution patterns.
+ * In general:
+ *
+ * If $token is an array, it is a list of tokens to substitute for the
+ * current token. These tokens then get individually processed. If there
+ * is a leading integer in the list, that integer determines how many
+ * tokens from the stream should be removed.
+ *
+ * If $token is a regular token, it is swapped with the current token.
+ *
+ * If $token is false, the current token is deleted.
+ *
+ * If $token is an integer, that number of tokens (with the first token
+ * being the current one) will be deleted.
+ *
+ * @param HTMLPurifier_Token|array|int|bool $token Token substitution value
+ * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if
+ * this is not an injector related operation.
+ * @throws HTMLPurifier_Exception
+ */
+ protected function processToken($token, $injector = -1)
+ {
+ // Zend OpCache miscompiles $token = array($token), so
+ // avoid this pattern. See: https://github.com/ezyang/htmlpurifier/issues/108
+
+ // normalize forms of token
+ if (is_object($token)) {
+ $tmp = $token;
+ $token = array(1, $tmp);
+ }
+ if (is_int($token)) {
+ $tmp = $token;
+ $token = array($tmp);
+ }
+ if ($token === false) {
+ $token = array(1);
+ }
+ if (!is_array($token)) {
+ throw new HTMLPurifier_Exception('Invalid token type from injector');
+ }
+ if (!is_int($token[0])) {
+ array_unshift($token, 1);
+ }
+ if ($token[0] === 0) {
+ throw new HTMLPurifier_Exception('Deleting zero tokens is not valid');
+ }
+
+ // $token is now an array with the following form:
+ // array(number nodes to delete, new node 1, new node 2, ...)
+
+ $delete = array_shift($token);
+ list($old, $r) = $this->zipper->splice($this->token, $delete, $token);
+
+ if ($injector > -1) {
+ // See Note [Injector skips]
+ // Determine appropriate skips. Here's what the code does:
+ // *If* we deleted one or more tokens, copy the skips
+ // of those tokens into the skips of the new tokens (in $token).
+ // Also, mark the newly inserted tokens as having come from
+ // $injector.
+ $oldskip = isset($old[0]) ? $old[0]->skip : array();
+ foreach ($token as $object) {
+ $object->skip = $oldskip;
+ $object->skip[$injector] = true;
+ }
+ }
+
+ return $r;
+
+ }
+
+ /**
+ * Inserts a token before the current token. Cursor now points to
+ * this token. You must reprocess after this.
+ * @param HTMLPurifier_Token $token
+ */
+ private function insertBefore($token)
+ {
+ // NB not $this->zipper->insertBefore(), due to positioning
+ // differences
+ $splice = $this->zipper->splice($this->token, 0, array($token));
+
+ return $splice[1];
+ }
+
+ /**
+ * Removes current token. Cursor now points to new token occupying previously
+ * occupied space. You must reprocess after this.
+ */
+ private function remove()
+ {
+ return $this->zipper->delete();
+ }
+}
+
+// Note [Injector skips]
+// ~~~~~~~~~~~~~~~~~~~~~
+// When I originally designed this class, the idea behind the 'skip'
+// property of HTMLPurifier_Token was to help avoid infinite loops
+// in injector processing. For example, suppose you wrote an injector
+// that bolded swear words. Naively, you might write it so that
+// whenever you saw ****, you replaced it with ****.
+//
+// When this happens, we will reprocess all of the tokens with the
+// other injectors. Now there is an opportunity for infinite loop:
+// if we rerun the swear-word injector on these tokens, we might
+// see **** and then reprocess again to get
+// **** ad infinitum.
+//
+// Thus, the idea of a skip is that once we process a token with
+// an injector, we mark all of those tokens as having "come from"
+// the injector, and we never run the injector again on these
+// tokens.
+//
+// There were two more complications, however:
+//
+// - With HTMLPurifier_Injector_RemoveEmpty, we noticed that if
+// you had , after you removed the , you
+// really would like this injector to go back and reprocess
+// the tag, discovering that it is now empty and can be
+// removed. So we reintroduced the possibility of infinite looping
+// by adding a "rewind" function, which let you go back to an
+// earlier point in the token stream and reprocess it with injectors.
+// Needless to say, we need to UN-skip the token so it gets
+// reprocessed.
+//
+// - Suppose that you successfuly process a token, replace it with
+// one with your skip mark, but now another injector wants to
+// process the skipped token with another token. Should you continue
+// to skip that new token, or reprocess it? If you reprocess,
+// you can end up with an infinite loop where one injector converts
+// to , and then another injector converts it back. So
+// we inherit the skips, but for some reason, I thought that we
+// should inherit the skip from the first token of the token
+// that we deleted. Why? Well, it seems to work OK.
+//
+// If I were to redesign this functionality, I would absolutely not
+// go about doing it this way: the semantics are just not very well
+// defined, and in any case you probably wanted to operate on trees,
+// not token streams.
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php
new file mode 100644
index 000000000..1a8149ecc
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php
@@ -0,0 +1,207 @@
+getHTMLDefinition();
+ $generator = new HTMLPurifier_Generator($config, $context);
+ $result = array();
+
+ $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
+ $remove_invalid_img = $config->get('Core.RemoveInvalidImg');
+
+ // currently only used to determine if comments should be kept
+ $trusted = $config->get('HTML.Trusted');
+ $comment_lookup = $config->get('HTML.AllowedComments');
+ $comment_regexp = $config->get('HTML.AllowedCommentsRegexp');
+ $check_comments = $comment_lookup !== array() || $comment_regexp !== null;
+
+ $remove_script_contents = $config->get('Core.RemoveScriptContents');
+ $hidden_elements = $config->get('Core.HiddenElements');
+
+ // remove script contents compatibility
+ if ($remove_script_contents === true) {
+ $hidden_elements['script'] = true;
+ } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) {
+ unset($hidden_elements['script']);
+ }
+
+ $attr_validator = new HTMLPurifier_AttrValidator();
+
+ // removes tokens until it reaches a closing tag with its value
+ $remove_until = false;
+
+ // converts comments into text tokens when this is equal to a tag name
+ $textify_comments = false;
+
+ $token = false;
+ $context->register('CurrentToken', $token);
+
+ $e = false;
+ if ($config->get('Core.CollectErrors')) {
+ $e =& $context->get('ErrorCollector');
+ }
+
+ foreach ($tokens as $token) {
+ if ($remove_until) {
+ if (empty($token->is_tag) || $token->name !== $remove_until) {
+ continue;
+ }
+ }
+ if (!empty($token->is_tag)) {
+ // DEFINITION CALL
+
+ // before any processing, try to transform the element
+ if (isset($definition->info_tag_transform[$token->name])) {
+ $original_name = $token->name;
+ // there is a transformation for this tag
+ // DEFINITION CALL
+ $token = $definition->
+ info_tag_transform[$token->name]->transform($token, $config, $context);
+ if ($e) {
+ $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name);
+ }
+ }
+
+ if (isset($definition->info[$token->name])) {
+ // mostly everything's good, but
+ // we need to make sure required attributes are in order
+ if (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) &&
+ $definition->info[$token->name]->required_attr &&
+ ($token->name != 'img' || $remove_invalid_img) // ensure config option still works
+ ) {
+ $attr_validator->validateToken($token, $config, $context);
+ $ok = true;
+ foreach ($definition->info[$token->name]->required_attr as $name) {
+ if (!isset($token->attr[$name])) {
+ $ok = false;
+ break;
+ }
+ }
+ if (!$ok) {
+ if ($e) {
+ $e->send(
+ E_ERROR,
+ 'Strategy_RemoveForeignElements: Missing required attribute',
+ $name
+ );
+ }
+ continue;
+ }
+ $token->armor['ValidateAttributes'] = true;
+ }
+
+ if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) {
+ $textify_comments = $token->name;
+ } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) {
+ $textify_comments = false;
+ }
+
+ } elseif ($escape_invalid_tags) {
+ // invalid tag, generate HTML representation and insert in
+ if ($e) {
+ $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text');
+ }
+ $token = new HTMLPurifier_Token_Text(
+ $generator->generateFromToken($token)
+ );
+ } else {
+ // check if we need to destroy all of the tag's children
+ // CAN BE GENERICIZED
+ if (isset($hidden_elements[$token->name])) {
+ if ($token instanceof HTMLPurifier_Token_Start) {
+ $remove_until = $token->name;
+ } elseif ($token instanceof HTMLPurifier_Token_Empty) {
+ // do nothing: we're still looking
+ } else {
+ $remove_until = false;
+ }
+ if ($e) {
+ $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed');
+ }
+ } else {
+ if ($e) {
+ $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed');
+ }
+ }
+ continue;
+ }
+ } elseif ($token instanceof HTMLPurifier_Token_Comment) {
+ // textify comments in script tags when they are allowed
+ if ($textify_comments !== false) {
+ $data = $token->data;
+ $token = new HTMLPurifier_Token_Text($data);
+ } elseif ($trusted || $check_comments) {
+ // always cleanup comments
+ $trailing_hyphen = false;
+ if ($e) {
+ // perform check whether or not there's a trailing hyphen
+ if (substr($token->data, -1) == '-') {
+ $trailing_hyphen = true;
+ }
+ }
+ $token->data = rtrim($token->data, '-');
+ $found_double_hyphen = false;
+ while (strpos($token->data, '--') !== false) {
+ $found_double_hyphen = true;
+ $token->data = str_replace('--', '-', $token->data);
+ }
+ if ($trusted || !empty($comment_lookup[trim($token->data)]) ||
+ ($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) {
+ // OK good
+ if ($e) {
+ if ($trailing_hyphen) {
+ $e->send(
+ E_NOTICE,
+ 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed'
+ );
+ }
+ if ($found_double_hyphen) {
+ $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed');
+ }
+ }
+ } else {
+ if ($e) {
+ $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
+ }
+ continue;
+ }
+ } else {
+ // strip comments
+ if ($e) {
+ $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
+ }
+ continue;
+ }
+ } elseif ($token instanceof HTMLPurifier_Token_Text) {
+ } else {
+ continue;
+ }
+ $result[] = $token;
+ }
+ if ($remove_until && $e) {
+ // we removed tokens until the end, throw error
+ $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until);
+ }
+ $context->destroy('CurrentToken');
+ return $result;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php
new file mode 100644
index 000000000..fbb3d27c8
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php
@@ -0,0 +1,45 @@
+register('CurrentToken', $token);
+
+ foreach ($tokens as $key => $token) {
+
+ // only process tokens that have attributes,
+ // namely start and empty tags
+ if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) {
+ continue;
+ }
+
+ // skip tokens that are armored
+ if (!empty($token->armor['ValidateAttributes'])) {
+ continue;
+ }
+
+ // note that we have no facilities here for removing tokens
+ $validator->validateToken($token, $config, $context);
+ }
+ $context->destroy('CurrentToken');
+ return $tokens;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php
new file mode 100644
index 000000000..c41ae3a76
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php
@@ -0,0 +1,48 @@
+accessed[$index] = true;
+ return parent::offsetGet($index);
+ }
+
+ /**
+ * Returns a lookup array of all array indexes that have been accessed.
+ * @return array in form array($index => true).
+ */
+ public function getAccessed()
+ {
+ return $this->accessed;
+ }
+
+ /**
+ * Resets the access array.
+ */
+ public function resetAccessed()
+ {
+ $this->accessed = array();
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php
new file mode 100644
index 000000000..7c73f8083
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php
@@ -0,0 +1,136 @@
+ 'DefaultKeyValue',
+ * 'KEY' => 'Value',
+ * 'KEY2' => 'Value2',
+ * 'MULTILINE-KEY' => "Multiline\nvalue.\n",
+ * )
+ *
+ * We use this as an easy to use file-format for configuration schema
+ * files, but the class itself is usage agnostic.
+ *
+ * You can use ---- to forcibly terminate parsing of a single string-hash;
+ * this marker is used in multi string-hashes to delimit boundaries.
+ */
+class HTMLPurifier_StringHashParser
+{
+
+ /**
+ * @type string
+ */
+ public $default = 'ID';
+
+ /**
+ * Parses a file that contains a single string-hash.
+ * @param string $file
+ * @return array
+ */
+ public function parseFile($file)
+ {
+ if (!file_exists($file)) {
+ return false;
+ }
+ $fh = fopen($file, 'r');
+ if (!$fh) {
+ return false;
+ }
+ $ret = $this->parseHandle($fh);
+ fclose($fh);
+ return $ret;
+ }
+
+ /**
+ * Parses a file that contains multiple string-hashes delimited by '----'
+ * @param string $file
+ * @return array
+ */
+ public function parseMultiFile($file)
+ {
+ if (!file_exists($file)) {
+ return false;
+ }
+ $ret = array();
+ $fh = fopen($file, 'r');
+ if (!$fh) {
+ return false;
+ }
+ while (!feof($fh)) {
+ $ret[] = $this->parseHandle($fh);
+ }
+ fclose($fh);
+ return $ret;
+ }
+
+ /**
+ * Internal parser that acepts a file handle.
+ * @note While it's possible to simulate in-memory parsing by using
+ * custom stream wrappers, if such a use-case arises we should
+ * factor out the file handle into its own class.
+ * @param resource $fh File handle with pointer at start of valid string-hash
+ * block.
+ * @return array
+ */
+ protected function parseHandle($fh)
+ {
+ $state = false;
+ $single = false;
+ $ret = array();
+ do {
+ $line = fgets($fh);
+ if ($line === false) {
+ break;
+ }
+ $line = rtrim($line, "\n\r");
+ if (!$state && $line === '') {
+ continue;
+ }
+ if ($line === '----') {
+ break;
+ }
+ if (strncmp('--#', $line, 3) === 0) {
+ // Comment
+ continue;
+ } elseif (strncmp('--', $line, 2) === 0) {
+ // Multiline declaration
+ $state = trim($line, '- ');
+ if (!isset($ret[$state])) {
+ $ret[$state] = '';
+ }
+ continue;
+ } elseif (!$state) {
+ $single = true;
+ if (strpos($line, ':') !== false) {
+ // Single-line declaration
+ list($state, $line) = explode(':', $line, 2);
+ $line = trim($line);
+ } else {
+ // Use default declaration
+ $state = $this->default;
+ }
+ }
+ if ($single) {
+ $ret[$state] = $line;
+ $single = false;
+ $state = false;
+ } else {
+ $ret[$state] .= "$line\n";
+ }
+ } while (!feof($fh));
+ return $ret;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php
new file mode 100644
index 000000000..7b8d83343
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php
@@ -0,0 +1,37 @@
+ 'xx-small',
+ '1' => 'xx-small',
+ '2' => 'small',
+ '3' => 'medium',
+ '4' => 'large',
+ '5' => 'x-large',
+ '6' => 'xx-large',
+ '7' => '300%',
+ '-1' => 'smaller',
+ '-2' => '60%',
+ '+1' => 'larger',
+ '+2' => '150%',
+ '+3' => '200%',
+ '+4' => '300%'
+ );
+
+ /**
+ * @param HTMLPurifier_Token_Tag $tag
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return HTMLPurifier_Token_End|string
+ */
+ public function transform($tag, $config, $context)
+ {
+ if ($tag instanceof HTMLPurifier_Token_End) {
+ $new_tag = clone $tag;
+ $new_tag->name = $this->transform_to;
+ return $new_tag;
+ }
+
+ $attr = $tag->attr;
+ $prepend_style = '';
+
+ // handle color transform
+ if (isset($attr['color'])) {
+ $prepend_style .= 'color:' . $attr['color'] . ';';
+ unset($attr['color']);
+ }
+
+ // handle face transform
+ if (isset($attr['face'])) {
+ $prepend_style .= 'font-family:' . $attr['face'] . ';';
+ unset($attr['face']);
+ }
+
+ // handle size transform
+ if (isset($attr['size'])) {
+ // normalize large numbers
+ if ($attr['size'] !== '') {
+ if ($attr['size'][0] == '+' || $attr['size'][0] == '-') {
+ $size = (int)$attr['size'];
+ if ($size < -2) {
+ $attr['size'] = '-2';
+ }
+ if ($size > 4) {
+ $attr['size'] = '+4';
+ }
+ } else {
+ $size = (int)$attr['size'];
+ if ($size > 7) {
+ $attr['size'] = '7';
+ }
+ }
+ }
+ if (isset($this->_size_lookup[$attr['size']])) {
+ $prepend_style .= 'font-size:' .
+ $this->_size_lookup[$attr['size']] . ';';
+ }
+ unset($attr['size']);
+ }
+
+ if ($prepend_style) {
+ $attr['style'] = isset($attr['style']) ?
+ $prepend_style . $attr['style'] :
+ $prepend_style;
+ }
+
+ $new_tag = clone $tag;
+ $new_tag->name = $this->transform_to;
+ $new_tag->attr = $attr;
+
+ return $new_tag;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php
new file mode 100644
index 000000000..71bf10b91
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php
@@ -0,0 +1,44 @@
+transform_to = $transform_to;
+ $this->style = $style;
+ }
+
+ /**
+ * @param HTMLPurifier_Token_Tag $tag
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return string
+ */
+ public function transform($tag, $config, $context)
+ {
+ $new_tag = clone $tag;
+ $new_tag->name = $this->transform_to;
+ if (!is_null($this->style) &&
+ ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty)
+ ) {
+ $this->prependCSS($new_tag->attr, $this->style);
+ }
+ return $new_tag;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php
new file mode 100644
index 000000000..84d3619a3
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php
@@ -0,0 +1,100 @@
+line = $l;
+ $this->col = $c;
+ }
+
+ /**
+ * Convenience function for DirectLex settings line/col position.
+ * @param int $l
+ * @param int $c
+ */
+ public function rawPosition($l, $c)
+ {
+ if ($c === -1) {
+ $l++;
+ }
+ $this->line = $l;
+ $this->col = $c;
+ }
+
+ /**
+ * Converts a token into its corresponding node.
+ */
+ abstract public function toNode();
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php
new file mode 100644
index 000000000..23453c705
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php
@@ -0,0 +1,38 @@
+data = $data;
+ $this->line = $line;
+ $this->col = $col;
+ }
+
+ public function toNode() {
+ return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php
new file mode 100644
index 000000000..78a95f555
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php
@@ -0,0 +1,15 @@
+empty = true;
+ return $n;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php
new file mode 100644
index 000000000..59b38fdc5
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php
@@ -0,0 +1,24 @@
+toNode not supported!");
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php
new file mode 100644
index 000000000..019f317ad
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php
@@ -0,0 +1,10 @@
+!empty($obj->is_tag)
+ * without having to use a function call is_a().
+ * @type bool
+ */
+ public $is_tag = true;
+
+ /**
+ * The lower-case name of the tag, like 'a', 'b' or 'blockquote'.
+ *
+ * @note Strictly speaking, XML tags are case sensitive, so we shouldn't
+ * be lower-casing them, but these tokens cater to HTML tags, which are
+ * insensitive.
+ * @type string
+ */
+ public $name;
+
+ /**
+ * Associative array of the tag's attributes.
+ * @type array
+ */
+ public $attr = array();
+
+ /**
+ * Non-overloaded constructor, which lower-cases passed tag name.
+ *
+ * @param string $name String name.
+ * @param array $attr Associative array of attributes.
+ * @param int $line
+ * @param int $col
+ * @param array $armor
+ */
+ public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array())
+ {
+ $this->name = ctype_lower($name) ? $name : strtolower($name);
+ foreach ($attr as $key => $value) {
+ // normalization only necessary when key is not lowercase
+ if (!ctype_lower($key)) {
+ $new_key = strtolower($key);
+ if (!isset($attr[$new_key])) {
+ $attr[$new_key] = $attr[$key];
+ }
+ if ($new_key !== $key) {
+ unset($attr[$key]);
+ }
+ }
+ }
+ $this->attr = $attr;
+ $this->line = $line;
+ $this->col = $col;
+ $this->armor = $armor;
+ }
+
+ public function toNode() {
+ return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php
new file mode 100644
index 000000000..f26a1c211
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php
@@ -0,0 +1,53 @@
+data = $data;
+ $this->is_whitespace = ctype_space($data);
+ $this->line = $line;
+ $this->col = $col;
+ }
+
+ public function toNode() {
+ return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php
new file mode 100644
index 000000000..dea2446b9
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php
@@ -0,0 +1,118 @@
+p_start = new HTMLPurifier_Token_Start('', array());
+ $this->p_end = new HTMLPurifier_Token_End('');
+ $this->p_empty = new HTMLPurifier_Token_Empty('', array());
+ $this->p_text = new HTMLPurifier_Token_Text('');
+ $this->p_comment = new HTMLPurifier_Token_Comment('');
+ }
+
+ /**
+ * Creates a HTMLPurifier_Token_Start.
+ * @param string $name Tag name
+ * @param array $attr Associative array of attributes
+ * @return HTMLPurifier_Token_Start Generated HTMLPurifier_Token_Start
+ */
+ public function createStart($name, $attr = array())
+ {
+ $p = clone $this->p_start;
+ $p->__construct($name, $attr);
+ return $p;
+ }
+
+ /**
+ * Creates a HTMLPurifier_Token_End.
+ * @param string $name Tag name
+ * @return HTMLPurifier_Token_End Generated HTMLPurifier_Token_End
+ */
+ public function createEnd($name)
+ {
+ $p = clone $this->p_end;
+ $p->__construct($name);
+ return $p;
+ }
+
+ /**
+ * Creates a HTMLPurifier_Token_Empty.
+ * @param string $name Tag name
+ * @param array $attr Associative array of attributes
+ * @return HTMLPurifier_Token_Empty Generated HTMLPurifier_Token_Empty
+ */
+ public function createEmpty($name, $attr = array())
+ {
+ $p = clone $this->p_empty;
+ $p->__construct($name, $attr);
+ return $p;
+ }
+
+ /**
+ * Creates a HTMLPurifier_Token_Text.
+ * @param string $data Data of text token
+ * @return HTMLPurifier_Token_Text Generated HTMLPurifier_Token_Text
+ */
+ public function createText($data)
+ {
+ $p = clone $this->p_text;
+ $p->__construct($data);
+ return $p;
+ }
+
+ /**
+ * Creates a HTMLPurifier_Token_Comment.
+ * @param string $data Data of comment token
+ * @return HTMLPurifier_Token_Comment Generated HTMLPurifier_Token_Comment
+ */
+ public function createComment($data)
+ {
+ $p = clone $this->p_comment;
+ $p->__construct($data);
+ return $p;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php
new file mode 100644
index 000000000..9c5be39d1
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php
@@ -0,0 +1,316 @@
+scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme);
+ $this->userinfo = $userinfo;
+ $this->host = $host;
+ $this->port = is_null($port) ? $port : (int)$port;
+ $this->path = $path;
+ $this->query = $query;
+ $this->fragment = $fragment;
+ }
+
+ /**
+ * Retrieves a scheme object corresponding to the URI's scheme/default
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return HTMLPurifier_URIScheme Scheme object appropriate for validating this URI
+ */
+ public function getSchemeObj($config, $context)
+ {
+ $registry = HTMLPurifier_URISchemeRegistry::instance();
+ if ($this->scheme !== null) {
+ $scheme_obj = $registry->getScheme($this->scheme, $config, $context);
+ if (!$scheme_obj) {
+ return false;
+ } // invalid scheme, clean it out
+ } else {
+ // no scheme: retrieve the default one
+ $def = $config->getDefinition('URI');
+ $scheme_obj = $def->getDefaultScheme($config, $context);
+ if (!$scheme_obj) {
+ if ($def->defaultScheme !== null) {
+ // something funky happened to the default scheme object
+ trigger_error(
+ 'Default scheme object "' . $def->defaultScheme . '" was not readable',
+ E_USER_WARNING
+ );
+ } // suppress error if it's null
+ return false;
+ }
+ }
+ return $scheme_obj;
+ }
+
+ /**
+ * Generic validation method applicable for all schemes. May modify
+ * this URI in order to get it into a compliant form.
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool True if validation/filtering succeeds, false if failure
+ */
+ public function validate($config, $context)
+ {
+ // ABNF definitions from RFC 3986
+ $chars_sub_delims = '!$&\'()*+,;=';
+ $chars_gen_delims = ':/?#[]@';
+ $chars_pchar = $chars_sub_delims . ':@';
+
+ // validate host
+ if (!is_null($this->host)) {
+ $host_def = new HTMLPurifier_AttrDef_URI_Host();
+ $this->host = $host_def->validate($this->host, $config, $context);
+ if ($this->host === false) {
+ $this->host = null;
+ }
+ }
+
+ // validate scheme
+ // NOTE: It's not appropriate to check whether or not this
+ // scheme is in our registry, since a URIFilter may convert a
+ // URI that we don't allow into one we do. So instead, we just
+ // check if the scheme can be dropped because there is no host
+ // and it is our default scheme.
+ if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') {
+ // support for relative paths is pretty abysmal when the
+ // scheme is present, so axe it when possible
+ $def = $config->getDefinition('URI');
+ if ($def->defaultScheme === $this->scheme) {
+ $this->scheme = null;
+ }
+ }
+
+ // validate username
+ if (!is_null($this->userinfo)) {
+ $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':');
+ $this->userinfo = $encoder->encode($this->userinfo);
+ }
+
+ // validate port
+ if (!is_null($this->port)) {
+ if ($this->port < 1 || $this->port > 65535) {
+ $this->port = null;
+ }
+ }
+
+ // validate path
+ $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/');
+ if (!is_null($this->host)) { // this catches $this->host === ''
+ // path-abempty (hier and relative)
+ // http://www.example.com/my/path
+ // //www.example.com/my/path (looks odd, but works, and
+ // recognized by most browsers)
+ // (this set is valid or invalid on a scheme by scheme
+ // basis, so we'll deal with it later)
+ // file:///my/path
+ // ///my/path
+ $this->path = $segments_encoder->encode($this->path);
+ } elseif ($this->path !== '') {
+ if ($this->path[0] === '/') {
+ // path-absolute (hier and relative)
+ // http:/my/path
+ // /my/path
+ if (strlen($this->path) >= 2 && $this->path[1] === '/') {
+ // This could happen if both the host gets stripped
+ // out
+ // http://my/path
+ // //my/path
+ $this->path = '';
+ } else {
+ $this->path = $segments_encoder->encode($this->path);
+ }
+ } elseif (!is_null($this->scheme)) {
+ // path-rootless (hier)
+ // http:my/path
+ // Short circuit evaluation means we don't need to check nz
+ $this->path = $segments_encoder->encode($this->path);
+ } else {
+ // path-noscheme (relative)
+ // my/path
+ // (once again, not checking nz)
+ $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@');
+ $c = strpos($this->path, '/');
+ if ($c !== false) {
+ $this->path =
+ $segment_nc_encoder->encode(substr($this->path, 0, $c)) .
+ $segments_encoder->encode(substr($this->path, $c));
+ } else {
+ $this->path = $segment_nc_encoder->encode($this->path);
+ }
+ }
+ } else {
+ // path-empty (hier and relative)
+ $this->path = ''; // just to be safe
+ }
+
+ // qf = query and fragment
+ $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?');
+
+ if (!is_null($this->query)) {
+ $this->query = $qf_encoder->encode($this->query);
+ }
+
+ if (!is_null($this->fragment)) {
+ $this->fragment = $qf_encoder->encode($this->fragment);
+ }
+ return true;
+ }
+
+ /**
+ * Convert URI back to string
+ * @return string URI appropriate for output
+ */
+ public function toString()
+ {
+ // reconstruct authority
+ $authority = null;
+ // there is a rendering difference between a null authority
+ // (http:foo-bar) and an empty string authority
+ // (http:///foo-bar).
+ if (!is_null($this->host)) {
+ $authority = '';
+ if (!is_null($this->userinfo)) {
+ $authority .= $this->userinfo . '@';
+ }
+ $authority .= $this->host;
+ if (!is_null($this->port)) {
+ $authority .= ':' . $this->port;
+ }
+ }
+
+ // Reconstruct the result
+ // One might wonder about parsing quirks from browsers after
+ // this reconstruction. Unfortunately, parsing behavior depends
+ // on what *scheme* was employed (file:///foo is handled *very*
+ // differently than http:///foo), so unfortunately we have to
+ // defer to the schemes to do the right thing.
+ $result = '';
+ if (!is_null($this->scheme)) {
+ $result .= $this->scheme . ':';
+ }
+ if (!is_null($authority)) {
+ $result .= '//' . $authority;
+ }
+ $result .= $this->path;
+ if (!is_null($this->query)) {
+ $result .= '?' . $this->query;
+ }
+ if (!is_null($this->fragment)) {
+ $result .= '#' . $this->fragment;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns true if this URL might be considered a 'local' URL given
+ * the current context. This is true when the host is null, or
+ * when it matches the host supplied to the configuration.
+ *
+ * Note that this does not do any scheme checking, so it is mostly
+ * only appropriate for metadata that doesn't care about protocol
+ * security. isBenign is probably what you actually want.
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function isLocal($config, $context)
+ {
+ if ($this->host === null) {
+ return true;
+ }
+ $uri_def = $config->getDefinition('URI');
+ if ($uri_def->host === $this->host) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this URL should be considered a 'benign' URL,
+ * that is:
+ *
+ * - It is a local URL (isLocal), and
+ * - It has a equal or better level of security
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function isBenign($config, $context)
+ {
+ if (!$this->isLocal($config, $context)) {
+ return false;
+ }
+
+ $scheme_obj = $this->getSchemeObj($config, $context);
+ if (!$scheme_obj) {
+ return false;
+ } // conservative approach
+
+ $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context);
+ if ($current_scheme_obj->secure) {
+ if (!$scheme_obj->secure) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php
new file mode 100644
index 000000000..e0bd8bcca
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php
@@ -0,0 +1,112 @@
+registerFilter(new HTMLPurifier_URIFilter_DisableExternal());
+ $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources());
+ $this->registerFilter(new HTMLPurifier_URIFilter_DisableResources());
+ $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist());
+ $this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe());
+ $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute());
+ $this->registerFilter(new HTMLPurifier_URIFilter_Munge());
+ }
+
+ public function registerFilter($filter)
+ {
+ $this->registeredFilters[$filter->name] = $filter;
+ }
+
+ public function addFilter($filter, $config)
+ {
+ $r = $filter->prepare($config);
+ if ($r === false) return; // null is ok, for backwards compat
+ if ($filter->post) {
+ $this->postFilters[$filter->name] = $filter;
+ } else {
+ $this->filters[$filter->name] = $filter;
+ }
+ }
+
+ protected function doSetup($config)
+ {
+ $this->setupMemberVariables($config);
+ $this->setupFilters($config);
+ }
+
+ protected function setupFilters($config)
+ {
+ foreach ($this->registeredFilters as $name => $filter) {
+ if ($filter->always_load) {
+ $this->addFilter($filter, $config);
+ } else {
+ $conf = $config->get('URI.' . $name);
+ if ($conf !== false && $conf !== null) {
+ $this->addFilter($filter, $config);
+ }
+ }
+ }
+ unset($this->registeredFilters);
+ }
+
+ protected function setupMemberVariables($config)
+ {
+ $this->host = $config->get('URI.Host');
+ $base_uri = $config->get('URI.Base');
+ if (!is_null($base_uri)) {
+ $parser = new HTMLPurifier_URIParser();
+ $this->base = $parser->parse($base_uri);
+ $this->defaultScheme = $this->base->scheme;
+ if (is_null($this->host)) $this->host = $this->base->host;
+ }
+ if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI.DefaultScheme');
+ }
+
+ public function getDefaultScheme($config, $context)
+ {
+ return HTMLPurifier_URISchemeRegistry::instance()->getScheme($this->defaultScheme, $config, $context);
+ }
+
+ public function filter(&$uri, $config, $context)
+ {
+ foreach ($this->filters as $name => $f) {
+ $result = $f->filter($uri, $config, $context);
+ if (!$result) return false;
+ }
+ return true;
+ }
+
+ public function postFilter(&$uri, $config, $context)
+ {
+ foreach ($this->postFilters as $name => $f) {
+ $result = $f->filter($uri, $config, $context);
+ if (!$result) return false;
+ }
+ return true;
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php
new file mode 100644
index 000000000..09724e9f4
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php
@@ -0,0 +1,74 @@
+getDefinition('URI')->host;
+ if ($our_host !== null) {
+ $this->ourHostParts = array_reverse(explode('.', $our_host));
+ }
+ }
+
+ /**
+ * @param HTMLPurifier_URI $uri Reference
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function filter(&$uri, $config, $context)
+ {
+ if (is_null($uri->host)) {
+ return true;
+ }
+ if ($this->ourHostParts === false) {
+ return false;
+ }
+ $host_parts = array_reverse(explode('.', $uri->host));
+ foreach ($this->ourHostParts as $i => $x) {
+ if (!isset($host_parts[$i])) {
+ return false;
+ }
+ if ($host_parts[$i] != $this->ourHostParts[$i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php
new file mode 100644
index 000000000..c6562169e
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php
@@ -0,0 +1,25 @@
+get('EmbeddedURI', true)) {
+ return true;
+ }
+ return parent::filter($uri, $config, $context);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php
new file mode 100644
index 000000000..d5c412c44
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php
@@ -0,0 +1,22 @@
+get('EmbeddedURI', true);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php
new file mode 100644
index 000000000..32197c0e6
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php
@@ -0,0 +1,46 @@
+blacklist = $config->get('URI.HostBlacklist');
+ return true;
+ }
+
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function filter(&$uri, $config, $context)
+ {
+ foreach ($this->blacklist as $blacklisted_host_fragment) {
+ if ($uri->host !== null && strpos($uri->host, $blacklisted_host_fragment) !== false) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php
new file mode 100644
index 000000000..c507bbff8
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php
@@ -0,0 +1,158 @@
+getDefinition('URI');
+ $this->base = $def->base;
+ if (is_null($this->base)) {
+ trigger_error(
+ 'URI.MakeAbsolute is being ignored due to lack of ' .
+ 'value for URI.Base configuration',
+ E_USER_WARNING
+ );
+ return false;
+ }
+ $this->base->fragment = null; // fragment is invalid for base URI
+ $stack = explode('/', $this->base->path);
+ array_pop($stack); // discard last segment
+ $stack = $this->_collapseStack($stack); // do pre-parsing
+ $this->basePathStack = $stack;
+ return true;
+ }
+
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function filter(&$uri, $config, $context)
+ {
+ if (is_null($this->base)) {
+ return true;
+ } // abort early
+ if ($uri->path === '' && is_null($uri->scheme) &&
+ is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) {
+ // reference to current document
+ $uri = clone $this->base;
+ return true;
+ }
+ if (!is_null($uri->scheme)) {
+ // absolute URI already: don't change
+ if (!is_null($uri->host)) {
+ return true;
+ }
+ $scheme_obj = $uri->getSchemeObj($config, $context);
+ if (!$scheme_obj) {
+ // scheme not recognized
+ return false;
+ }
+ if (!$scheme_obj->hierarchical) {
+ // non-hierarchal URI with explicit scheme, don't change
+ return true;
+ }
+ // special case: had a scheme but always is hierarchical and had no authority
+ }
+ if (!is_null($uri->host)) {
+ // network path, don't bother
+ return true;
+ }
+ if ($uri->path === '') {
+ $uri->path = $this->base->path;
+ } elseif ($uri->path[0] !== '/') {
+ // relative path, needs more complicated processing
+ $stack = explode('/', $uri->path);
+ $new_stack = array_merge($this->basePathStack, $stack);
+ if ($new_stack[0] !== '' && !is_null($this->base->host)) {
+ array_unshift($new_stack, '');
+ }
+ $new_stack = $this->_collapseStack($new_stack);
+ $uri->path = implode('/', $new_stack);
+ } else {
+ // absolute path, but still we should collapse
+ $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path)));
+ }
+ // re-combine
+ $uri->scheme = $this->base->scheme;
+ if (is_null($uri->userinfo)) {
+ $uri->userinfo = $this->base->userinfo;
+ }
+ if (is_null($uri->host)) {
+ $uri->host = $this->base->host;
+ }
+ if (is_null($uri->port)) {
+ $uri->port = $this->base->port;
+ }
+ return true;
+ }
+
+ /**
+ * Resolve dots and double-dots in a path stack
+ * @param array $stack
+ * @return array
+ */
+ private function _collapseStack($stack)
+ {
+ $result = array();
+ $is_folder = false;
+ for ($i = 0; isset($stack[$i]); $i++) {
+ $is_folder = false;
+ // absorb an internally duplicated slash
+ if ($stack[$i] == '' && $i && isset($stack[$i + 1])) {
+ continue;
+ }
+ if ($stack[$i] == '..') {
+ if (!empty($result)) {
+ $segment = array_pop($result);
+ if ($segment === '' && empty($result)) {
+ // error case: attempted to back out too far:
+ // restore the leading slash
+ $result[] = '';
+ } elseif ($segment === '..') {
+ $result[] = '..'; // cannot remove .. with ..
+ }
+ } else {
+ // relative path, preserve the double-dots
+ $result[] = '..';
+ }
+ $is_folder = true;
+ continue;
+ }
+ if ($stack[$i] == '.') {
+ // silently absorb
+ $is_folder = true;
+ continue;
+ }
+ $result[] = $stack[$i];
+ }
+ if ($is_folder) {
+ $result[] = '';
+ }
+ return $result;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php
new file mode 100644
index 000000000..e1393deb7
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php
@@ -0,0 +1,115 @@
+target = $config->get('URI.' . $this->name);
+ $this->parser = new HTMLPurifier_URIParser();
+ $this->doEmbed = $config->get('URI.MungeResources');
+ $this->secretKey = $config->get('URI.MungeSecretKey');
+ if ($this->secretKey && !function_exists('hash_hmac')) {
+ throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support.");
+ }
+ return true;
+ }
+
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function filter(&$uri, $config, $context)
+ {
+ if ($context->get('EmbeddedURI', true) && !$this->doEmbed) {
+ return true;
+ }
+
+ $scheme_obj = $uri->getSchemeObj($config, $context);
+ if (!$scheme_obj) {
+ return true;
+ } // ignore unknown schemes, maybe another postfilter did it
+ if (!$scheme_obj->browsable) {
+ return true;
+ } // ignore non-browseable schemes, since we can't munge those in a reasonable way
+ if ($uri->isBenign($config, $context)) {
+ return true;
+ } // don't redirect if a benign URL
+
+ $this->makeReplace($uri, $config, $context);
+ $this->replace = array_map('rawurlencode', $this->replace);
+
+ $new_uri = strtr($this->target, $this->replace);
+ $new_uri = $this->parser->parse($new_uri);
+ // don't redirect if the target host is the same as the
+ // starting host
+ if ($uri->host === $new_uri->host) {
+ return true;
+ }
+ $uri = $new_uri; // overwrite
+ return true;
+ }
+
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ */
+ protected function makeReplace($uri, $config, $context)
+ {
+ $string = $uri->toString();
+ // always available
+ $this->replace['%s'] = $string;
+ $this->replace['%r'] = $context->get('EmbeddedURI', true) ?: '';
+ $token = $context->get('CurrentToken', true) ?: '';
+ $this->replace['%n'] = $token ? $token->name : '';
+ $this->replace['%m'] = $context->get('CurrentAttr', true) ?: '';
+ $this->replace['%p'] = $context->get('CurrentCSSProperty', true) ?: '';
+ // not always available
+ if ($this->secretKey) {
+ $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey);
+ }
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php
new file mode 100644
index 000000000..f609c47a3
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php
@@ -0,0 +1,68 @@
+regexp = $config->get('URI.SafeIframeRegexp');
+ return true;
+ }
+
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function filter(&$uri, $config, $context)
+ {
+ // check if filter not applicable
+ if (!$config->get('HTML.SafeIframe')) {
+ return true;
+ }
+ // check if the filter should actually trigger
+ if (!$context->get('EmbeddedURI', true)) {
+ return true;
+ }
+ $token = $context->get('CurrentToken', true);
+ if (!($token && $token->name == 'iframe')) {
+ return true;
+ }
+ // check if we actually have some whitelists enabled
+ if ($this->regexp === null) {
+ return false;
+ }
+ // actually check the whitelists
+ return preg_match($this->regexp, $uri->toString());
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php
new file mode 100644
index 000000000..0e7381a07
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php
@@ -0,0 +1,71 @@
+percentEncoder = new HTMLPurifier_PercentEncoder();
+ }
+
+ /**
+ * Parses a URI.
+ * @param $uri string URI to parse
+ * @return HTMLPurifier_URI representation of URI. This representation has
+ * not been validated yet and may not conform to RFC.
+ */
+ public function parse($uri)
+ {
+ $uri = $this->percentEncoder->normalize($uri);
+
+ // Regexp is as per Appendix B.
+ // Note that ["<>] are an addition to the RFC's recommended
+ // characters, because they represent external delimeters.
+ $r_URI = '!'.
+ '(([a-zA-Z0-9\.\+\-]+):)?'. // 2. Scheme
+ '(//([^/?#"<>]*))?'. // 4. Authority
+ '([^?#"<>]*)'. // 5. Path
+ '(\?([^#"<>]*))?'. // 7. Query
+ '(#([^"<>]*))?'. // 8. Fragment
+ '!';
+
+ $matches = array();
+ $result = preg_match($r_URI, $uri, $matches);
+
+ if (!$result) return false; // *really* invalid URI
+
+ // seperate out parts
+ $scheme = !empty($matches[1]) ? $matches[2] : null;
+ $authority = !empty($matches[3]) ? $matches[4] : null;
+ $path = $matches[5]; // always present, can be empty
+ $query = !empty($matches[6]) ? $matches[7] : null;
+ $fragment = !empty($matches[8]) ? $matches[9] : null;
+
+ // further parse authority
+ if ($authority !== null) {
+ $r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/";
+ $matches = array();
+ preg_match($r_authority, $authority, $matches);
+ $userinfo = !empty($matches[1]) ? $matches[2] : null;
+ $host = !empty($matches[3]) ? $matches[3] : '';
+ $port = !empty($matches[4]) ? (int) $matches[5] : null;
+ } else {
+ $port = $host = $userinfo = null;
+ }
+
+ return new HTMLPurifier_URI(
+ $scheme, $userinfo, $host, $port, $path, $query, $fragment);
+ }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php
new file mode 100644
index 000000000..fe9e82cf2
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php
@@ -0,0 +1,102 @@
+, resolves edge cases
+ * with making relative URIs absolute
+ * @type bool
+ */
+ public $hierarchical = false;
+
+ /**
+ * Whether or not the URI may omit a hostname when the scheme is
+ * explicitly specified, ala file:///path/to/file. As of writing,
+ * 'file' is the only scheme that browsers support his properly.
+ * @type bool
+ */
+ public $may_omit_host = false;
+
+ /**
+ * Validates the components of a URI for a specific scheme.
+ * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool success or failure
+ */
+ abstract public function doValidate(&$uri, $config, $context);
+
+ /**
+ * Public interface for validating components of a URI. Performs a
+ * bunch of default actions. Don't overload this method.
+ * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool success or failure
+ */
+ public function validate(&$uri, $config, $context)
+ {
+ if ($this->default_port == $uri->port) {
+ $uri->port = null;
+ }
+ // kludge: browsers do funny things when the scheme but not the
+ // authority is set
+ if (!$this->may_omit_host &&
+ // if the scheme is present, a missing host is always in error
+ (!is_null($uri->scheme) && ($uri->host === '' || is_null($uri->host))) ||
+ // if the scheme is not present, a *blank* host is in error,
+ // since this translates into '///path' which most browsers
+ // interpret as being 'http://path'.
+ (is_null($uri->scheme) && $uri->host === '')
+ ) {
+ do {
+ if (is_null($uri->scheme)) {
+ if (substr($uri->path, 0, 2) != '//') {
+ $uri->host = null;
+ break;
+ }
+ // URI is '////path', so we cannot nullify the
+ // host to preserve semantics. Try expanding the
+ // hostname instead (fall through)
+ }
+ // first see if we can manually insert a hostname
+ $host = $config->get('URI.Host');
+ if (!is_null($host)) {
+ $uri->host = $host;
+ } else {
+ // we can't do anything sensible, reject the URL.
+ return false;
+ }
+ } while (false);
+ }
+ return $this->doValidate($uri, $config, $context);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php
new file mode 100644
index 000000000..41c49d553
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php
@@ -0,0 +1,136 @@
+ true,
+ 'image/gif' => true,
+ 'image/png' => true,
+ );
+ // this is actually irrelevant since we only write out the path
+ // component
+ /**
+ * @type bool
+ */
+ public $may_omit_host = true;
+
+ /**
+ * @param HTMLPurifier_URI $uri
+ * @param HTMLPurifier_Config $config
+ * @param HTMLPurifier_Context $context
+ * @return bool
+ */
+ public function doValidate(&$uri, $config, $context)
+ {
+ $result = explode(',', $uri->path, 2);
+ $is_base64 = false;
+ $charset = null;
+ $content_type = null;
+ if (count($result) == 2) {
+ list($metadata, $data) = $result;
+ // do some legwork on the metadata
+ $metas = explode(';', $metadata);
+ while (!empty($metas)) {
+ $cur = array_shift($metas);
+ if ($cur == 'base64') {
+ $is_base64 = true;
+ break;
+ }
+ if (substr($cur, 0, 8) == 'charset=') {
+ // doesn't match if there are arbitrary spaces, but
+ // whatever dude
+ if ($charset !== null) {
+ continue;
+ } // garbage
+ $charset = substr($cur, 8); // not used
+ } else {
+ if ($content_type !== null) {
+ continue;
+ } // garbage
+ $content_type = $cur;
+ }
+ }
+ } else {
+ $data = $result[0];
+ }
+ if ($content_type !== null && empty($this->allowed_types[$content_type])) {
+ return false;
+ }
+ if ($charset !== null) {
+ // error; we don't allow plaintext stuff
+ $charset = null;
+ }
+ $data = rawurldecode($data);
+ if ($is_base64) {
+ $raw_data = base64_decode($data);
+ } else {
+ $raw_data = $data;
+ }
+ if ( strlen($raw_data) < 12 ) {
+ // error; exif_imagetype throws exception with small files,
+ // and this likely indicates a corrupt URI/failed parse anyway
+ return false;
+ }
+ // XXX probably want to refactor this into a general mechanism
+ // for filtering arbitrary content types
+ if (function_exists('sys_get_temp_dir')) {
+ $file = tempnam(sys_get_temp_dir(), "");
+ } else {
+ $file = tempnam("/tmp", "");
+ }
+ file_put_contents($file, $raw_data);
+ if (function_exists('exif_imagetype')) {
+ $image_code = exif_imagetype($file);
+ unlink($file);
+ } elseif (function_exists('getimagesize')) {
+ set_error_handler(array($this, 'muteErrorHandler'));
+ $info = getimagesize($file);
+ restore_error_handler();
+ unlink($file);
+ if ($info == false) {
+ return false;
+ }
+ $image_code = $info[2];
+ } else {
+ trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR);
+ }
+ $real_content_type = image_type_to_mime_type($image_code);
+ if ($real_content_type != $content_type) {
+ // we're nice guys; if the content type is something else we
+ // support, change it over
+ if (empty($this->allowed_types[$real_content_type])) {
+ return false;
+ }
+ $content_type = $real_content_type;
+ }
+ // ok, it's kosher, rewrite what we need
+ $uri->userinfo = null;
+ $uri->host = null;
+ $uri->port = null;
+ $uri->fragment = null;
+ $uri->query = null;
+ $uri->path = "$content_type;base64," . base64_encode($raw_data);
+ return true;
+ }
+
+ /**
+ * @param int $errno
+ * @param string $errstr
+ */
+ public function muteErrorHandler($errno, $errstr)
+ {
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php
new file mode 100644
index 000000000..215be4ba8
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php
@@ -0,0 +1,44 @@
+userinfo = null;
+ // file:// makes no provisions for accessing the resource
+ $uri->port = null;
+ // While it seems to work on Firefox, the querystring has
+ // no possible effect and is thus stripped.
+ $uri->query = null;
+ return true;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php
new file mode 100644
index 000000000..1eb43ee5c
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php
@@ -0,0 +1,58 @@
+query = null;
+
+ // typecode check
+ $semicolon_pos = strrpos($uri->path, ';'); // reverse
+ if ($semicolon_pos !== false) {
+ $type = substr($uri->path, $semicolon_pos + 1); // no semicolon
+ $uri->path = substr($uri->path, 0, $semicolon_pos);
+ $type_ret = '';
+ if (strpos($type, '=') !== false) {
+ // figure out whether or not the declaration is correct
+ list($key, $typecode) = explode('=', $type, 2);
+ if ($key !== 'type') {
+ // invalid key, tack it back on encoded
+ $uri->path .= '%3B' . $type;
+ } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') {
+ $type_ret = ";type=$typecode";
+ }
+ } else {
+ $uri->path .= '%3B' . $type;
+ }
+ $uri->path = str_replace(';', '%3B', $uri->path);
+ $uri->path .= $type_ret;
+ }
+ return true;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php
new file mode 100644
index 000000000..ce69ec438
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php
@@ -0,0 +1,36 @@
+userinfo = null;
+ return true;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php
new file mode 100644
index 000000000..0e96882db
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php
@@ -0,0 +1,18 @@
+userinfo = null;
+ $uri->host = null;
+ $uri->port = null;
+ // we need to validate path against RFC 2368's addr-spec
+ return true;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php
new file mode 100644
index 000000000..7490927d6
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php
@@ -0,0 +1,35 @@
+userinfo = null;
+ $uri->host = null;
+ $uri->port = null;
+ $uri->query = null;
+ // typecode check needed on path
+ return true;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php
new file mode 100644
index 000000000..f211d715e
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php
@@ -0,0 +1,32 @@
+userinfo = null;
+ $uri->query = null;
+ return true;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php
new file mode 100644
index 000000000..dfad8efcf
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/tel.php
@@ -0,0 +1,46 @@
+userinfo = null;
+ $uri->host = null;
+ $uri->port = null;
+
+ // Delete all non-numeric characters, commas, and non-x characters
+ // from phone number, EXCEPT for a leading plus sign.
+ $uri->path = preg_replace('/(?!^\+)[^\dx,]/', '',
+ // Normalize e(x)tension to lower-case
+ str_replace('X', 'x', rawurldecode($uri->path)));
+
+ return true;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php
new file mode 100644
index 000000000..4ac8a0b76
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php
@@ -0,0 +1,81 @@
+get('URI.AllowedSchemes');
+ if (!$config->get('URI.OverrideAllowedSchemes') &&
+ !isset($allowed_schemes[$scheme])
+ ) {
+ return;
+ }
+
+ if (isset($this->schemes[$scheme])) {
+ return $this->schemes[$scheme];
+ }
+ if (!isset($allowed_schemes[$scheme])) {
+ return;
+ }
+
+ $class = 'HTMLPurifier_URIScheme_' . $scheme;
+ if (!class_exists($class)) {
+ return;
+ }
+ $this->schemes[$scheme] = new $class();
+ return $this->schemes[$scheme];
+ }
+
+ /**
+ * Registers a custom scheme to the cache, bypassing reflection.
+ * @param string $scheme Scheme name
+ * @param HTMLPurifier_URIScheme $scheme_obj
+ */
+ public function register($scheme, $scheme_obj)
+ {
+ $this->schemes[$scheme] = $scheme_obj;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php
new file mode 100644
index 000000000..b5a1eab5c
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php
@@ -0,0 +1,307 @@
+ array(
+ 'px' => 3, // This is as per CSS 2.1 and Firefox. Your mileage may vary
+ 'pt' => 4,
+ 'pc' => 48,
+ 'in' => 288,
+ self::METRIC => array('pt', '0.352777778', 'mm'),
+ ),
+ self::METRIC => array(
+ 'mm' => 1,
+ 'cm' => 10,
+ self::ENGLISH => array('mm', '2.83464567', 'pt'),
+ ),
+ );
+
+ /**
+ * Minimum bcmath precision for output.
+ * @type int
+ */
+ protected $outputPrecision;
+
+ /**
+ * Bcmath precision for internal calculations.
+ * @type int
+ */
+ protected $internalPrecision;
+
+ /**
+ * Whether or not BCMath is available.
+ * @type bool
+ */
+ private $bcmath;
+
+ public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false)
+ {
+ $this->outputPrecision = $output_precision;
+ $this->internalPrecision = $internal_precision;
+ $this->bcmath = !$force_no_bcmath && function_exists('bcmul');
+ }
+
+ /**
+ * Converts a length object of one unit into another unit.
+ * @param HTMLPurifier_Length $length
+ * Instance of HTMLPurifier_Length to convert. You must validate()
+ * it before passing it here!
+ * @param string $to_unit
+ * Unit to convert to.
+ * @return HTMLPurifier_Length|bool
+ * @note
+ * About precision: This conversion function pays very special
+ * attention to the incoming precision of values and attempts
+ * to maintain a number of significant figure. Results are
+ * fairly accurate up to nine digits. Some caveats:
+ * - If a number is zero-padded as a result of this significant
+ * figure tracking, the zeroes will be eliminated.
+ * - If a number contains less than four sigfigs ($outputPrecision)
+ * and this causes some decimals to be excluded, those
+ * decimals will be added on.
+ */
+ public function convert($length, $to_unit)
+ {
+ if (!$length->isValid()) {
+ return false;
+ }
+
+ $n = $length->getN();
+ $unit = $length->getUnit();
+
+ if ($n === '0' || $unit === false) {
+ return new HTMLPurifier_Length('0', false);
+ }
+
+ $state = $dest_state = false;
+ foreach (self::$units as $k => $x) {
+ if (isset($x[$unit])) {
+ $state = $k;
+ }
+ if (isset($x[$to_unit])) {
+ $dest_state = $k;
+ }
+ }
+ if (!$state || !$dest_state) {
+ return false;
+ }
+
+ // Some calculations about the initial precision of the number;
+ // this will be useful when we need to do final rounding.
+ $sigfigs = $this->getSigFigs($n);
+ if ($sigfigs < $this->outputPrecision) {
+ $sigfigs = $this->outputPrecision;
+ }
+
+ // BCMath's internal precision deals only with decimals. Use
+ // our default if the initial number has no decimals, or increase
+ // it by how ever many decimals, thus, the number of guard digits
+ // will always be greater than or equal to internalPrecision.
+ $log = (int)floor(log(abs($n), 10));
+ $cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; // internal precision
+
+ for ($i = 0; $i < 2; $i++) {
+
+ // Determine what unit IN THIS SYSTEM we need to convert to
+ if ($dest_state === $state) {
+ // Simple conversion
+ $dest_unit = $to_unit;
+ } else {
+ // Convert to the smallest unit, pending a system shift
+ $dest_unit = self::$units[$state][$dest_state][0];
+ }
+
+ // Do the conversion if necessary
+ if ($dest_unit !== $unit) {
+ $factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp);
+ $n = $this->mul($n, $factor, $cp);
+ $unit = $dest_unit;
+ }
+
+ // Output was zero, so bail out early. Shouldn't ever happen.
+ if ($n === '') {
+ $n = '0';
+ $unit = $to_unit;
+ break;
+ }
+
+ // It was a simple conversion, so bail out
+ if ($dest_state === $state) {
+ break;
+ }
+
+ if ($i !== 0) {
+ // Conversion failed! Apparently, the system we forwarded
+ // to didn't have this unit. This should never happen!
+ return false;
+ }
+
+ // Pre-condition: $i == 0
+
+ // Perform conversion to next system of units
+ $n = $this->mul($n, self::$units[$state][$dest_state][1], $cp);
+ $unit = self::$units[$state][$dest_state][2];
+ $state = $dest_state;
+
+ // One more loop around to convert the unit in the new system.
+
+ }
+
+ // Post-condition: $unit == $to_unit
+ if ($unit !== $to_unit) {
+ return false;
+ }
+
+ // Useful for debugging:
+ //echo " needs a
n";
+ //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n
\n";
+
+ $n = $this->round($n, $sigfigs);
+ if (strpos($n, '.') !== false) {
+ $n = rtrim($n, '0');
+ }
+ $n = rtrim($n, '.');
+
+ return new HTMLPurifier_Length($n, $unit);
+ }
+
+ /**
+ * Returns the number of significant figures in a string number.
+ * @param string $n Decimal number
+ * @return int number of sigfigs
+ */
+ public function getSigFigs($n)
+ {
+ $n = ltrim($n, '0+-');
+ $dp = strpos($n, '.'); // decimal position
+ if ($dp === false) {
+ $sigfigs = strlen(rtrim($n, '0'));
+ } else {
+ $sigfigs = strlen(ltrim($n, '0.')); // eliminate extra decimal character
+ if ($dp !== 0) {
+ $sigfigs--;
+ }
+ }
+ return $sigfigs;
+ }
+
+ /**
+ * Adds two numbers, using arbitrary precision when available.
+ * @param string $s1
+ * @param string $s2
+ * @param int $scale
+ * @return string
+ */
+ private function add($s1, $s2, $scale)
+ {
+ if ($this->bcmath) {
+ return bcadd($s1, $s2, $scale);
+ } else {
+ return $this->scale((float)$s1 + (float)$s2, $scale);
+ }
+ }
+
+ /**
+ * Multiples two numbers, using arbitrary precision when available.
+ * @param string $s1
+ * @param string $s2
+ * @param int $scale
+ * @return string
+ */
+ private function mul($s1, $s2, $scale)
+ {
+ if ($this->bcmath) {
+ return bcmul($s1, $s2, $scale);
+ } else {
+ return $this->scale((float)$s1 * (float)$s2, $scale);
+ }
+ }
+
+ /**
+ * Divides two numbers, using arbitrary precision when available.
+ * @param string $s1
+ * @param string $s2
+ * @param int $scale
+ * @return string
+ */
+ private function div($s1, $s2, $scale)
+ {
+ if ($this->bcmath) {
+ return bcdiv($s1, $s2, $scale);
+ } else {
+ return $this->scale((float)$s1 / (float)$s2, $scale);
+ }
+ }
+
+ /**
+ * Rounds a number according to the number of sigfigs it should have,
+ * using arbitrary precision when available.
+ * @param float $n
+ * @param int $sigfigs
+ * @return string
+ */
+ private function round($n, $sigfigs)
+ {
+ $new_log = (int)floor(log(abs((float)$n), 10)); // Number of digits left of decimal - 1
+ $rp = $sigfigs - $new_log - 1; // Number of decimal places needed
+ $neg = $n < 0 ? '-' : ''; // Negative sign
+ if ($this->bcmath) {
+ if ($rp >= 0) {
+ $n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1);
+ $n = bcdiv($n, '1', $rp);
+ } else {
+ // This algorithm partially depends on the standardized
+ // form of numbers that comes out of bcmath.
+ $n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs), 0);
+ $n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log - $sigfigs + 1);
+ }
+ return $n;
+ } else {
+ return $this->scale(round((float)$n, $sigfigs - $new_log - 1), $rp + 1);
+ }
+ }
+
+ /**
+ * Scales a float to $scale digits right of decimal point, like BCMath.
+ * @param float $r
+ * @param int $scale
+ * @return string
+ */
+ private function scale($r, $scale)
+ {
+ if ($scale < 0) {
+ // The f sprintf type doesn't support negative numbers, so we
+ // need to cludge things manually. First get the string.
+ $r = sprintf('%.0f', (float)$r);
+ // Due to floating point precision loss, $r will more than likely
+ // look something like 4652999999999.9234. We grab one more digit
+ // than we need to precise from $r and then use that to round
+ // appropriately.
+ $precise = (string)round(substr($r, 0, strlen($r) + $scale), -1);
+ // Now we return it, truncating the zero that was rounded off.
+ return substr($precise, 0, -1) . str_repeat('0', -$scale + 1);
+ }
+ return number_format((float)$r, $scale, '.', '');
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php
new file mode 100644
index 000000000..0c97c8289
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php
@@ -0,0 +1,198 @@
+ self::C_STRING,
+ 'istring' => self::ISTRING,
+ 'text' => self::TEXT,
+ 'itext' => self::ITEXT,
+ 'int' => self::C_INT,
+ 'float' => self::C_FLOAT,
+ 'bool' => self::C_BOOL,
+ 'lookup' => self::LOOKUP,
+ 'list' => self::ALIST,
+ 'hash' => self::HASH,
+ 'mixed' => self::C_MIXED
+ );
+
+ /**
+ * Lookup table of types that are string, and can have aliases or
+ * allowed value lists.
+ */
+ public static $stringTypes = array(
+ self::C_STRING => true,
+ self::ISTRING => true,
+ self::TEXT => true,
+ self::ITEXT => true,
+ );
+
+ /**
+ * Validate a variable according to type.
+ * It may return NULL as a valid type if $allow_null is true.
+ *
+ * @param mixed $var Variable to validate
+ * @param int $type Type of variable, see HTMLPurifier_VarParser->types
+ * @param bool $allow_null Whether or not to permit null as a value
+ * @return string Validated and type-coerced variable
+ * @throws HTMLPurifier_VarParserException
+ */
+ final public function parse($var, $type, $allow_null = false)
+ {
+ if (is_string($type)) {
+ if (!isset(HTMLPurifier_VarParser::$types[$type])) {
+ throw new HTMLPurifier_VarParserException("Invalid type '$type'");
+ } else {
+ $type = HTMLPurifier_VarParser::$types[$type];
+ }
+ }
+ $var = $this->parseImplementation($var, $type, $allow_null);
+ if ($allow_null && $var === null) {
+ return null;
+ }
+ // These are basic checks, to make sure nothing horribly wrong
+ // happened in our implementations.
+ switch ($type) {
+ case (self::C_STRING):
+ case (self::ISTRING):
+ case (self::TEXT):
+ case (self::ITEXT):
+ if (!is_string($var)) {
+ break;
+ }
+ if ($type == self::ISTRING || $type == self::ITEXT) {
+ $var = strtolower($var);
+ }
+ return $var;
+ case (self::C_INT):
+ if (!is_int($var)) {
+ break;
+ }
+ return $var;
+ case (self::C_FLOAT):
+ if (!is_float($var)) {
+ break;
+ }
+ return $var;
+ case (self::C_BOOL):
+ if (!is_bool($var)) {
+ break;
+ }
+ return $var;
+ case (self::LOOKUP):
+ case (self::ALIST):
+ case (self::HASH):
+ if (!is_array($var)) {
+ break;
+ }
+ if ($type === self::LOOKUP) {
+ foreach ($var as $k) {
+ if ($k !== true) {
+ $this->error('Lookup table contains value other than true');
+ }
+ }
+ } elseif ($type === self::ALIST) {
+ $keys = array_keys($var);
+ if (array_keys($keys) !== $keys) {
+ $this->error('Indices for list are not uniform');
+ }
+ }
+ return $var;
+ case (self::C_MIXED):
+ return $var;
+ default:
+ $this->errorInconsistent(get_class($this), $type);
+ }
+ $this->errorGeneric($var, $type);
+ }
+
+ /**
+ * Actually implements the parsing. Base implementation does not
+ * do anything to $var. Subclasses should overload this!
+ * @param mixed $var
+ * @param int $type
+ * @param bool $allow_null
+ * @return string
+ */
+ protected function parseImplementation($var, $type, $allow_null)
+ {
+ return $var;
+ }
+
+ /**
+ * Throws an exception.
+ * @throws HTMLPurifier_VarParserException
+ */
+ protected function error($msg)
+ {
+ throw new HTMLPurifier_VarParserException($msg);
+ }
+
+ /**
+ * Throws an inconsistency exception.
+ * @note This should not ever be called. It would be called if we
+ * extend the allowed values of HTMLPurifier_VarParser without
+ * updating subclasses.
+ * @param string $class
+ * @param int $type
+ * @throws HTMLPurifier_Exception
+ */
+ protected function errorInconsistent($class, $type)
+ {
+ throw new HTMLPurifier_Exception(
+ "Inconsistency in $class: " . HTMLPurifier_VarParser::getTypeName($type) .
+ " not implemented"
+ );
+ }
+
+ /**
+ * Generic error for if a type didn't work.
+ * @param mixed $var
+ * @param int $type
+ */
+ protected function errorGeneric($var, $type)
+ {
+ $vtype = gettype($var);
+ $this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype");
+ }
+
+ /**
+ * @param int $type
+ * @return string
+ */
+ public static function getTypeName($type)
+ {
+ static $lookup;
+ if (!$lookup) {
+ // Lazy load the alternative lookup table
+ $lookup = array_flip(HTMLPurifier_VarParser::$types);
+ }
+ if (!isset($lookup[$type])) {
+ return 'unknown';
+ }
+ return $lookup[$type];
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php
new file mode 100644
index 000000000..3bfbe8386
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php
@@ -0,0 +1,130 @@
+ $j) {
+ $var[$i] = trim($j);
+ }
+ if ($type === self::HASH) {
+ // key:value,key2:value2
+ $nvar = array();
+ foreach ($var as $keypair) {
+ $c = explode(':', $keypair, 2);
+ if (!isset($c[1])) {
+ continue;
+ }
+ $nvar[trim($c[0])] = trim($c[1]);
+ }
+ $var = $nvar;
+ }
+ }
+ if (!is_array($var)) {
+ break;
+ }
+ $keys = array_keys($var);
+ if ($keys === array_keys($keys)) {
+ if ($type == self::ALIST) {
+ return $var;
+ } elseif ($type == self::LOOKUP) {
+ $new = array();
+ foreach ($var as $key) {
+ $new[$key] = true;
+ }
+ return $new;
+ } else {
+ break;
+ }
+ }
+ if ($type === self::ALIST) {
+ trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING);
+ return array_values($var);
+ }
+ if ($type === self::LOOKUP) {
+ foreach ($var as $key => $value) {
+ if ($value !== true) {
+ trigger_error(
+ "Lookup array has non-true value at key '$key'; " .
+ "maybe your input array was not indexed numerically",
+ E_USER_WARNING
+ );
+ }
+ $var[$key] = true;
+ }
+ }
+ return $var;
+ default:
+ $this->errorInconsistent(__CLASS__, $type);
+ }
+ $this->errorGeneric($var, $type);
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php
new file mode 100644
index 000000000..f11c318ef
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php
@@ -0,0 +1,38 @@
+evalExpression($var);
+ }
+
+ /**
+ * @param string $expr
+ * @return mixed
+ * @throws HTMLPurifier_VarParserException
+ */
+ protected function evalExpression($expr)
+ {
+ $var = null;
+ $result = eval("\$var = $expr;");
+ if ($result === false) {
+ throw new HTMLPurifier_VarParserException("Fatal error in evaluated code");
+ }
+ return $var;
+ }
+}
+
+// vim: et sw=4 sts=4
diff --git a/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php
new file mode 100644
index 000000000..5df341495
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php
@@ -0,0 +1,11 @@
+front = $front;
+ $this->back = $back;
+ }
+
+ /**
+ * Creates a zipper from an array, with a hole in the
+ * 0-index position.
+ * @param Array to zipper-ify.
+ * @return Tuple of zipper and element of first position.
+ */
+ static public function fromArray($array) {
+ $z = new self(array(), array_reverse($array));
+ $t = $z->delete(); // delete the "dummy hole"
+ return array($z, $t);
+ }
+
+ /**
+ * Convert zipper back into a normal array, optionally filling in
+ * the hole with a value. (Usually you should supply a $t, unless you
+ * are at the end of the array.)
+ */
+ public function toArray($t = NULL) {
+ $a = $this->front;
+ if ($t !== NULL) $a[] = $t;
+ for ($i = count($this->back)-1; $i >= 0; $i--) {
+ $a[] = $this->back[$i];
+ }
+ return $a;
+ }
+
+ /**
+ * Move hole to the next element.
+ * @param $t Element to fill hole with
+ * @return Original contents of new hole.
+ */
+ public function next($t) {
+ if ($t !== NULL) array_push($this->front, $t);
+ return empty($this->back) ? NULL : array_pop($this->back);
+ }
+
+ /**
+ * Iterated hole advancement.
+ * @param $t Element to fill hole with
+ * @param $i How many forward to advance hole
+ * @return Original contents of new hole, i away
+ */
+ public function advance($t, $n) {
+ for ($i = 0; $i < $n; $i++) {
+ $t = $this->next($t);
+ }
+ return $t;
+ }
+
+ /**
+ * Move hole to the previous element
+ * @param $t Element to fill hole with
+ * @return Original contents of new hole.
+ */
+ public function prev($t) {
+ if ($t !== NULL) array_push($this->back, $t);
+ return empty($this->front) ? NULL : array_pop($this->front);
+ }
+
+ /**
+ * Delete contents of current hole, shifting hole to
+ * next element.
+ * @return Original contents of new hole.
+ */
+ public function delete() {
+ return empty($this->back) ? NULL : array_pop($this->back);
+ }
+
+ /**
+ * Returns true if we are at the end of the list.
+ * @return bool
+ */
+ public function done() {
+ return empty($this->back);
+ }
+
+ /**
+ * Insert element before hole.
+ * @param Element to insert
+ */
+ public function insertBefore($t) {
+ if ($t !== NULL) array_push($this->front, $t);
+ }
+
+ /**
+ * Insert element after hole.
+ * @param Element to insert
+ */
+ public function insertAfter($t) {
+ if ($t !== NULL) array_push($this->back, $t);
+ }
+
+ /**
+ * Splice in multiple elements at hole. Functional specification
+ * in terms of array_splice:
+ *
+ * $arr1 = $arr;
+ * $old1 = array_splice($arr1, $i, $delete, $replacement);
+ *
+ * list($z, $t) = HTMLPurifier_Zipper::fromArray($arr);
+ * $t = $z->advance($t, $i);
+ * list($old2, $t) = $z->splice($t, $delete, $replacement);
+ * $arr2 = $z->toArray($t);
+ *
+ * assert($old1 === $old2);
+ * assert($arr1 === $arr2);
+ *
+ * NB: the absolute index location after this operation is
+ * *unchanged!*
+ *
+ * @param Current contents of hole.
+ */
+ public function splice($t, $delete, $replacement) {
+ // delete
+ $old = array();
+ $r = $t;
+ for ($i = $delete; $i > 0; $i--) {
+ $old[] = $r;
+ $r = $this->delete();
+ }
+ // insert
+ for ($i = count($replacement)-1; $i >= 0; $i--) {
+ $this->insertAfter($r);
+ $r = $replacement[$i];
+ }
+ return array($old, $r);
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/htaccess.txt b/libraries/phpspreadsheet/vendor/htaccess.txt
new file mode 100644
index 000000000..9afb1a1b3
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/htaccess.txt
@@ -0,0 +1,9 @@
+# Apache 2.4+
+
+
+### Financial Contributors
+
+Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/zipstream/contribute)]
+
+#### Individuals
+
+
+
+#### Organizations
+
+Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/zipstream/contribute)]
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libraries/phpspreadsheet/vendor/maennchen/zipstream-php/composer.json b/libraries/phpspreadsheet/vendor/maennchen/zipstream-php/composer.json
new file mode 100644
index 000000000..98c536a43
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/maennchen/zipstream-php/composer.json
@@ -0,0 +1,88 @@
+{
+ "name": "maennchen/zipstream-php",
+ "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
+ "keywords": ["zip", "stream"],
+ "type": "library",
+ "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"
+ }
+ ],
+ "require": {
+ "php-64bit": "^8.1",
+ "ext-mbstring": "*",
+ "ext-zlib": "*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0",
+ "guzzlehttp/guzzle": "^7.5",
+ "ext-zip": "*",
+ "mikey179/vfsstream": "^1.6",
+ "php-coveralls/php-coveralls": "^2.5",
+ "friendsofphp/php-cs-fixer": "^3.16",
+ "vimeo/psalm": "^5.0"
+ },
+ "suggest": {
+ "psr/http-message": "^2.0",
+ "guzzlehttp/psr7": "^2.4"
+ },
+ "scripts": {
+ "format": "php-cs-fixer fix",
+ "test": [
+ "@test:unit",
+ "@test:formatted",
+ "@test:lint"
+ ],
+ "test:unit": "phpunit --coverage-clover=coverage.clover.xml --coverage-html cov",
+ "test:unit:slow": "@test:unit --group slow",
+ "test:unit:fast": "@test:unit --exclude-group slow",
+ "test:formatted": "@format --dry-run --stop-on-violation --using-cache=no",
+ "test:lint": "psalm --stats --show-info=true --find-unused-psalm-suppress",
+ "coverage:report": "php-coveralls --coverage_clover=coverage.clover.xml --json_path=coveralls-upload.json --insecure",
+ "install:tools": "phive install --trust-gpg-keys 0x67F861C3D889C656",
+ "docs:generate": "tools/phpdocumentor --sourcecode"
+ },
+ "autoload": {
+ "psr-4": {
+ "ZipStream\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": { "ZipStream\\Test\\": "test/" }
+ },
+ "archive": {
+ "exclude": [
+ "/composer.lock",
+ "/docs",
+ "/.gitattributes",
+ "/.github",
+ "/.gitignore",
+ "/guides",
+ "/.phive",
+ "/.php-cs-fixer.cache",
+ "/.php-cs-fixer.dist.php",
+ "/.phpdoc",
+ "/phpdoc.dist.xml",
+ "/.phpunit.result.cache",
+ "/phpunit.xml.dist",
+ "/psalm.xml",
+ "/test",
+ "/tools",
+ "/.tool-versions",
+ "/vendor"
+ ]
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/maennchen/zipstream-php/guides/ContentLength.rst b/libraries/phpspreadsheet/vendor/maennchen/zipstream-php/guides/ContentLength.rst
new file mode 100644
index 000000000..21fea34d7
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/maennchen/zipstream-php/guides/ContentLength.rst
@@ -0,0 +1,47 @@
+Adding Content-Length header
+=============
+
+Adding a ``Content-Length`` header for ``ZipStream`` can be achieved by
+using the options ``SIMULATION_STRICT`` or ``SIMULATION_LAX`` in the
+``operationMode`` parameter.
+
+In the ``SIMULATION_STRICT`` mode, ``ZipStream`` will not allow to calculate the
+size based on reading the whole file. ``SIMULATION_LAX`` will read the whole
+file if neccessary.
+
+``SIMULATION_STRICT`` is therefore useful to make sure that the size can be
+calculated efficiently.
+
+.. code-block:: php
+ use ZipStream\OperationMode;
+ use ZipStream\ZipStream;
+
+ $zip = new ZipStream(
+ operationMode: OperationMode::SIMULATE_STRICT, // or SIMULATE_LAX
+ defaultEnableZeroHeader: false,
+ sendHttpHeaders: true,
+ outputStream: $stream,
+ );
+
+ // Normally add files
+ $zip->addFile('sample.txt', 'Sample String Data');
+
+ // Use addFileFromCallback and exactSize if you want to defer opening of
+ // the file resource
+ $zip->addFileFromCallback(
+ 'sample.txt',
+ exactSize: 18,
+ callback: function () {
+ return fopen('...');
+ }
+ );
+
+ // Read resulting file size
+ $size = $zip->finish();
+
+ // Tell it to the browser
+ header('Content-Length: '. $size);
+
+ // Execute the Simulation and stream the actual zip to the client
+ $zip->executeSimulation();
+
diff --git a/libraries/phpspreadsheet/vendor/maennchen/zipstream-php/guides/FlySystem.rst b/libraries/phpspreadsheet/vendor/maennchen/zipstream-php/guides/FlySystem.rst
new file mode 100644
index 000000000..4e6c6fb82
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/maennchen/zipstream-php/guides/FlySystem.rst
@@ -0,0 +1,34 @@
+Usage with FlySystem
+===============
+
+For saving or uploading the generated zip, you can use the
+`Flysystem
Not only does it reduce the risk of typos; but at some point in the future, ExcelError values will be an object rather than a string, and we won't then need to update all the tests.
+ - Don't over-complicate test code by testing happy and unhappy paths in the same test.
+
+This makes it easier to see exactly what is being tested when reviewing the PR. I want to be able to see it in the PR, not have to hunt in other unchanged classes to see what the test is doing.
+
+## 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 the tag with `git push --tags`, GitHub Actions will create a GitHub release automatically, and the release details will automatically be sent to packagist.
+4. Github seems to remove markdown headings in the Release Notes, so you should edit to restore these.
+
+> **Note:** Tagged releases are made from the `master` branch. Only in an emergency should a tagged release be made from the `release` branch. (i.e. cherry-picked hot-fixes.)
+
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/LICENSE b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/LICENSE
new file mode 100644
index 000000000..3ec5723dd
--- /dev/null
+++ b/libraries/phpspreadsheet/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.
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/README.md b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/README.md
new file mode 100644
index 000000000..a69c3afc9
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/README.md
@@ -0,0 +1,144 @@
+# PhpSpreadsheet
+
+[](https://github.com/PHPOffice/PhpSpreadsheet/actions)
+[](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/?branch=master)
+[](https://scrutinizer-ci.com/g/PHPOffice/PhpSpreadsheet/?branch=master)
+[](https://packagist.org/packages/phpoffice/phpspreadsheet)
+[](https://packagist.org/packages/phpoffice/phpspreadsheet)
+[](https://packagist.org/packages/phpoffice/phpspreadsheet)
+[](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.
+
+## PHP Version Support
+
+LTS: Support for PHP versions will only be maintained for a period of six months beyond the
+[end of life](https://www.php.net/supported-versions) of that PHP version.
+
+Currently the required PHP minimum version is PHP __7.4__, and we [will support that version](https://www.php.net/eol.php) until 28th June 2023.
+
+See the `composer.json` for other requirements.
+
+## Installation
+
+Use [composer](https://getcomposer.org) to install PhpSpreadsheet into your project:
+
+```sh
+composer require phpoffice/phpspreadsheet
+```
+
+If you are building your installation on a development machine that is on a different PHP version to the server where it will be deployed, or if your PHP CLI version is not the same as your run-time such as `php-fpm` or Apache's `mod_php`, then you might want to add the following to your `composer.json` before installing:
+```json
+{
+ "require": {
+ "phpoffice/phpspreadsheet": "^1.28"
+ },
+ "config": {
+ "platform": {
+ "php": "7.4"
+ }
+ }
+}
+```
+and then run
+```sh
+composer install
+```
+to ensure that the correct dependencies are retrieved to match your deployment environment.
+
+See [CLI vs Application run-time](https://php.watch/articles/composer-platform-check) for more details.
+
+### Additional Installation Options
+
+If you want to write to PDF, or to include Charts when you write to HTML or PDF, then you will need to install additional libraries:
+
+#### PDF
+
+For PDF Generation, you can install any of the following, and then configure PhpSpreadsheet to indicate which library you are going to use:
+ - mpdf/mpdf
+ - dompdf/dompdf
+ - tecnickcom/tcpdf
+
+and configure PhpSpreadsheet using:
+
+```php
+// Dompdf, Mpdf or Tcpdf (as appropriate)
+$className = \PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf::class;
+IOFactory::registerWriter('Pdf', $className);
+```
+or the appropriate PDF Writer wrapper for the library that you have chosen to install.
+
+#### Chart Export
+
+For Chart export, we support following packages, which you will also need to install yourself using `composer require`
+ - [jpgraph/jpgraph](https://packagist.org/packages/jpgraph/jpgraph) (this package was abandoned at version 4.0.
+ You can manually download the latest version that supports PHP 8 and above from [jpgraph.net](https://jpgraph.net/))
+ - [mitoteam/jpgraph](https://packagist.org/packages/mitoteam/jpgraph) - up to date fork with modern PHP versions support and some bugs fixed.
+
+and then configure PhpSpreadsheet using:
+```php
+// to use jpgraph/jpgraph
+Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class);
+//or
+// to use mitoteam/jpgraph
+Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class);
+```
+
+One or the other of these libraries is necessary if you want to generate HTML or PDF files that include charts; or to render a Chart to an Image format from within your code.
+They are not necessary to define charts for writing to `Xlsx` files.
+Other file formats don't support writing Charts.
+
+## 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).
+
+## Patreon
+
+I am now running a [Patreon](https://www.patreon.com/MarkBaker) to support the work that I do on PhpSpreadsheet.
+
+Supporters will receive access to articles about working with PhpSpreadsheet, and how to use some of its more advanced features.
+
+Posts already available to Patreon supporters:
+ - The Dating Game
+ - A look at how MS Excel (and PhpSpreadsheet) handle date and time values.
+- Looping the Loop
+ - Advice on Iterating through the rows and cells in a worksheet.
+
+And for Patrons at levels actively using PhpSpreadsheet:
+ - Behind the Mask
+ - A look at Number Format Masks.
+
+The Next Article (currently Work in Progress):
+ - Formula for Success
+ - How to debug formulae that don't produce the expected result.
+
+
+My aim is to post at least one article each month, taking a detailed look at some feature of MS Excel and how to use that feature in PhpSpreadsheet, or on how to perform different activities in PhpSpreadsheet.
+
+Planned posts for the future include topics like:
+ - Tables
+ - Structured References
+ - AutoFiltering
+ - Array Formulae
+ - Conditional Formatting
+ - Data Validation
+ - Value Binders
+ - Images
+ - Charts
+
+After a period of six months exclusive to Patreon supporters, articles will be incorporated into the public documentation for the library.
+
+## 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).
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/composer.json b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/composer.json
new file mode 100644
index 000000000..aabddd351
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/composer.json
@@ -0,0 +1,117 @@
+{
+ "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": [
+ "phpcs src/ tests/ --report=checkstyle",
+ "phpcs --report-width=200 samples/ src/ tests/ --ignore=samples/Header.php --standard=PHPCompatibility --runtime-set testVersion 7.4- -n",
+ "php-cs-fixer fix --ansi --dry-run --diff",
+ "phpunit --color=always",
+ "phpstan analyse --ansi --memory-limit=2048M"
+ ],
+ "style": [
+ "phpcs src/ tests/ --report=checkstyle",
+ "php-cs-fixer fix --ansi --dry-run --diff"
+ ],
+ "fix": [
+ "phpcbf src/ tests/ --report=checkstyle",
+ "php-cs-fixer fix"
+ ],
+ "versions": [
+ "phpcs --report-width=200 samples/ src/ tests/ --ignore=samples/Header.php --standard=PHPCompatibility --runtime-set testVersion 7.4- -n"
+ ]
+ },
+ "require": {
+ "php": "^7.4 || ^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.15",
+ "maennchen/zipstream-php": "^2.1 || ^3.0",
+ "markbaker/complex": "^3.0",
+ "markbaker/matrix": "^3.0",
+ "psr/http-client": "^1.0",
+ "psr/http-factory": "^1.0",
+ "psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
+ },
+ "require-dev": {
+ "dealerdirect/phpcodesniffer-composer-installer": "dev-main",
+ "dompdf/dompdf": "^1.0 || ^2.0",
+ "friendsofphp/php-cs-fixer": "^3.2",
+ "mitoteam/jpgraph": "^10.3",
+ "mpdf/mpdf": "^8.1.1",
+ "phpcompatibility/php-compatibility": "^9.3",
+ "phpstan/phpstan": "^1.1",
+ "phpstan/phpstan-phpunit": "^1.0",
+ "phpunit/phpunit": "^8.5 || ^9.0",
+ "squizlabs/php_codesniffer": "^3.7",
+ "tecnickcom/tcpdf": "^6.5"
+ },
+ "suggest": {
+ "ext-intl": "PHP Internationalization Functions",
+ "mpdf/mpdf": "Option for rendering PDF with PDF Writer",
+ "dompdf/dompdf": "Option for rendering PDF with PDF Writer",
+ "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer",
+ "mitoteam/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"
+ }
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/phpstan-baseline.neon b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/phpstan-baseline.neon
new file mode 100644
index 000000000..e107b2667
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/phpstan-baseline.neon
@@ -0,0 +1,351 @@
+parameters:
+ ignoreErrors:
+ -
+ message: "#^Offset 1 does not exist on array\\{0\\?\\: string, 1\\?\\: ''\\|numeric\\-string, 2\\: '\\+', 3\\?\\: ''\\|numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Calculation.php
+
+ -
+ message: "#^Offset 1 does not exist on array\\{0\\?\\: string, 1\\?\\: ''\\|numeric\\-string, 2\\: ',', 3\\?\\: ''\\|numeric\\-string\\}\\.$#"
+ count: 2
+ path: src/PhpSpreadsheet/Calculation/Calculation.php
+
+ -
+ message: "#^Offset 1 does not exist on array\\{0\\?\\: string, 1\\?\\: ''\\|numeric\\-string, 2\\: '\\-', 3\\?\\: ''\\|numeric\\-string\\}\\.$#"
+ count: 2
+ path: src/PhpSpreadsheet/Calculation/Calculation.php
+
+ -
+ message: "#^Offset 2 does not exist on array\\{0\\?\\: string, 1\\?\\: string, 2\\?\\: string, 3\\?\\: string, 4\\?\\: string, 5\\?\\: string, 6\\?\\: non\\-empty\\-string, 7\\?\\: numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Calculation.php
+
+ -
+ message: "#^Offset 3 does not exist on array\\{0\\?\\: string, 1\\: ''\\|numeric\\-string, 2\\: '\\-', 3\\?\\: ''\\|numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Calculation.php
+
+ -
+ message: "#^Offset 3 does not exist on array\\{0\\?\\: string, 1\\?\\: ''\\|numeric\\-string, 2\\: ',', 3\\?\\: ''\\|numeric\\-string\\}\\.$#"
+ count: 2
+ path: src/PhpSpreadsheet/Calculation/Calculation.php
+
+ -
+ message: "#^Offset 3 does not exist on array\\{0\\?\\: string, 1\\?\\: ''\\|numeric\\-string, 2\\: '\\-', 3\\?\\: ''\\|numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Calculation.php
+
+ -
+ message: "#^Offset 8 on array\\{string, string, string, string, string, string, non\\-empty\\-string, numeric\\-string\\} in isset\\(\\) does not exist\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Calculation.php
+
+ -
+ message: "#^Strict comparison using \\=\\=\\= between mixed and null will always evaluate to false\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Calculation.php
+
+ -
+ message: "#^Binary operation \"%%\" between int\\|string and 100 results in an error\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php
+
+ -
+ message: "#^Binary operation \"%%\" between int\\|string and 4 results in an error\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php
+
+ -
+ message: "#^Binary operation \"%%\" between int\\|string and 400 results in an error\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php
+
+ -
+ message: "#^Binary operation \"%%\" between string and 24 results in an error\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php
+
+ -
+ message: "#^Binary operation \"\\-\" between 1 and array\\|float\\|string results in an error\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Engineering/Erf.php
+
+ -
+ message: "#^Cannot call method getTokenSubType\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#"
+ count: 4
+ path: src/PhpSpreadsheet/Calculation/FormulaParser.php
+
+ -
+ message: "#^Cannot call method getTokenType\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#"
+ count: 9
+ path: src/PhpSpreadsheet/Calculation/FormulaParser.php
+
+ -
+ message: "#^Cannot call method setTokenSubType\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#"
+ count: 5
+ path: src/PhpSpreadsheet/Calculation/FormulaParser.php
+
+ -
+ message: "#^Cannot call method setValue\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken\\|null\\.$#"
+ count: 5
+ path: src/PhpSpreadsheet/Calculation/FormulaParser.php
+
+ -
+ message: "#^Strict comparison using \\=\\=\\= between PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\FormulaToken and null will always evaluate to false\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/FormulaParser.php
+
+ -
+ message: "#^Offset 1 does not exist on array\\{0\\?\\: string, 1\\?\\: non\\-falsy\\-string, 2\\?\\: string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Functions.php
+
+ -
+ message: "#^Offset 2 does not exist on array\\{0\\?\\: string, 1\\?\\: non\\-falsy\\-string, 2\\?\\: string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Functions.php
+
+ -
+ message: "#^Offset 2 does not exist on array\\{0\\?\\: string, 1\\?\\: string, 2\\?\\: string, 3\\?\\: string, 4\\?\\: string, 5\\?\\: string, 6\\?\\: non\\-empty\\-string, 7\\?\\: numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Information/Value.php
+
+ -
+ message: "#^Offset 6 does not exist on array\\{0\\?\\: string, 1\\?\\: string, 2\\?\\: string, 3\\?\\: string, 4\\?\\: string, 5\\?\\: string, 6\\?\\: non\\-empty\\-string, 7\\?\\: numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Information/Value.php
+
+ -
+ message: "#^Offset 7 does not exist on array\\{0\\?\\: string, 1\\?\\: string, 2\\?\\: string, 3\\?\\: string, 4\\?\\: string, 5\\?\\: string, 6\\?\\: non\\-empty\\-string, 7\\?\\: numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Information/Value.php
+
+ -
+ message: "#^Offset 3 does not exist on array\\{0\\?\\: string, 1\\?\\: string, 2\\?\\: string, 3\\?\\: string, 4\\?\\: string, 5\\?\\: string, 6\\?\\: non\\-empty\\-string, 7\\?\\: numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/LookupRef/Formula.php
+
+ -
+ message: "#^Offset 6 does not exist on array\\{0\\?\\: string, 1\\?\\: string, 2\\?\\: string, 3\\?\\: string, 4\\?\\: string, 5\\?\\: string, 6\\?\\: non\\-empty\\-string, 7\\?\\: numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/LookupRef/Formula.php
+
+ -
+ message: "#^Offset 7 does not exist on array\\{0\\?\\: string, 1\\?\\: string, 2\\?\\: string, 3\\?\\: string, 4\\?\\: string, 5\\?\\: string, 6\\?\\: non\\-empty\\-string, 7\\?\\: numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/LookupRef/Formula.php
+
+ -
+ message: "#^Binary operation \"/\" between float\\|int and float\\|string results in an error\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Statistical/Deviations.php
+
+ -
+ message: "#^Binary operation \"\\-\" between 1 and array\\|float\\|string results in an error\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php
+
+ -
+ message: "#^Binary operation \"\\-\" between 1 and array\\|float\\|string results in an error\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php
+
+ -
+ message: "#^Offset 'col' does not exist on array\\{0\\?\\: string, col\\?\\: non\\-empty\\-string, 1\\?\\: non\\-empty\\-string, row\\?\\: non\\-empty\\-string, 2\\?\\: non\\-empty\\-string\\}\\.$#"
+ count: 2
+ path: src/PhpSpreadsheet/Cell/AddressHelper.php
+
+ -
+ message: "#^Offset 'row' does not exist on array\\{0\\?\\: string, col\\?\\: non\\-empty\\-string, 1\\?\\: non\\-empty\\-string, row\\?\\: non\\-empty\\-string, 2\\?\\: non\\-empty\\-string\\}\\.$#"
+ count: 2
+ path: src/PhpSpreadsheet/Cell/AddressHelper.php
+
+ -
+ message: "#^Parameter \\#1 \\$num of function dechex expects int, string given\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Helper/Html.php
+
+ -
+ message: "#^Offset 'size' does not exist on array\\{0\\?\\: string, size\\?\\: non\\-empty\\-string, 1\\?\\: non\\-empty\\-string, unit\\?\\: non\\-falsy\\-string, 2\\?\\: non\\-falsy\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Helper/Size.php
+
+ -
+ message: "#^Parameter \\#2 \\$length of function fgetcsv expects int\\<0, max\\>\\|null, int\\|null given\\.$#"
+ count: 2
+ path: src/PhpSpreadsheet/Reader/Csv.php
+
+ -
+ message: "#^Parameter \\#1 \\$namespace of method DOMDocument\\:\\:getElementsByTagNameNS\\(\\) expects string, string\\|null given\\.$#"
+ count: 3
+ path: src/PhpSpreadsheet/Reader/Ods.php
+
+ -
+ message: "#^Parameter \\#1 \\$namespace of method DOMElement\\:\\:getElementsByTagNameNS\\(\\) expects string, string\\|null given\\.$#"
+ count: 7
+ path: src/PhpSpreadsheet/Reader/Ods.php
+
+ -
+ message: "#^Parameter \\#2 \\$tableNs of class PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\AutoFilter constructor expects string, string\\|null given\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Reader/Ods.php
+
+ -
+ message: "#^Parameter \\#2 \\$tableNs of class PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\DefinedNames constructor expects string, string\\|null given\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Reader/Ods.php
+
+ -
+ message: "#^Parameter \\#2 \\$tableNs of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:processMergedCells\\(\\) expects string, string\\|null given\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Reader/Ods.php
+
+ -
+ message: "#^Parameter \\#3 \\$configNs of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:lookForActiveSheet\\(\\) expects string, string\\|null given\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Reader/Ods.php
+
+ -
+ message: "#^Parameter \\#3 \\$configNs of method PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\:\\:lookForSelectedCells\\(\\) expects string, string\\|null given\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Reader/Ods.php
+
+ -
+ message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$officeNs \\(string\\) does not accept string\\|null\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php
+
+ -
+ message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$stylesFo \\(string\\) does not accept string\\|null\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php
+
+ -
+ message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$stylesNs \\(string\\) does not accept string\\|null\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php
+
+ -
+ message: "#^Property PhpOffice\\\\PhpSpreadsheet\\\\Reader\\\\Ods\\\\PageSettings\\:\\:\\$tableNs \\(string\\) does not accept string\\|null\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Reader/Ods/PageSettings.php
+
+ -
+ message: "#^Parameter \\#1 \\$column of method PhpOffice\\\\PhpSpreadsheet\\\\Worksheet\\\\Worksheet\\:\\:getColumnDimension\\(\\) expects string, \\(float\\|int\\) given\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Reader/Slk.php
+
+ -
+ message: "#^Offset 4 does not exist on array\\{0\\: string, 1\\: non\\-empty\\-string, 2\\: numeric\\-string, 3\\: string, 4\\?\\: string, 5\\?\\: numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Reader/Xlsx.php
+
+ -
+ message: "#^Offset 5 does not exist on array\\{0\\: string, 1\\: non\\-empty\\-string, 2\\: numeric\\-string, 3\\: string, 4\\?\\: string, 5\\?\\: numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Reader/Xlsx.php
+
+ -
+ message: "#^Variable \\$column in empty\\(\\) always exists and is not falsy\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/ReferenceHelper.php
+
+ -
+ message: "#^Offset 1 does not exist on array\\{0\\?\\: string, 1\\?\\: non\\-falsy\\-string, 2\\?\\: non\\-empty\\-string\\|numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php
+
+ -
+ message: "#^Offset 2 does not exist on array\\{0\\?\\: string, 1\\?\\: non\\-falsy\\-string, 2\\?\\: non\\-empty\\-string\\|numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php
+
+ -
+ message: "#^Variable \\$language on left side of \\?\\? always exists and is not nullable\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Style/NumberFormat/Wizard/NumberBase.php
+
+ -
+ message: "#^Offset 'mime' on array\\{0\\: int\\<0, max\\>, 1\\: int\\<0, max\\>, 2\\: int, 3\\: string, mime\\: string, channels\\?\\: int, bits\\?\\: int\\} on left side of \\?\\? always exists and is not nullable\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Worksheet/MemoryDrawing.php
+
+ -
+ message: "#^Variable \\$rgb in empty\\(\\) always exists and is not falsy\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Worksheet/MemoryDrawing.php
+
+ -
+ message: "#^Parameter \\#1 \\$filename of function fopen expects string, resource\\|string given\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/BaseWriter.php
+
+ -
+ message: "#^Parameter \\#1 \\$url of function parse_url expects string, resource\\|string given\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/BaseWriter.php
+
+ -
+ message: "#^Offset 'mime' does not exist on array\\{\\}\\|array\\{0\\: int\\<0, max\\>, 1\\: int\\<0, max\\>, 2\\: int, 3\\: string, mime\\: string, channels\\?\\: int, bits\\?\\: int\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/Html.php
+
+ -
+ message: "#^Offset 0 does not exist on array\\{\\}\\|array\\{0\\: int\\<0, max\\>, 1\\: int\\<0, max\\>, 2\\: int, 3\\: string, mime\\: string, channels\\?\\: int, bits\\?\\: int\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/Html.php
+
+ -
+ message: "#^Offset 1 does not exist on array\\{\\}\\|array\\{0\\: int\\<0, max\\>, 1\\: int\\<0, max\\>, 2\\: int, 3\\: string, mime\\: string, channels\\?\\: int, bits\\?\\: int\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/Html.php
+
+ -
+ message: "#^Variable \\$column in empty\\(\\) always exists and is not falsy\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/Ods/Formula.php
+
+ -
+ message: "#^Variable \\$column in empty\\(\\) always exists and is not falsy\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/Ods/NamedExpressions.php
+
+ -
+ message: "#^Offset 2 on array\\{0\\: int\\<0, max\\>, 1\\: int\\<0, max\\>, 2\\: int, 3\\: string, mime\\: string, channels\\?\\: int, bits\\?\\: int\\} on left side of \\?\\? always exists and is not nullable\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/Xls.php
+
+ -
+ message: "#^Offset 2 does not exist on array\\{0\\?\\: string, 1\\?\\: ''\\|'\\$', 2\\?\\: non\\-falsy\\-string, 3\\?\\: ''\\|'\\$', 4\\?\\: numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/Xls/Parser.php
+
+ -
+ message: "#^Offset 2 does not exist on array\\{0\\?\\: string, 1\\?\\: ''\\|'\\$', 2\\?\\: numeric\\-string, 3\\?\\: ''\\|'\\$', 4\\?\\: numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/Xls/Parser.php
+
+ -
+ message: "#^Offset 4 does not exist on array\\{0\\?\\: string, 1\\?\\: ''\\|'\\$', 2\\?\\: non\\-falsy\\-string, 3\\?\\: ''\\|'\\$', 4\\?\\: numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/Xls/Parser.php
+
+ -
+ message: "#^Offset 4 does not exist on array\\{0\\?\\: string, 1\\?\\: ''\\|'\\$', 2\\?\\: numeric\\-string, 3\\?\\: ''\\|'\\$', 4\\?\\: numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/Xls/Parser.php
+
+ -
+ message: "#^Parameter \\#2 \\$length of function fread expects int\\<1, max\\>, int\\<0, max\\> given\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/Xls/Worksheet.php
+
+ -
+ message: "#^Comparison operation \"\\>\\=\" between int\\<5, 7\\> and 3 is always true\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php
+
+ -
+ message: "#^Offset 1 does not exist on array\\{0\\?\\: string, 1\\?\\: numeric\\-string\\}\\.$#"
+ count: 1
+ path: src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/phpstan-conditional.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/phpstan-conditional.php
new file mode 100644
index 000000000..9b1150b34
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/phpstan-conditional.php
@@ -0,0 +1,86 @@
+ '~^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,
+ ];
+ // GdImage with Phpstan 1.9.2
+ $config['parameters']['ignoreErrors'][] = [
+ 'message' => '~Class GdImage not found.*$~',
+ 'path' => __DIR__ . '/tests/PhpSpreadsheetTests/Worksheet/MemoryDrawingTest.php',
+ 'count' => 3,
+ ];
+ // Erroneous analysis by Phpstan before PHP8 - 3rd parameter is nullable
+ // Fixed for Php7 with Phpstan 1.9.
+ //$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,
+ ];
+ // New with Phpstan 1.9.2 for Php7 only
+ $config['parameters']['ignoreErrors'][] = [
+ 'message' => '#^Parameter \\#2 \\.\\.\\.\\$args of function array_merge expects array, array
+ * Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz
+ * Applied Mathematics Division
+ * Argonne National Laboratory
+ * Argonne, IL 60439
+ *
+ *
+ *
+ *
+ * The program returns the value XINF for X .LE. 0.0 or when overflow would occur.
+ * The computation is believed to be free of underflow and overflow.
+ *
';
+
+ return false;
+ } else {
+ return $this->renderCombinationChart($groupCount, $outputDestination);
+ }
+ }
+
+ switch ($chartType) {
+ case 'area3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'areaChart':
+ $this->renderAreaChart($groupCount);
+
+ break;
+ case 'bar3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'barChart':
+ $this->renderBarChart($groupCount, $dimensions);
+
+ break;
+ case 'line3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'lineChart':
+ $this->renderLineChart($groupCount);
+
+ break;
+ case 'pie3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'pieChart':
+ $this->renderPieChart($groupCount, $dimensions, false, false);
+
+ break;
+ case 'doughnut3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'doughnutChart':
+ $this->renderPieChart($groupCount, $dimensions, true, true);
+
+ break;
+ case 'scatterChart':
+ $this->renderScatterChart($groupCount);
+
+ break;
+ case 'bubbleChart':
+ $this->renderBubbleChart($groupCount);
+
+ break;
+ case 'radarChart':
+ $this->renderRadarChart($groupCount);
+
+ break;
+ case 'surface3DChart':
+ case 'surfaceChart':
+ $this->renderContourChart($groupCount);
+
+ break;
+ case 'stockChart':
+ $this->renderStockChart($groupCount);
+
+ break;
+ default:
+ echo $chartType . ' is not yet implemented
';
+
+ return false;
+ }
+ $this->renderLegend();
+
+ $this->graph->Stroke($outputDestination);
+
+ return true;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php
new file mode 100644
index 000000000..b5e70d3a1
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php
@@ -0,0 +1,36 @@
+caption = $caption;
+ $this->layout = $layout;
+ $this->setOverlay($overlay);
+ }
+
+ /**
+ * Get caption.
+ *
+ * @return array|RichText|string
+ */
+ public function getCaption()
+ {
+ return $this->caption;
+ }
+
+ public function getCaptionText(): string
+ {
+ $caption = $this->caption;
+ if (is_string($caption)) {
+ return $caption;
+ }
+ if ($caption instanceof RichText) {
+ return $caption->getPlainText();
+ }
+ $retVal = '';
+ foreach ($caption as $textx) {
+ /** @var RichText|string */
+ $text = $textx;
+ if ($text instanceof RichText) {
+ $retVal .= $text->getPlainText();
+ } else {
+ $retVal .= $text;
+ }
+ }
+
+ return $retVal;
+ }
+
+ /**
+ * Set caption.
+ *
+ * @param array|RichText|string $caption
+ *
+ * @return $this
+ */
+ public function setCaption($caption)
+ {
+ $this->caption = $caption;
+
+ return $this;
+ }
+
+ /**
+ * Get allow overlay of other elements?
+ *
+ * @return bool
+ */
+ public function getOverlay()
+ {
+ return $this->overlay;
+ }
+
+ /**
+ * Set allow overlay of other elements?
+ *
+ * @param bool $overlay
+ */
+ public function setOverlay($overlay): void
+ {
+ $this->overlay = $overlay;
+ }
+
+ public function getLayout(): ?Layout
+ {
+ return $this->layout;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/TrendLine.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/TrendLine.php
new file mode 100644
index 000000000..75a5896c9
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/TrendLine.php
@@ -0,0 +1,226 @@
+setTrendLineProperties(
+ $trendLineType,
+ $order,
+ $period,
+ $dispRSqr,
+ $dispEq,
+ $backward,
+ $forward,
+ $intercept,
+ $name
+ );
+ }
+
+ public function getTrendLineType(): string
+ {
+ return $this->trendLineType;
+ }
+
+ public function setTrendLineType(string $trendLineType): self
+ {
+ $this->trendLineType = $trendLineType;
+
+ return $this;
+ }
+
+ public function getOrder(): int
+ {
+ return $this->order;
+ }
+
+ public function setOrder(int $order): self
+ {
+ $this->order = $order;
+
+ return $this;
+ }
+
+ public function getPeriod(): int
+ {
+ return $this->period;
+ }
+
+ public function setPeriod(int $period): self
+ {
+ $this->period = $period;
+
+ return $this;
+ }
+
+ public function getDispRSqr(): bool
+ {
+ return $this->dispRSqr;
+ }
+
+ public function setDispRSqr(bool $dispRSqr): self
+ {
+ $this->dispRSqr = $dispRSqr;
+
+ return $this;
+ }
+
+ public function getDispEq(): bool
+ {
+ return $this->dispEq;
+ }
+
+ public function setDispEq(bool $dispEq): self
+ {
+ $this->dispEq = $dispEq;
+
+ return $this;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function setName(string $name): self
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ public function getBackward(): float
+ {
+ return $this->backward;
+ }
+
+ public function setBackward(float $backward): self
+ {
+ $this->backward = $backward;
+
+ return $this;
+ }
+
+ public function getForward(): float
+ {
+ return $this->forward;
+ }
+
+ public function setForward(float $forward): self
+ {
+ $this->forward = $forward;
+
+ return $this;
+ }
+
+ public function getIntercept(): float
+ {
+ return $this->intercept;
+ }
+
+ public function setIntercept(float $intercept): self
+ {
+ $this->intercept = $intercept;
+
+ return $this;
+ }
+
+ public function setTrendLineProperties(
+ ?string $trendLineType = null,
+ ?int $order = 0,
+ ?int $period = 0,
+ ?bool $dispRSqr = false,
+ ?bool $dispEq = false,
+ ?float $backward = null,
+ ?float $forward = null,
+ ?float $intercept = null,
+ ?string $name = null
+ ): self {
+ if (!empty($trendLineType)) {
+ $this->setTrendLineType($trendLineType);
+ }
+ if ($order !== null) {
+ $this->setOrder($order);
+ }
+ if ($period !== null) {
+ $this->setPeriod($period);
+ }
+ if ($dispRSqr !== null) {
+ $this->setDispRSqr($dispRSqr);
+ }
+ if ($dispEq !== null) {
+ $this->setDispEq($dispEq);
+ }
+ if ($backward !== null) {
+ $this->setBackward($backward);
+ }
+ if ($forward !== null) {
+ $this->setForward($forward);
+ }
+ if ($intercept !== null) {
+ $this->setIntercept($intercept);
+ }
+ if ($name !== null) {
+ $this->setName($name);
+ }
+
+ return $this;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php
new file mode 100644
index 000000000..9a9df227e
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Cells.php
@@ -0,0 +1,478 @@
+parent = $parent;
+ $this->cache = $cache;
+ $this->cachePrefix = $this->getUniqueID();
+ }
+
+ /**
+ * Return the parent worksheet for this cell collection.
+ *
+ * @return null|Worksheet
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Whether the collection holds a cell for the given coordinate.
+ *
+ * @param string $cellCoordinate Coordinate of the cell to check
+ */
+ public function has($cellCoordinate): bool
+ {
+ return ($cellCoordinate === $this->currentCoordinate) || isset($this->index[$cellCoordinate]);
+ }
+
+ /**
+ * Add or update a cell in the collection.
+ *
+ * @param Cell $cell Cell to update
+ */
+ public function update(Cell $cell): Cell
+ {
+ return $this->add($cell->getCoordinate(), $cell);
+ }
+
+ /**
+ * Delete a cell in cache identified by coordinate.
+ *
+ * @param string $cellCoordinate Coordinate of the cell to delete
+ */
+ public function delete($cellCoordinate): void
+ {
+ if ($cellCoordinate === $this->currentCoordinate && $this->currentCell !== null) {
+ $this->currentCell->detach();
+ $this->currentCoordinate = null;
+ $this->currentCell = null;
+ $this->currentCellIsDirty = false;
+ }
+
+ unset($this->index[$cellCoordinate]);
+
+ // Delete the entry from cache
+ $this->cache->delete($this->cachePrefix . $cellCoordinate);
+ }
+
+ /**
+ * Get a list of all cell coordinates currently held in the collection.
+ *
+ * @return string[]
+ */
+ public function getCoordinates()
+ {
+ return array_keys($this->index);
+ }
+
+ /**
+ * Get a sorted list of all cell coordinates currently held in the collection by row and column.
+ *
+ * @return string[]
+ */
+ public function getSortedCoordinates()
+ {
+ asort($this->index);
+
+ return array_keys($this->index);
+ }
+
+ /**
+ * Return the cell coordinate of the currently active cell object.
+ *
+ * @return null|string
+ */
+ public function getCurrentCoordinate()
+ {
+ return $this->currentCoordinate;
+ }
+
+ /**
+ * Return the column coordinate of the currently active cell object.
+ */
+ public function getCurrentColumn(): string
+ {
+ $column = 0;
+ $row = '';
+ sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row);
+
+ return (string) $column;
+ }
+
+ /**
+ * Return the row coordinate of the currently active cell object.
+ */
+ public function getCurrentRow(): int
+ {
+ $column = 0;
+ $row = '';
+ sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row);
+
+ return (int) $row;
+ }
+
+ /**
+ * Get highest worksheet column and highest row that have cell records.
+ *
+ * @return array Highest column name and highest row number
+ */
+ public function getHighestRowAndColumn()
+ {
+ // Lookup highest column and highest row
+ $maxRow = $maxColumn = 1;
+ foreach ($this->index as $coordinate) {
+ $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1;
+ $maxRow = ($maxRow > $row) ? $maxRow : $row;
+ $column = $coordinate % self::MAX_COLUMN_ID;
+ $maxColumn = ($maxColumn > $column) ? $maxColumn : $column;
+ }
+
+ return [
+ 'row' => $maxRow,
+ 'column' => Coordinate::stringFromColumnIndex($maxColumn),
+ ];
+ }
+
+ /**
+ * Get highest worksheet column.
+ *
+ * @param null|int|string $row Return the highest column for the specified row,
+ * or the highest column of any row if no row number is passed
+ *
+ * @return string Highest column name
+ */
+ public function getHighestColumn($row = null)
+ {
+ if ($row === null) {
+ return $this->getHighestRowAndColumn()['column'];
+ }
+
+ $row = (int) $row;
+ if ($row <= 0) {
+ throw new PhpSpreadsheetException('Row number must be a positive integer');
+ }
+
+ $maxColumn = 1;
+ $toRow = $row * self::MAX_COLUMN_ID;
+ $fromRow = --$row * self::MAX_COLUMN_ID;
+ foreach ($this->index as $coordinate) {
+ if ($coordinate < $fromRow || $coordinate >= $toRow) {
+ continue;
+ }
+ $column = $coordinate % self::MAX_COLUMN_ID;
+ $maxColumn = $maxColumn > $column ? $maxColumn : $column;
+ }
+
+ return Coordinate::stringFromColumnIndex($maxColumn);
+ }
+
+ /**
+ * Get highest worksheet row.
+ *
+ * @param null|string $column Return the highest row for the specified column,
+ * or the highest row of any column if no column letter is passed
+ *
+ * @return int Highest row number
+ */
+ public function getHighestRow($column = null)
+ {
+ if ($column === null) {
+ return $this->getHighestRowAndColumn()['row'];
+ }
+
+ $maxRow = 1;
+ $columnIndex = Coordinate::columnIndexFromString($column);
+ foreach ($this->index as $coordinate) {
+ if ($coordinate % self::MAX_COLUMN_ID !== $columnIndex) {
+ continue;
+ }
+ $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1;
+ $maxRow = ($maxRow > $row) ? $maxRow : $row;
+ }
+
+ return $maxRow;
+ }
+
+ /**
+ * Generate a unique ID for cache referencing.
+ *
+ * @return string Unique Reference
+ */
+ private function getUniqueID()
+ {
+ $cacheType = Settings::getCache();
+
+ return ($cacheType instanceof Memory\SimpleCache1 || $cacheType instanceof Memory\SimpleCache3)
+ ? random_bytes(7) . ':'
+ : uniqid('phpspreadsheet.', true) . '.';
+ }
+
+ /**
+ * Clone the cell collection.
+ *
+ * @return self
+ */
+ public function cloneCellCollection(Worksheet $worksheet)
+ {
+ $this->storeCurrentCell();
+ $newCollection = clone $this;
+
+ $newCollection->parent = $worksheet;
+ $newCollection->cachePrefix = $newCollection->getUniqueID();
+
+ foreach ($this->index as $key => $value) {
+ $newCollection->index[$key] = $value;
+ $stored = $newCollection->cache->set(
+ $newCollection->cachePrefix . $key,
+ clone $this->cache->get($this->cachePrefix . $key)
+ );
+ if ($stored === false) {
+ $this->destructIfNeeded($newCollection, 'Failed to copy cells in cache');
+ }
+ }
+
+ return $newCollection;
+ }
+
+ /**
+ * Remove a row, deleting all cells in that row.
+ *
+ * @param int|string $row Row number to remove
+ */
+ public function removeRow($row): void
+ {
+ $this->storeCurrentCell();
+ $row = (int) $row;
+ if ($row <= 0) {
+ throw new PhpSpreadsheetException('Row number must be a positive integer');
+ }
+
+ $toRow = $row * self::MAX_COLUMN_ID;
+ $fromRow = --$row * self::MAX_COLUMN_ID;
+ foreach ($this->index as $coordinate) {
+ if ($coordinate >= $fromRow && $coordinate < $toRow) {
+ $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1;
+ $column = Coordinate::stringFromColumnIndex($coordinate % self::MAX_COLUMN_ID);
+ $this->delete("{$column}{$row}");
+ }
+ }
+ }
+
+ /**
+ * Remove a column, deleting all cells in that column.
+ *
+ * @param string $column Column ID to remove
+ */
+ public function removeColumn($column): void
+ {
+ $this->storeCurrentCell();
+
+ $columnIndex = Coordinate::columnIndexFromString($column);
+ foreach ($this->index as $coordinate) {
+ if ($coordinate % self::MAX_COLUMN_ID === $columnIndex) {
+ $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1;
+ $column = Coordinate::stringFromColumnIndex($coordinate % self::MAX_COLUMN_ID);
+ $this->delete("{$column}{$row}");
+ }
+ }
+ }
+
+ /**
+ * Store cell data in cache for the current cell object if it's "dirty",
+ * and the 'nullify' the current cell object.
+ */
+ private function storeCurrentCell(): void
+ {
+ if ($this->currentCellIsDirty && isset($this->currentCoordinate, $this->currentCell)) {
+ $this->currentCell->/** @scrutinizer ignore-call */ detach();
+
+ $stored = $this->cache->set($this->cachePrefix . $this->currentCoordinate, $this->currentCell);
+ if ($stored === false) {
+ $this->destructIfNeeded($this, "Failed to store cell {$this->currentCoordinate} in cache");
+ }
+ $this->currentCellIsDirty = false;
+ }
+
+ $this->currentCoordinate = null;
+ $this->currentCell = null;
+ }
+
+ private function destructIfNeeded(self $cells, string $message): void
+ {
+ $cells->__destruct();
+
+ throw new PhpSpreadsheetException($message);
+ }
+
+ /**
+ * Add or update a cell identified by its coordinate into the collection.
+ *
+ * @param string $cellCoordinate Coordinate of the cell to update
+ * @param Cell $cell Cell to update
+ *
+ * @return Cell
+ */
+ public function add($cellCoordinate, Cell $cell)
+ {
+ if ($cellCoordinate !== $this->currentCoordinate) {
+ $this->storeCurrentCell();
+ }
+ $column = 0;
+ $row = '';
+ sscanf($cellCoordinate, '%[A-Z]%d', $column, $row);
+ $this->index[$cellCoordinate] = (--$row * self::MAX_COLUMN_ID) + Coordinate::columnIndexFromString((string) $column);
+
+ $this->currentCoordinate = $cellCoordinate;
+ $this->currentCell = $cell;
+ $this->currentCellIsDirty = true;
+
+ return $cell;
+ }
+
+ /**
+ * Get cell at a specific coordinate.
+ *
+ * @param string $cellCoordinate Coordinate of the cell
+ *
+ * @return null|Cell Cell that was found, or null if not found
+ */
+ public function get($cellCoordinate)
+ {
+ if ($cellCoordinate === $this->currentCoordinate) {
+ return $this->currentCell;
+ }
+ $this->storeCurrentCell();
+
+ // Return null if requested entry doesn't exist in collection
+ if ($this->has($cellCoordinate) === false) {
+ return null;
+ }
+
+ // Check if the entry that has been requested actually exists in the cache
+ $cell = $this->cache->get($this->cachePrefix . $cellCoordinate);
+ if ($cell === null) {
+ throw new PhpSpreadsheetException("Cell entry {$cellCoordinate} no longer exists in cache. This probably means that the cache was cleared by someone else.");
+ }
+
+ // Set current entry to the requested entry
+ $this->currentCoordinate = $cellCoordinate;
+ $this->currentCell = $cell;
+ // Re-attach this as the cell's parent
+ $this->currentCell->attach($this);
+
+ // Return requested entry
+ return $this->currentCell;
+ }
+
+ /**
+ * Clear the cell collection and disconnect from our parent.
+ */
+ public function unsetWorksheetCells(): void
+ {
+ if ($this->currentCell !== null) {
+ $this->currentCell->detach();
+ $this->currentCell = null;
+ $this->currentCoordinate = null;
+ }
+
+ // Flush the cache
+ $this->__destruct();
+
+ $this->index = [];
+
+ // detach ourself from the worksheet, so that it can then delete this object successfully
+ $this->parent = null;
+ }
+
+ /**
+ * Destroy this cell collection.
+ */
+ public function __destruct()
+ {
+ $this->cache->deleteMultiple($this->getAllCacheKeys());
+ }
+
+ /**
+ * Returns all known cache keys.
+ *
+ * @return Generator|string[]
+ */
+ private function getAllCacheKeys()
+ {
+ foreach ($this->index as $coordinate => $value) {
+ yield $this->cachePrefix . $coordinate;
+ }
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php
new file mode 100644
index 000000000..b3833bd8b
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/CellsFactory.php
@@ -0,0 +1,20 @@
+cache = [];
+
+ return true;
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function delete($key)
+ {
+ unset($this->cache[$key]);
+
+ return true;
+ }
+
+ /**
+ * @param iterable $keys
+ *
+ * @return bool
+ */
+ public function deleteMultiple($keys)
+ {
+ foreach ($keys as $key) {
+ $this->delete($key);
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ if ($this->has($key)) {
+ return $this->cache[$key];
+ }
+
+ return $default;
+ }
+
+ /**
+ * @param iterable $keys
+ * @param mixed $default
+ *
+ * @return iterable
+ */
+ public function getMultiple($keys, $default = null)
+ {
+ $results = [];
+ foreach ($keys as $key) {
+ $results[$key] = $this->get($key, $default);
+ }
+
+ return $results;
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function has($key)
+ {
+ return array_key_exists($key, $this->cache);
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $value
+ * @param null|DateInterval|int $ttl
+ *
+ * @return bool
+ */
+ public function set($key, $value, $ttl = null)
+ {
+ $this->cache[$key] = $value;
+
+ return true;
+ }
+
+ /**
+ * @param iterable $values
+ * @param null|DateInterval|int $ttl
+ *
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null)
+ {
+ foreach ($values as $key => $value) {
+ $this->set($key, $value);
+ }
+
+ return true;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache3.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache3.php
new file mode 100644
index 000000000..34f644705
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory/SimpleCache3.php
@@ -0,0 +1,109 @@
+cache = [];
+
+ return true;
+ }
+
+ /**
+ * @param string $key
+ */
+ public function delete($key): bool
+ {
+ unset($this->cache[$key]);
+
+ return true;
+ }
+
+ /**
+ * @param iterable $keys
+ */
+ public function deleteMultiple($keys): bool
+ {
+ foreach ($keys as $key) {
+ $this->delete($key);
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $default
+ */
+ public function get($key, $default = null): mixed
+ {
+ if ($this->has($key)) {
+ return $this->cache[$key];
+ }
+
+ return $default;
+ }
+
+ /**
+ * @param iterable $keys
+ * @param mixed $default
+ */
+ public function getMultiple($keys, $default = null): iterable
+ {
+ $results = [];
+ foreach ($keys as $key) {
+ $results[$key] = $this->get($key, $default);
+ }
+
+ return $results;
+ }
+
+ /**
+ * @param string $key
+ */
+ public function has($key): bool
+ {
+ return array_key_exists($key, $this->cache);
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $value
+ * @param null|DateInterval|int $ttl
+ */
+ public function set($key, $value, $ttl = null): bool
+ {
+ $this->cache[$key] = $value;
+
+ return true;
+ }
+
+ /**
+ * @param iterable $values
+ * @param null|DateInterval|int $ttl
+ */
+ public function setMultiple($values, $ttl = null): bool
+ {
+ foreach ($values as $key => $value) {
+ $this->set($key, $value);
+ }
+
+ return true;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Comment.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Comment.php
new file mode 100644
index 000000000..abadc7dfb
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Comment.php
@@ -0,0 +1,362 @@
+author = 'Author';
+ $this->text = new RichText();
+ $this->fillColor = new Color('FFFFFFE1');
+ $this->alignment = Alignment::HORIZONTAL_GENERAL;
+ $this->backgroundImage = new Drawing();
+ }
+
+ /**
+ * Get Author.
+ */
+ public function getAuthor(): string
+ {
+ return $this->author;
+ }
+
+ /**
+ * Set Author.
+ */
+ public function setAuthor(string $author): self
+ {
+ $this->author = $author;
+
+ return $this;
+ }
+
+ /**
+ * Get Rich text comment.
+ */
+ public function getText(): RichText
+ {
+ return $this->text;
+ }
+
+ /**
+ * Set Rich text comment.
+ */
+ public function setText(RichText $text): self
+ {
+ $this->text = $text;
+
+ return $this;
+ }
+
+ /**
+ * Get comment width (CSS style, i.e. XXpx or YYpt).
+ */
+ public function getWidth(): string
+ {
+ return $this->width;
+ }
+
+ /**
+ * Set comment width (CSS style, i.e. XXpx or YYpt). Default unit is pt.
+ */
+ public function setWidth(string $width): self
+ {
+ $width = new Size($width);
+ if ($width->valid()) {
+ $this->width = (string) $width;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get comment height (CSS style, i.e. XXpx or YYpt).
+ */
+ public function getHeight(): string
+ {
+ return $this->height;
+ }
+
+ /**
+ * Set comment height (CSS style, i.e. XXpx or YYpt). Default unit is pt.
+ */
+ public function setHeight(string $height): self
+ {
+ $height = new Size($height);
+ if ($height->valid()) {
+ $this->height = (string) $height;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get left margin (CSS style, i.e. XXpx or YYpt).
+ */
+ public function getMarginLeft(): string
+ {
+ return $this->marginLeft;
+ }
+
+ /**
+ * Set left margin (CSS style, i.e. XXpx or YYpt). Default unit is pt.
+ */
+ public function setMarginLeft(string $margin): self
+ {
+ $margin = new Size($margin);
+ if ($margin->valid()) {
+ $this->marginLeft = (string) $margin;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get top margin (CSS style, i.e. XXpx or YYpt).
+ */
+ public function getMarginTop(): string
+ {
+ return $this->marginTop;
+ }
+
+ /**
+ * Set top margin (CSS style, i.e. XXpx or YYpt). Default unit is pt.
+ */
+ public function setMarginTop(string $margin): self
+ {
+ $margin = new Size($margin);
+ if ($margin->valid()) {
+ $this->marginTop = (string) $margin;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Is the comment visible by default?
+ */
+ public function getVisible(): bool
+ {
+ return $this->visible;
+ }
+
+ /**
+ * Set comment default visibility.
+ */
+ public function setVisible(bool $visibility): self
+ {
+ $this->visible = $visibility;
+
+ return $this;
+ }
+
+ /**
+ * Set fill color.
+ */
+ public function setFillColor(Color $color): self
+ {
+ $this->fillColor = $color;
+
+ return $this;
+ }
+
+ /**
+ * Get fill color.
+ */
+ public function getFillColor(): Color
+ {
+ return $this->fillColor;
+ }
+
+ /**
+ * Set Alignment.
+ */
+ public function setAlignment(string $alignment): self
+ {
+ $this->alignment = $alignment;
+
+ return $this;
+ }
+
+ /**
+ * Get Alignment.
+ */
+ public function getAlignment(): string
+ {
+ return $this->alignment;
+ }
+
+ /**
+ * Get hash code.
+ */
+ public function getHashCode(): string
+ {
+ return md5(
+ $this->author .
+ $this->text->getHashCode() .
+ $this->width .
+ $this->height .
+ $this->marginLeft .
+ $this->marginTop .
+ ($this->visible ? 1 : 0) .
+ $this->fillColor->getHashCode() .
+ $this->alignment .
+ ($this->hasBackgroundImage() ? $this->backgroundImage->getHashCode() : '') .
+ __CLASS__
+ );
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone()
+ {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if (is_object($value)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+
+ /**
+ * Convert to string.
+ */
+ public function __toString(): string
+ {
+ return $this->text->getPlainText();
+ }
+
+ /**
+ * Check is background image exists.
+ */
+ public function hasBackgroundImage(): bool
+ {
+ $path = $this->backgroundImage->getPath();
+
+ if (empty($path)) {
+ return false;
+ }
+
+ return getimagesize($path) !== false;
+ }
+
+ /**
+ * Returns background image.
+ */
+ public function getBackgroundImage(): Drawing
+ {
+ return $this->backgroundImage;
+ }
+
+ /**
+ * Sets background image.
+ */
+ public function setBackgroundImage(Drawing $objDrawing): self
+ {
+ if (!array_key_exists($objDrawing->getType(), Drawing::IMAGE_TYPES_CONVERTION_MAP)) {
+ throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.');
+ }
+ $this->backgroundImage = $objDrawing;
+
+ return $this;
+ }
+
+ /**
+ * Sets size of comment as size of background image.
+ */
+ public function setSizeAsBackgroundImage(): self
+ {
+ if ($this->hasBackgroundImage()) {
+ $this->setWidth(SharedDrawing::pixelsToPoints($this->backgroundImage->getWidth()) . 'pt');
+ $this->setHeight(SharedDrawing::pixelsToPoints($this->backgroundImage->getHeight()) . 'pt');
+ }
+
+ return $this;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/DefinedName.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/DefinedName.php
new file mode 100644
index 000000000..07433b100
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/DefinedName.php
@@ -0,0 +1,275 @@
+worksheet).
+ *
+ * @var bool
+ */
+ protected $localOnly;
+
+ /**
+ * Scope.
+ *
+ * @var ?Worksheet
+ */
+ protected $scope;
+
+ /**
+ * Whether this is a named range or a named formula.
+ *
+ * @var bool
+ */
+ protected $isFormula;
+
+ /**
+ * Create a new Defined Name.
+ */
+ public function __construct(
+ string $name,
+ ?Worksheet $worksheet = null,
+ ?string $value = null,
+ bool $localOnly = false,
+ ?Worksheet $scope = null
+ ) {
+ if ($worksheet === null) {
+ $worksheet = $scope;
+ }
+
+ // Set local members
+ $this->name = $name;
+ $this->worksheet = $worksheet;
+ $this->value = (string) $value;
+ $this->localOnly = $localOnly;
+ // If local only, then the scope will be set to worksheet unless a scope is explicitly set
+ $this->scope = ($localOnly === true) ? (($scope === null) ? $worksheet : $scope) : null;
+ // If the range string contains characters that aren't associated with the range definition (A-Z,1-9
+ // for cell references, and $, or the range operators (colon comma or space), quotes and ! for
+ // worksheet names
+ // then this is treated as a named formula, and not a named range
+ $this->isFormula = self::testIfFormula($this->value);
+ }
+
+ /**
+ * Create a new defined name, either a range or a formula.
+ */
+ public static function createInstance(
+ string $name,
+ ?Worksheet $worksheet = null,
+ ?string $value = null,
+ bool $localOnly = false,
+ ?Worksheet $scope = null
+ ): self {
+ $value = (string) $value;
+ $isFormula = self::testIfFormula($value);
+ if ($isFormula) {
+ return new NamedFormula($name, $worksheet, $value, $localOnly, $scope);
+ }
+
+ return new NamedRange($name, $worksheet, $value, $localOnly, $scope);
+ }
+
+ public static function testIfFormula(string $value): bool
+ {
+ if (substr($value, 0, 1) === '=') {
+ $value = substr($value, 1);
+ }
+
+ if (is_numeric($value)) {
+ return true;
+ }
+
+ $segMatcher = false;
+ foreach (explode("'", $value) as $subVal) {
+ // Only test in alternate array entries (the non-quoted blocks)
+ $segMatcher = $segMatcher === false;
+ if (
+ $segMatcher &&
+ (preg_match('/' . self::REGEXP_IDENTIFY_FORMULA . '/miu', $subVal))
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get name.
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set name.
+ */
+ public function setName(string $name): self
+ {
+ if (!empty($name)) {
+ // Old title
+ $oldTitle = $this->name;
+
+ // Re-attach
+ if ($this->worksheet !== null) {
+ $this->worksheet->getParentOrThrow()->removeNamedRange($this->name, $this->worksheet);
+ }
+ $this->name = $name;
+
+ if ($this->worksheet !== null) {
+ $this->worksheet->getParentOrThrow()->addDefinedName($this);
+ }
+
+ if ($this->worksheet !== null) {
+ // New title
+ $newTitle = $this->name;
+ ReferenceHelper::getInstance()->updateNamedFormulae($this->worksheet->getParentOrThrow(), $oldTitle, $newTitle);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get worksheet.
+ */
+ public function getWorksheet(): ?Worksheet
+ {
+ return $this->worksheet;
+ }
+
+ /**
+ * Set worksheet.
+ */
+ public function setWorksheet(?Worksheet $worksheet): self
+ {
+ $this->worksheet = $worksheet;
+
+ return $this;
+ }
+
+ /**
+ * Get range or formula value.
+ */
+ public function getValue(): string
+ {
+ return $this->value;
+ }
+
+ /**
+ * Set range or formula value.
+ */
+ public function setValue(string $value): self
+ {
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get localOnly.
+ */
+ public function getLocalOnly(): bool
+ {
+ return $this->localOnly;
+ }
+
+ /**
+ * Set localOnly.
+ */
+ public function setLocalOnly(bool $localScope): self
+ {
+ $this->localOnly = $localScope;
+ $this->scope = $localScope ? $this->worksheet : null;
+
+ return $this;
+ }
+
+ /**
+ * Get scope.
+ */
+ public function getScope(): ?Worksheet
+ {
+ return $this->scope;
+ }
+
+ /**
+ * Set scope.
+ */
+ public function setScope(?Worksheet $worksheet): self
+ {
+ $this->scope = $worksheet;
+ $this->localOnly = $worksheet !== null;
+
+ return $this;
+ }
+
+ /**
+ * Identify whether this is a named range or a named formula.
+ */
+ public function isFormula(): bool
+ {
+ return $this->isFormula;
+ }
+
+ /**
+ * Resolve a named range to a regular cell range or formula.
+ */
+ public static function resolveName(string $definedName, Worksheet $worksheet, string $sheetName = ''): ?self
+ {
+ if ($sheetName === '') {
+ $worksheet2 = $worksheet;
+ } else {
+ $worksheet2 = $worksheet->getParentOrThrow()->getSheetByName($sheetName);
+ if ($worksheet2 === null) {
+ return null;
+ }
+ }
+
+ return $worksheet->getParentOrThrow()->getDefinedName($definedName, $worksheet2);
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone()
+ {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if (is_object($value)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php
new file mode 100644
index 000000000..302afee79
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php
@@ -0,0 +1,551 @@
+lastModifiedBy = $this->creator;
+ $this->created = self::intOrFloatTimestamp(null);
+ $this->modified = $this->created;
+ }
+
+ /**
+ * Get Creator.
+ */
+ public function getCreator(): string
+ {
+ return $this->creator;
+ }
+
+ /**
+ * Set Creator.
+ *
+ * @return $this
+ */
+ public function setCreator(string $creator): self
+ {
+ $this->creator = $creator;
+
+ return $this;
+ }
+
+ /**
+ * Get Last Modified By.
+ */
+ public function getLastModifiedBy(): string
+ {
+ return $this->lastModifiedBy;
+ }
+
+ /**
+ * Set Last Modified By.
+ *
+ * @return $this
+ */
+ public function setLastModifiedBy(string $modifiedBy): self
+ {
+ $this->lastModifiedBy = $modifiedBy;
+
+ return $this;
+ }
+
+ /**
+ * @param null|float|int|string $timestamp
+ *
+ * @return float|int
+ */
+ private static function intOrFloatTimestamp($timestamp)
+ {
+ if ($timestamp === null) {
+ $timestamp = (float) (new DateTime())->format('U');
+ } elseif (is_string($timestamp)) {
+ if (is_numeric($timestamp)) {
+ $timestamp = (float) $timestamp;
+ } else {
+ $timestamp = (string) preg_replace('/[.][0-9]*$/', '', $timestamp);
+ $timestamp = (string) preg_replace('/^(\\d{4})- (\\d)/', '$1-0$2', $timestamp);
+ $timestamp = (string) preg_replace('/^(\\d{4}-\\d{2})- (\\d)/', '$1-0$2', $timestamp);
+ $timestamp = (float) (new DateTime($timestamp))->format('U');
+ }
+ }
+
+ return IntOrFloat::evaluate($timestamp);
+ }
+
+ /**
+ * Get Created.
+ *
+ * @return float|int
+ */
+ public function getCreated()
+ {
+ return $this->created;
+ }
+
+ /**
+ * Set Created.
+ *
+ * @param null|float|int|string $timestamp
+ *
+ * @return $this
+ */
+ public function setCreated($timestamp): self
+ {
+ $this->created = self::intOrFloatTimestamp($timestamp);
+
+ return $this;
+ }
+
+ /**
+ * Get Modified.
+ *
+ * @return float|int
+ */
+ public function getModified()
+ {
+ return $this->modified;
+ }
+
+ /**
+ * Set Modified.
+ *
+ * @param null|float|int|string $timestamp
+ *
+ * @return $this
+ */
+ public function setModified($timestamp): self
+ {
+ $this->modified = self::intOrFloatTimestamp($timestamp);
+
+ return $this;
+ }
+
+ /**
+ * Get Title.
+ */
+ public function getTitle(): string
+ {
+ return $this->title;
+ }
+
+ /**
+ * Set Title.
+ *
+ * @return $this
+ */
+ public function setTitle(string $title): self
+ {
+ $this->title = $title;
+
+ return $this;
+ }
+
+ /**
+ * Get Description.
+ */
+ public function getDescription(): string
+ {
+ return $this->description;
+ }
+
+ /**
+ * Set Description.
+ *
+ * @return $this
+ */
+ public function setDescription(string $description): self
+ {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ /**
+ * Get Subject.
+ */
+ public function getSubject(): string
+ {
+ return $this->subject;
+ }
+
+ /**
+ * Set Subject.
+ *
+ * @return $this
+ */
+ public function setSubject(string $subject): self
+ {
+ $this->subject = $subject;
+
+ return $this;
+ }
+
+ /**
+ * Get Keywords.
+ */
+ public function getKeywords(): string
+ {
+ return $this->keywords;
+ }
+
+ /**
+ * Set Keywords.
+ *
+ * @return $this
+ */
+ public function setKeywords(string $keywords): self
+ {
+ $this->keywords = $keywords;
+
+ return $this;
+ }
+
+ /**
+ * Get Category.
+ */
+ public function getCategory(): string
+ {
+ return $this->category;
+ }
+
+ /**
+ * Set Category.
+ *
+ * @return $this
+ */
+ public function setCategory(string $category): self
+ {
+ $this->category = $category;
+
+ return $this;
+ }
+
+ /**
+ * Get Company.
+ */
+ public function getCompany(): string
+ {
+ return $this->company;
+ }
+
+ /**
+ * Set Company.
+ *
+ * @return $this
+ */
+ public function setCompany(string $company): self
+ {
+ $this->company = $company;
+
+ return $this;
+ }
+
+ /**
+ * Get Manager.
+ */
+ public function getManager(): string
+ {
+ return $this->manager;
+ }
+
+ /**
+ * Set Manager.
+ *
+ * @return $this
+ */
+ public function setManager(string $manager): self
+ {
+ $this->manager = $manager;
+
+ return $this;
+ }
+
+ /**
+ * Get a List of Custom Property Names.
+ *
+ * @return string[]
+ */
+ public function getCustomProperties(): array
+ {
+ return array_keys($this->customProperties);
+ }
+
+ /**
+ * Check if a Custom Property is defined.
+ */
+ public function isCustomPropertySet(string $propertyName): bool
+ {
+ return array_key_exists($propertyName, $this->customProperties);
+ }
+
+ /**
+ * Get a Custom Property Value.
+ *
+ * @return mixed
+ */
+ public function getCustomPropertyValue(string $propertyName)
+ {
+ if (isset($this->customProperties[$propertyName])) {
+ return $this->customProperties[$propertyName]['value'];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get a Custom Property Type.
+ *
+ * @return null|string
+ */
+ public function getCustomPropertyType(string $propertyName)
+ {
+ return $this->customProperties[$propertyName]['type'] ?? null;
+ }
+
+ /**
+ * @param mixed $propertyValue
+ */
+ private function identifyPropertyType($propertyValue): string
+ {
+ if (is_float($propertyValue)) {
+ return self::PROPERTY_TYPE_FLOAT;
+ }
+ if (is_int($propertyValue)) {
+ return self::PROPERTY_TYPE_INTEGER;
+ }
+ if (is_bool($propertyValue)) {
+ return self::PROPERTY_TYPE_BOOLEAN;
+ }
+
+ return self::PROPERTY_TYPE_STRING;
+ }
+
+ /**
+ * Set a Custom Property.
+ *
+ * @param mixed $propertyValue
+ * @param string $propertyType
+ * 'i' : Integer
+ * 'f' : Floating Point
+ * 's' : String
+ * 'd' : Date/Time
+ * 'b' : Boolean
+ *
+ * @return $this
+ */
+ public function setCustomProperty(string $propertyName, $propertyValue = '', $propertyType = null): self
+ {
+ if (($propertyType === null) || (!in_array($propertyType, self::VALID_PROPERTY_TYPE_LIST))) {
+ $propertyType = $this->identifyPropertyType($propertyValue);
+ }
+
+ if (!is_object($propertyValue)) {
+ $this->customProperties[$propertyName] = [
+ 'value' => self::convertProperty($propertyValue, $propertyType),
+ 'type' => $propertyType,
+ ];
+ }
+
+ return $this;
+ }
+
+ private const PROPERTY_TYPE_ARRAY = [
+ 'i' => self::PROPERTY_TYPE_INTEGER, // Integer
+ 'i1' => self::PROPERTY_TYPE_INTEGER, // 1-Byte Signed Integer
+ 'i2' => self::PROPERTY_TYPE_INTEGER, // 2-Byte Signed Integer
+ 'i4' => self::PROPERTY_TYPE_INTEGER, // 4-Byte Signed Integer
+ 'i8' => self::PROPERTY_TYPE_INTEGER, // 8-Byte Signed Integer
+ 'int' => self::PROPERTY_TYPE_INTEGER, // Integer
+ 'ui1' => self::PROPERTY_TYPE_INTEGER, // 1-Byte Unsigned Integer
+ 'ui2' => self::PROPERTY_TYPE_INTEGER, // 2-Byte Unsigned Integer
+ 'ui4' => self::PROPERTY_TYPE_INTEGER, // 4-Byte Unsigned Integer
+ 'ui8' => self::PROPERTY_TYPE_INTEGER, // 8-Byte Unsigned Integer
+ 'uint' => self::PROPERTY_TYPE_INTEGER, // Unsigned Integer
+ 'f' => self::PROPERTY_TYPE_FLOAT, // Real Number
+ 'r4' => self::PROPERTY_TYPE_FLOAT, // 4-Byte Real Number
+ 'r8' => self::PROPERTY_TYPE_FLOAT, // 8-Byte Real Number
+ 'decimal' => self::PROPERTY_TYPE_FLOAT, // Decimal
+ 's' => self::PROPERTY_TYPE_STRING, // String
+ 'empty' => self::PROPERTY_TYPE_STRING, // Empty
+ 'null' => self::PROPERTY_TYPE_STRING, // Null
+ 'lpstr' => self::PROPERTY_TYPE_STRING, // LPSTR
+ 'lpwstr' => self::PROPERTY_TYPE_STRING, // LPWSTR
+ 'bstr' => self::PROPERTY_TYPE_STRING, // Basic String
+ 'd' => self::PROPERTY_TYPE_DATE, // Date and Time
+ 'date' => self::PROPERTY_TYPE_DATE, // Date and Time
+ 'filetime' => self::PROPERTY_TYPE_DATE, // File Time
+ 'b' => self::PROPERTY_TYPE_BOOLEAN, // Boolean
+ 'bool' => self::PROPERTY_TYPE_BOOLEAN, // Boolean
+ ];
+
+ private const SPECIAL_TYPES = [
+ 'empty' => '',
+ 'null' => null,
+ ];
+
+ /**
+ * Convert property to form desired by Excel.
+ *
+ * @param mixed $propertyValue
+ *
+ * @return mixed
+ */
+ public static function convertProperty($propertyValue, string $propertyType)
+ {
+ return self::SPECIAL_TYPES[$propertyType] ?? self::convertProperty2($propertyValue, $propertyType);
+ }
+
+ /**
+ * Convert property to form desired by Excel.
+ *
+ * @param mixed $propertyValue
+ *
+ * @return mixed
+ */
+ private static function convertProperty2($propertyValue, string $type)
+ {
+ $propertyType = self::convertPropertyType($type);
+ switch ($propertyType) {
+ case self::PROPERTY_TYPE_INTEGER:
+ $intValue = (int) $propertyValue;
+
+ return ($type[0] === 'u') ? abs($intValue) : $intValue;
+ case self::PROPERTY_TYPE_FLOAT:
+ return (float) $propertyValue;
+ case self::PROPERTY_TYPE_DATE:
+ return self::intOrFloatTimestamp($propertyValue);
+ case self::PROPERTY_TYPE_BOOLEAN:
+ return is_bool($propertyValue) ? $propertyValue : ($propertyValue === 'true');
+ default: // includes string
+ return $propertyValue;
+ }
+ }
+
+ public static function convertPropertyType(string $propertyType): string
+ {
+ return self::PROPERTY_TYPE_ARRAY[$propertyType] ?? self::PROPERTY_TYPE_UNKNOWN;
+ }
+
+ public function getHyperlinkBase(): string
+ {
+ return $this->hyperlinkBase;
+ }
+
+ public function setHyperlinkBase(string $hyperlinkBase): self
+ {
+ $this->hyperlinkBase = $hyperlinkBase;
+
+ return $this;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php
new file mode 100644
index 000000000..279aed437
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Security.php
@@ -0,0 +1,152 @@
+lockRevision ||
+ $this->lockStructure ||
+ $this->lockWindows;
+ }
+
+ public function getLockRevision(): bool
+ {
+ return $this->lockRevision;
+ }
+
+ public function setLockRevision(?bool $locked): self
+ {
+ if ($locked !== null) {
+ $this->lockRevision = $locked;
+ }
+
+ return $this;
+ }
+
+ public function getLockStructure(): bool
+ {
+ return $this->lockStructure;
+ }
+
+ public function setLockStructure(?bool $locked): self
+ {
+ if ($locked !== null) {
+ $this->lockStructure = $locked;
+ }
+
+ return $this;
+ }
+
+ public function getLockWindows(): bool
+ {
+ return $this->lockWindows;
+ }
+
+ public function setLockWindows(?bool $locked): self
+ {
+ if ($locked !== null) {
+ $this->lockWindows = $locked;
+ }
+
+ return $this;
+ }
+
+ public function getRevisionsPassword(): string
+ {
+ return $this->revisionsPassword;
+ }
+
+ /**
+ * Set RevisionsPassword.
+ *
+ * @param string $password
+ * @param bool $alreadyHashed If the password has already been hashed, set this to true
+ *
+ * @return $this
+ */
+ public function setRevisionsPassword(?string $password, bool $alreadyHashed = false)
+ {
+ if ($password !== null) {
+ if (!$alreadyHashed) {
+ $password = PasswordHasher::hashPassword($password);
+ }
+ $this->revisionsPassword = $password;
+ }
+
+ return $this;
+ }
+
+ public function getWorkbookPassword(): string
+ {
+ return $this->workbookPassword;
+ }
+
+ /**
+ * Set WorkbookPassword.
+ *
+ * @param string $password
+ * @param bool $alreadyHashed If the password has already been hashed, set this to true
+ *
+ * @return $this
+ */
+ public function setWorkbookPassword(?string $password, bool $alreadyHashed = false)
+ {
+ if ($password !== null) {
+ if (!$alreadyHashed) {
+ $password = PasswordHasher::hashPassword($password);
+ }
+ $this->workbookPassword = $password;
+ }
+
+ return $this;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php
new file mode 100644
index 000000000..9c5ab30ee
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php
@@ -0,0 +1,7 @@
+
+ */
+ protected $items = [];
+
+ /**
+ * HashTable key map.
+ *
+ * @var array' . str_replace('_', ' ', $this->getScriptFilename()) . '
';
+ }
+
+ /**
+ * Returns an array of all known samples.
+ *
+ * @return string[][] [$name => $path]
+ */
+ public function getSamples()
+ {
+ // Populate samples
+ $baseDir = realpath(__DIR__ . '/../../../samples');
+ if ($baseDir === false) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException('realpath returned false');
+ // @codeCoverageIgnoreEnd
+ }
+ $directory = new RecursiveDirectoryIterator($baseDir);
+ $iterator = new RecursiveIteratorIterator($directory);
+ $regex = new RegexIterator($iterator, '/^.+\.php$/', RecursiveRegexIterator::GET_MATCH);
+
+ $files = [];
+ foreach ($regex as $file) {
+ $file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0]));
+ if (is_array($file)) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException('str_replace returned array');
+ // @codeCoverageIgnoreEnd
+ }
+ $info = pathinfo($file);
+ $category = str_replace('_', ' ', $info['dirname'] ?? '');
+ $name = str_replace('_', ' ', (string) preg_replace('/(|\.php)/', '', $info['filename']));
+ if (!in_array($category, ['.', 'boostrap', 'templates'])) {
+ if (!isset($files[$category])) {
+ $files[$category] = [];
+ }
+ $files[$category][$name] = $file;
+ }
+ }
+
+ // Sort everything
+ ksort($files);
+ foreach ($files as &$f) {
+ asort($f);
+ }
+
+ return $files;
+ }
+
+ /**
+ * Write documents.
+ *
+ * @param string $filename
+ * @param string[] $writers
+ */
+ public function write(Spreadsheet $spreadsheet, $filename, array $writers = ['Xlsx', 'Xls'], bool $withCharts = false, ?callable $writerCallback = null): void
+ {
+ // Set active sheet index to the first sheet, so Excel opens this as the first sheet
+ $spreadsheet->setActiveSheetIndex(0);
+
+ // Write documents
+ foreach ($writers as $writerType) {
+ $path = $this->getFilename($filename, mb_strtolower($writerType));
+ $writer = IOFactory::createWriter($spreadsheet, $writerType);
+ $writer->setIncludeCharts($withCharts);
+ if ($writerCallback !== null) {
+ $writerCallback($writer);
+ }
+ $callStartTime = microtime(true);
+ if (PHP_VERSION_ID >= 80400 && $writer instanceof Dompdf) {
+ @$writer->save($path);
+ } else {
+ $writer->save($path);
+ }
+ $this->logWrite($writer, $path, /** @scrutinizer ignore-type */ $callStartTime);
+ if ($this->isCli() === false) {
+ echo 'Download ' . basename($path) . '
';
+ }
+ }
+
+ $this->logEndingNotes();
+ }
+
+ protected function isDirOrMkdir(string $folder): bool
+ {
+ return \is_dir($folder) || \mkdir($folder);
+ }
+
+ /**
+ * Returns the temporary directory and make sure it exists.
+ *
+ * @return string
+ */
+ public function getTemporaryFolder()
+ {
+ $tempFolder = sys_get_temp_dir() . '/phpspreadsheet';
+ if (!$this->isDirOrMkdir($tempFolder)) {
+ throw new RuntimeException(sprintf('Directory "%s" was not created', $tempFolder));
+ }
+
+ return $tempFolder;
+ }
+
+ /**
+ * Returns the filename that should be used for sample output.
+ *
+ * @param string $filename
+ * @param string $extension
+ */
+ public function getFilename($filename, $extension = 'xlsx'): string
+ {
+ $originalExtension = pathinfo($filename, PATHINFO_EXTENSION);
+
+ return $this->getTemporaryFolder() . '/' . str_replace('.' . /** @scrutinizer ignore-type */ $originalExtension, '.' . $extension, basename($filename));
+ }
+
+ /**
+ * Return a random temporary file name.
+ *
+ * @param string $extension
+ *
+ * @return string
+ */
+ public function getTemporaryFilename($extension = 'xlsx')
+ {
+ $temporaryFilename = tempnam($this->getTemporaryFolder(), 'phpspreadsheet-');
+ if ($temporaryFilename === false) {
+ // @codeCoverageIgnoreStart
+ throw new RuntimeException('tempnam returned false');
+ // @codeCoverageIgnoreEnd
+ }
+ unlink($temporaryFilename);
+
+ return $temporaryFilename . '.' . $extension;
+ }
+
+ public function log(string $message): void
+ {
+ $eol = $this->isCli() ? PHP_EOL : '
';
+ echo($this->isCli() ? date('H:i:s ') : '') . $message . $eol;
+ }
+
+ public function renderChart(Chart $chart, string $fileName): void
+ {
+ if ($this->isCli() === true) {
+ return;
+ }
+
+ Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class);
+
+ $fileName = $this->getFilename($fileName, 'png');
+
+ try {
+ $chart->render($fileName);
+ $this->log('Rendered image: ' . $fileName);
+ $imageData = file_get_contents($fileName);
+ if ($imageData !== false) {
+ echo '{$path}
in " . sprintf('%.4f', $callTime) . ' seconds';
+
+ $this->log($message);
+ }
+
+ /**
+ * Log a line about the read operation.
+ *
+ * @param string $format
+ * @param string $path
+ * @param float $callStartTime
+ */
+ public function logRead($format, $path, $callStartTime): void
+ {
+ $callEndTime = microtime(true);
+ $callTime = $callEndTime - $callStartTime;
+ $message = "Read {$format} format from {$path}
in " . sprintf('%.4f', $callTime) . ' seconds';
+
+ $this->log($message);
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Size.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Size.php
new file mode 100644
index 000000000..12ba4ef73
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Size.php
@@ -0,0 +1,52 @@
+\d*\.?\d+)(?P';
+
+ $maxRow = max($this->rows);
+ $maxRowLength = mb_strlen((string) $maxRow) + 1;
+ $columnWidths = $this->getColumnWidths();
+
+ $this->renderColumnHeader($maxRowLength, $columnWidths);
+ $this->renderRows($maxRowLength, $columnWidths);
+ $this->renderFooter($maxRowLength, $columnWidths);
+
+ $this->gridDisplay .= $this->isCli ? '' : '
';
+
+ return $this->gridDisplay;
+ }
+
+ private function renderRows(int $maxRowLength, array $columnWidths): void
+ {
+ foreach ($this->matrix as $row => $rowData) {
+ $this->gridDisplay .= '|' . str_pad((string) $this->rows[$row], $maxRowLength, ' ', STR_PAD_LEFT) . ' ';
+ $this->renderCells($rowData, $columnWidths);
+ $this->gridDisplay .= '|' . PHP_EOL;
+ }
+ }
+
+ private function renderCells(array $rowData, array $columnWidths): void
+ {
+ foreach ($rowData as $column => $cell) {
+ $displayCell = ($this->isCli) ? (string) $cell : htmlentities((string) $cell);
+ $this->gridDisplay .= '| ';
+ $this->gridDisplay .= $displayCell . str_repeat(' ', $columnWidths[$column] - mb_strlen($cell ?? '') + 1);
+ }
+ }
+
+ private function renderColumnHeader(int $maxRowLength, array $columnWidths): void
+ {
+ $this->gridDisplay .= str_repeat(' ', $maxRowLength + 2);
+ foreach ($this->columns as $column => $reference) {
+ $this->gridDisplay .= '+-' . str_repeat('-', $columnWidths[$column] + 1);
+ }
+ $this->gridDisplay .= '+' . PHP_EOL;
+
+ $this->gridDisplay .= str_repeat(' ', $maxRowLength + 2);
+ foreach ($this->columns as $column => $reference) {
+ $this->gridDisplay .= '| ' . str_pad((string) $reference, $columnWidths[$column] + 1, ' ');
+ }
+ $this->gridDisplay .= '|' . PHP_EOL;
+
+ $this->renderFooter($maxRowLength, $columnWidths);
+ }
+
+ private function renderFooter(int $maxRowLength, array $columnWidths): void
+ {
+ $this->gridDisplay .= '+' . str_repeat('-', $maxRowLength + 1);
+ foreach ($this->columns as $column => $reference) {
+ $this->gridDisplay .= '+-';
+ $this->gridDisplay .= str_pad((string) '', $columnWidths[$column] + 1, '-');
+ }
+ $this->gridDisplay .= '+' . PHP_EOL;
+ }
+
+ private function getColumnWidths(): array
+ {
+ $columnCount = count($this->matrix, COUNT_RECURSIVE) / count($this->matrix);
+ $columnWidths = [];
+ for ($column = 0; $column < $columnCount; ++$column) {
+ $columnWidths[] = $this->getColumnWidth(array_column($this->matrix, $column));
+ }
+
+ return $columnWidths;
+ }
+
+ private function getColumnWidth(array $columnData): int
+ {
+ $columnWidth = 0;
+ $columnData = array_values($columnData);
+
+ foreach ($columnData as $columnValue) {
+ if (is_string($columnValue)) {
+ $columnWidth = max($columnWidth, mb_strlen($columnValue));
+ } elseif (is_bool($columnValue)) {
+ $columnWidth = max($columnWidth, mb_strlen($columnValue ? 'TRUE' : 'FALSE'));
+ }
+
+ $columnWidth = max($columnWidth, mb_strlen((string) $columnWidth));
+ }
+
+ return $columnWidth;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php
new file mode 100644
index 000000000..c215847b3
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php
@@ -0,0 +1,13 @@
+ Reader\Xlsx::class,
+ self::READER_XLS => Reader\Xls::class,
+ self::READER_XML => Reader\Xml::class,
+ self::READER_ODS => Reader\Ods::class,
+ self::READER_SLK => Reader\Slk::class,
+ self::READER_GNUMERIC => Reader\Gnumeric::class,
+ self::READER_HTML => Reader\Html::class,
+ self::READER_CSV => Reader\Csv::class,
+ ];
+
+ /** @var string[] */
+ private static $writers = [
+ self::WRITER_XLS => Writer\Xls::class,
+ self::WRITER_XLSX => Writer\Xlsx::class,
+ self::WRITER_ODS => Writer\Ods::class,
+ self::WRITER_CSV => Writer\Csv::class,
+ self::WRITER_HTML => Writer\Html::class,
+ 'Tcpdf' => Writer\Pdf\Tcpdf::class,
+ 'Dompdf' => Writer\Pdf\Dompdf::class,
+ 'Mpdf' => Writer\Pdf\Mpdf::class,
+ ];
+
+ /**
+ * Create Writer\IWriter.
+ */
+ public static function createWriter(Spreadsheet $spreadsheet, string $writerType): IWriter
+ {
+ if (!isset(self::$writers[$writerType])) {
+ throw new Writer\Exception("No writer found for type $writerType");
+ }
+
+ // Instantiate writer
+ /** @var IWriter */
+ $className = self::$writers[$writerType];
+
+ return new $className($spreadsheet);
+ }
+
+ /**
+ * Create IReader.
+ */
+ public static function createReader(string $readerType): IReader
+ {
+ if (!isset(self::$readers[$readerType])) {
+ throw new Reader\Exception("No reader found for type $readerType");
+ }
+
+ // Instantiate reader
+ /** @var IReader */
+ $className = self::$readers[$readerType];
+
+ return new $className();
+ }
+
+ /**
+ * Loads Spreadsheet from file using automatic Reader\IReader resolution.
+ *
+ * @param string $filename The name of the spreadsheet file
+ * @param int $flags the optional second parameter flags may be used to identify specific elements
+ * that should be loaded, but which won't be loaded by default, using these values:
+ * IReader::LOAD_WITH_CHARTS - Include any charts that are defined in the loaded file.
+ * IReader::READ_DATA_ONLY - Read cell values only, not formatting or merge structure.
+ * IReader::IGNORE_EMPTY_CELLS - Don't load empty cells into the model.
+ * @param string[] $readers An array of Readers to use to identify the file type. By default, load() will try
+ * all possible Readers until it finds a match; but this allows you to pass in a
+ * list of Readers so it will only try the subset that you specify here.
+ * Values in this list can be any of the constant values defined in the set
+ * IOFactory::READER_*.
+ */
+ public static function load(string $filename, int $flags = 0, ?array $readers = null): Spreadsheet
+ {
+ $reader = self::createReaderForFile($filename, $readers);
+
+ return $reader->load($filename, $flags);
+ }
+
+ /**
+ * Identify file type using automatic IReader resolution.
+ */
+ public static function identify(string $filename, ?array $readers = null): string
+ {
+ $reader = self::createReaderForFile($filename, $readers);
+ $className = get_class($reader);
+ $classType = explode('\\', $className);
+ unset($reader);
+
+ return array_pop($classType);
+ }
+
+ /**
+ * Create Reader\IReader for file using automatic IReader resolution.
+ *
+ * @param string[] $readers An array of Readers to use to identify the file type. By default, load() will try
+ * all possible Readers until it finds a match; but this allows you to pass in a
+ * list of Readers so it will only try the subset that you specify here.
+ * Values in this list can be any of the constant values defined in the set
+ * IOFactory::READER_*.
+ */
+ public static function createReaderForFile(string $filename, ?array $readers = null): IReader
+ {
+ File::assertFile($filename);
+
+ $testReaders = self::$readers;
+ if ($readers !== null) {
+ $readers = array_map('strtoupper', $readers);
+ $testReaders = array_filter(
+ self::$readers,
+ function (string $readerType) use ($readers) {
+ return in_array(strtoupper($readerType), $readers, true);
+ },
+ ARRAY_FILTER_USE_KEY
+ );
+ }
+
+ // First, lucky guess by inspecting file extension
+ $guessedReader = self::getReaderTypeFromExtension($filename);
+ if (($guessedReader !== null) && array_key_exists($guessedReader, $testReaders)) {
+ $reader = self::createReader($guessedReader);
+
+ // Let's see if we are lucky
+ if ($reader->canRead($filename)) {
+ return $reader;
+ }
+ }
+
+ // If we reach here then "lucky guess" didn't give any result
+ // Try walking through all the options in self::$readers (or the selected subset)
+ foreach ($testReaders as $readerType => $class) {
+ // Ignore our original guess, we know that won't work
+ if ($readerType !== $guessedReader) {
+ $reader = self::createReader($readerType);
+ if ($reader->canRead($filename)) {
+ return $reader;
+ }
+ }
+ }
+
+ throw new Reader\Exception('Unable to identify a reader for this file');
+ }
+
+ /**
+ * Guess a reader type from the file extension, if any.
+ */
+ private static function getReaderTypeFromExtension(string $filename): ?string
+ {
+ $pathinfo = pathinfo($filename);
+ if (!isset($pathinfo['extension'])) {
+ return null;
+ }
+
+ switch (strtolower($pathinfo['extension'])) {
+ case 'xlsx': // Excel (OfficeOpenXML) Spreadsheet
+ case 'xlsm': // Excel (OfficeOpenXML) Macro Spreadsheet (macros will be discarded)
+ case 'xltx': // Excel (OfficeOpenXML) Template
+ case 'xltm': // Excel (OfficeOpenXML) Macro Template (macros will be discarded)
+ return 'Xlsx';
+ case 'xls': // Excel (BIFF) Spreadsheet
+ case 'xlt': // Excel (BIFF) Template
+ return 'Xls';
+ case 'ods': // Open/Libre Offic Calc
+ case 'ots': // Open/Libre Offic Calc Template
+ return 'Ods';
+ case 'slk':
+ return 'Slk';
+ case 'xml': // Excel 2003 SpreadSheetML
+ return 'Xml';
+ case 'gnumeric':
+ return 'Gnumeric';
+ case 'htm':
+ case 'html':
+ return 'Html';
+ case 'csv':
+ // Do nothing
+ // We must not try to use CSV reader since it loads
+ // all files including Excel files etc.
+ return null;
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Register a writer with its type and class name.
+ */
+ public static function registerWriter(string $writerType, string $writerClass): void
+ {
+ if (!is_a($writerClass, IWriter::class, true)) {
+ throw new Writer\Exception('Registered writers must implement ' . IWriter::class);
+ }
+
+ self::$writers[$writerType] = $writerClass;
+ }
+
+ /**
+ * Register a reader with its type and class name.
+ */
+ public static function registerReader(string $readerType, string $readerClass): void
+ {
+ if (!is_a($readerClass, IReader::class, true)) {
+ throw new Reader\Exception('Registered readers must implement ' . IReader::class);
+ }
+
+ self::$readers[$readerType] = $readerClass;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php
new file mode 100644
index 000000000..500151f0b
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php
@@ -0,0 +1,45 @@
+value;
+ }
+
+ /**
+ * Set the formula value.
+ */
+ public function setFormula(string $formula): self
+ {
+ if (!empty($formula)) {
+ $this->value = $formula;
+ }
+
+ return $this;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php
new file mode 100644
index 000000000..db9c5f121
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php
@@ -0,0 +1,55 @@
+value;
+ }
+
+ /**
+ * Set the range value.
+ */
+ public function setRange(string $range): self
+ {
+ if (!empty($range)) {
+ $this->value = $range;
+ }
+
+ return $this;
+ }
+
+ public function getCellsInRange(): array
+ {
+ $range = $this->value;
+ if (substr($range, 0, 1) === '=') {
+ $range = substr($range, 1);
+ }
+
+ return Coordinate::extractAllCellReferencesInRange($range);
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php
new file mode 100644
index 000000000..aa380aa95
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/BaseReader.php
@@ -0,0 +1,206 @@
+readFilter = new DefaultReadFilter();
+ }
+
+ public function getReadDataOnly()
+ {
+ return $this->readDataOnly;
+ }
+
+ public function setReadDataOnly($readCellValuesOnly)
+ {
+ $this->readDataOnly = (bool) $readCellValuesOnly;
+
+ return $this;
+ }
+
+ public function getReadEmptyCells()
+ {
+ return $this->readEmptyCells;
+ }
+
+ public function setReadEmptyCells($readEmptyCells)
+ {
+ $this->readEmptyCells = (bool) $readEmptyCells;
+
+ return $this;
+ }
+
+ public function getIncludeCharts()
+ {
+ return $this->includeCharts;
+ }
+
+ public function setIncludeCharts($includeCharts)
+ {
+ $this->includeCharts = (bool) $includeCharts;
+
+ return $this;
+ }
+
+ public function getLoadSheetsOnly()
+ {
+ return $this->loadSheetsOnly;
+ }
+
+ public function setLoadSheetsOnly($sheetList)
+ {
+ if ($sheetList === null) {
+ return $this->setLoadAllSheets();
+ }
+
+ $this->loadSheetsOnly = is_array($sheetList) ? $sheetList : [$sheetList];
+
+ return $this;
+ }
+
+ public function setLoadAllSheets()
+ {
+ $this->loadSheetsOnly = null;
+
+ return $this;
+ }
+
+ public function getReadFilter()
+ {
+ return $this->readFilter;
+ }
+
+ public function setReadFilter(IReadFilter $readFilter)
+ {
+ $this->readFilter = $readFilter;
+
+ return $this;
+ }
+
+ public function getSecurityScanner(): ?XmlScanner
+ {
+ return $this->securityScanner;
+ }
+
+ public function getSecurityScannerOrThrow(): XmlScanner
+ {
+ if ($this->securityScanner === null) {
+ throw new ReaderException('Security scanner is unexpectedly null');
+ }
+
+ return $this->securityScanner;
+ }
+
+ protected function processFlags(int $flags): void
+ {
+ if (((bool) ($flags & self::LOAD_WITH_CHARTS)) === true) {
+ $this->setIncludeCharts(true);
+ }
+ if (((bool) ($flags & self::READ_DATA_ONLY)) === true) {
+ $this->setReadDataOnly(true);
+ }
+ if (((bool) ($flags & self::SKIP_EMPTY_CELLS) || (bool) ($flags & self::IGNORE_EMPTY_CELLS)) === true) {
+ $this->setReadEmptyCells(false);
+ }
+ }
+
+ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
+ {
+ throw new PhpSpreadsheetException('Reader classes must implement their own loadSpreadsheetFromFile() method');
+ }
+
+ /**
+ * Loads Spreadsheet from file.
+ *
+ * @param int $flags the optional second parameter flags may be used to identify specific elements
+ * that should be loaded, but which won't be loaded by default, using these values:
+ * IReader::LOAD_WITH_CHARTS - Include any charts that are defined in the loaded file
+ */
+ public function load(string $filename, int $flags = 0): Spreadsheet
+ {
+ $this->processFlags($flags);
+
+ try {
+ return $this->loadSpreadsheetFromFile($filename);
+ } catch (ReaderException $e) {
+ throw $e;
+ }
+ }
+
+ /**
+ * Open file for reading.
+ */
+ protected function openFile(string $filename): void
+ {
+ $fileHandle = false;
+ if ($filename) {
+ File::assertFile($filename);
+
+ // Open file
+ $fileHandle = fopen($filename, 'rb');
+ }
+ if ($fileHandle === false) {
+ throw new ReaderException('Could not open file ' . $filename . ' for reading.');
+ }
+
+ $this->fileHandle = $fileHandle;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php
new file mode 100644
index 000000000..3feec8e88
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Csv.php
@@ -0,0 +1,682 @@
+inputEncoding = $encoding;
+
+ return $this;
+ }
+
+ public function getInputEncoding(): string
+ {
+ return $this->inputEncoding;
+ }
+
+ public function setFallbackEncoding(string $fallbackEncoding): self
+ {
+ $this->fallbackEncoding = $fallbackEncoding;
+
+ return $this;
+ }
+
+ public function getFallbackEncoding(): string
+ {
+ return $this->fallbackEncoding;
+ }
+
+ /**
+ * Move filepointer past any BOM marker.
+ */
+ protected function skipBOM(): void
+ {
+ rewind($this->fileHandle);
+
+ if (fgets($this->fileHandle, self::UTF8_BOM_LEN + 1) !== self::UTF8_BOM) {
+ rewind($this->fileHandle);
+ }
+ }
+
+ /**
+ * Identify any separator that is explicitly set in the file.
+ */
+ protected function checkSeparator(): void
+ {
+ $line = fgets($this->fileHandle);
+ if ($line === false) {
+ return;
+ }
+
+ if ((strlen(trim($line, "\r\n")) == 5) && (stripos($line, 'sep=') === 0)) {
+ $this->delimiter = substr($line, 4, 1);
+
+ return;
+ }
+
+ $this->skipBOM();
+ }
+
+ /**
+ * Infer the separator if it isn't explicitly set in the file or specified by the user.
+ */
+ protected function inferSeparator(): void
+ {
+ if ($this->delimiter !== null) {
+ return;
+ }
+
+ $inferenceEngine = new Delimiter($this->fileHandle, $this->escapeCharacter ?? self::$defaultEscapeCharacter, $this->enclosure);
+
+ // If number of lines is 0, nothing to infer : fall back to the default
+ if ($inferenceEngine->linesCounted() === 0) {
+ $this->delimiter = $inferenceEngine->getDefaultDelimiter();
+ $this->skipBOM();
+
+ return;
+ }
+
+ $this->delimiter = $inferenceEngine->infer();
+
+ // If no delimiter could be detected, fall back to the default
+ if ($this->delimiter === null) {
+ $this->delimiter = $inferenceEngine->getDefaultDelimiter();
+ }
+
+ $this->skipBOM();
+ }
+
+ /**
+ * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
+ */
+ public function listWorksheetInfo(string $filename): array
+ {
+ // Open file
+ $this->openFileOrMemory($filename);
+ $fileHandle = $this->fileHandle;
+
+ // Skip BOM, if any
+ $this->skipBOM();
+ $this->checkSeparator();
+ $this->inferSeparator();
+
+ $worksheetInfo = [];
+ $worksheetInfo[0]['worksheetName'] = 'Worksheet';
+ $worksheetInfo[0]['lastColumnLetter'] = 'A';
+ $worksheetInfo[0]['lastColumnIndex'] = 0;
+ $worksheetInfo[0]['totalRows'] = 0;
+ $worksheetInfo[0]['totalColumns'] = 0;
+
+ // Loop through each line of the file in turn
+ $rowData = self::getCsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter);
+ while (is_array($rowData)) {
+ ++$worksheetInfo[0]['totalRows'];
+ $worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], count($rowData) - 1);
+ $rowData = self::getCsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter);
+ }
+
+ $worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1);
+ $worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1;
+
+ // Close file
+ fclose($fileHandle);
+
+ return $worksheetInfo;
+ }
+
+ /**
+ * Loads Spreadsheet from file.
+ */
+ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
+ {
+ // Create new Spreadsheet
+ $spreadsheet = new Spreadsheet();
+
+ // Load into this instance
+ return $this->loadIntoExisting($filename, $spreadsheet);
+ }
+
+ /**
+ * Loads Spreadsheet from string.
+ */
+ public function loadSpreadsheetFromString(string $contents): Spreadsheet
+ {
+ // Create new Spreadsheet
+ $spreadsheet = new Spreadsheet();
+
+ // Load into this instance
+ return $this->loadStringOrFile('data://text/plain,' . urlencode($contents), $spreadsheet, true);
+ }
+
+ private function openFileOrMemory(string $filename): void
+ {
+ // Open file
+ $fhandle = $this->canRead($filename);
+ if (!$fhandle) {
+ throw new Exception($filename . ' is an Invalid Spreadsheet file.');
+ }
+ if ($this->inputEncoding === self::GUESS_ENCODING) {
+ $this->inputEncoding = self::guessEncoding($filename, $this->fallbackEncoding);
+ }
+ $this->openFile($filename);
+ if ($this->inputEncoding !== 'UTF-8') {
+ fclose($this->fileHandle);
+ $entireFile = file_get_contents($filename);
+ $fileHandle = fopen('php://memory', 'r+b');
+ if ($fileHandle !== false && $entireFile !== false) {
+ $this->fileHandle = $fileHandle;
+ $data = StringHelper::convertEncoding($entireFile, 'UTF-8', $this->inputEncoding);
+ fwrite($this->fileHandle, $data);
+ $this->skipBOM();
+ }
+ }
+ }
+
+ public function setTestAutoDetect(bool $value): self
+ {
+ $this->testAutodetect = $value;
+
+ return $this;
+ }
+
+ private function setAutoDetect(?string $value): ?string
+ {
+ $retVal = null;
+ if ($value !== null && $this->testAutodetect) {
+ $retVal2 = @ini_set('auto_detect_line_endings', $value);
+ if (is_string($retVal2)) {
+ $retVal = $retVal2;
+ }
+ }
+
+ return $retVal;
+ }
+
+ public function castFormattedNumberToNumeric(
+ bool $castFormattedNumberToNumeric,
+ bool $preserveNumericFormatting = false
+ ): void {
+ $this->castFormattedNumberToNumeric = $castFormattedNumberToNumeric;
+ $this->preserveNumericFormatting = $preserveNumericFormatting;
+ }
+
+ /**
+ * Open data uri for reading.
+ */
+ private function openDataUri(string $filename): void
+ {
+ $fileHandle = fopen($filename, 'rb');
+ if ($fileHandle === false) {
+ // @codeCoverageIgnoreStart
+ throw new ReaderException('Could not open file ' . $filename . ' for reading.');
+ // @codeCoverageIgnoreEnd
+ }
+
+ $this->fileHandle = $fileHandle;
+ }
+
+ /**
+ * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
+ */
+ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet
+ {
+ return $this->loadStringOrFile($filename, $spreadsheet, false);
+ }
+
+ /**
+ * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
+ */
+ private function loadStringOrFile(string $filename, Spreadsheet $spreadsheet, bool $dataUri): Spreadsheet
+ {
+ // Deprecated in Php8.1
+ $iniset = $this->setAutoDetect('1');
+
+ // Open file
+ if ($dataUri) {
+ $this->openDataUri($filename);
+ } else {
+ $this->openFileOrMemory($filename);
+ }
+ $fileHandle = $this->fileHandle;
+
+ // Skip BOM, if any
+ $this->skipBOM();
+ $this->checkSeparator();
+ $this->inferSeparator();
+
+ // Create new PhpSpreadsheet object
+ while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
+ $spreadsheet->createSheet();
+ }
+ $sheet = $spreadsheet->setActiveSheetIndex($this->sheetIndex);
+
+ // Set our starting row based on whether we're in contiguous mode or not
+ $currentRow = 1;
+ $outRow = 0;
+
+ // Loop through each line of the file in turn
+ $rowData = self::getCsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter);
+ $valueBinder = Cell::getValueBinder();
+ $preserveBooleanString = method_exists($valueBinder, 'getBooleanConversion') && $valueBinder->getBooleanConversion();
+ while (is_array($rowData)) {
+ $noOutputYet = true;
+ $columnLetter = 'A';
+ foreach ($rowData as $rowDatum) {
+ $this->convertBoolean($rowDatum, $preserveBooleanString);
+ $numberFormatMask = $this->convertFormattedNumber($rowDatum);
+ if (($rowDatum !== '' || $this->preserveNullString) && $this->readFilter->readCell($columnLetter, $currentRow)) {
+ if ($this->contiguous) {
+ if ($noOutputYet) {
+ $noOutputYet = false;
+ ++$outRow;
+ }
+ } else {
+ $outRow = $currentRow;
+ }
+ // Set basic styling for the value (Note that this could be overloaded by styling in a value binder)
+ $sheet->getCell($columnLetter . $outRow)->getStyle()
+ ->getNumberFormat()
+ ->setFormatCode($numberFormatMask);
+ // Set cell value
+ $sheet->getCell($columnLetter . $outRow)->setValue($rowDatum);
+ }
+ ++$columnLetter;
+ }
+ $rowData = self::getCsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter);
+ ++$currentRow;
+ }
+
+ // Close file
+ fclose($fileHandle);
+
+ $this->setAutoDetect($iniset);
+
+ // Return
+ return $spreadsheet;
+ }
+
+ /**
+ * Convert string true/false to boolean, and null to null-string.
+ *
+ * @param mixed $rowDatum
+ */
+ private function convertBoolean(&$rowDatum, bool $preserveBooleanString): void
+ {
+ if (is_string($rowDatum) && !$preserveBooleanString) {
+ if (strcasecmp(Calculation::getTRUE(), $rowDatum) === 0 || strcasecmp('true', $rowDatum) === 0) {
+ $rowDatum = true;
+ } elseif (strcasecmp(Calculation::getFALSE(), $rowDatum) === 0 || strcasecmp('false', $rowDatum) === 0) {
+ $rowDatum = false;
+ }
+ } else {
+ $rowDatum = $rowDatum ?? '';
+ }
+ }
+
+ /**
+ * Convert numeric strings to int or float values.
+ *
+ * @param mixed $rowDatum
+ */
+ private function convertFormattedNumber(&$rowDatum): string
+ {
+ $numberFormatMask = NumberFormat::FORMAT_GENERAL;
+ if ($this->castFormattedNumberToNumeric === true && is_string($rowDatum)) {
+ $numeric = str_replace(
+ [StringHelper::getThousandsSeparator(), StringHelper::getDecimalSeparator()],
+ ['', '.'],
+ $rowDatum
+ );
+
+ if (is_numeric($numeric)) {
+ $decimalPos = strpos($rowDatum, StringHelper::getDecimalSeparator());
+ if ($this->preserveNumericFormatting === true) {
+ $numberFormatMask = (strpos($rowDatum, StringHelper::getThousandsSeparator()) !== false)
+ ? '#,##0' : '0';
+ if ($decimalPos !== false) {
+ $decimals = strlen($rowDatum) - $decimalPos - 1;
+ $numberFormatMask .= '.' . str_repeat('0', min($decimals, 6));
+ }
+ }
+
+ $rowDatum = ($decimalPos !== false) ? (float) $numeric : (int) $numeric;
+ }
+ }
+
+ return $numberFormatMask;
+ }
+
+ public function getDelimiter(): ?string
+ {
+ return $this->delimiter;
+ }
+
+ public function setDelimiter(?string $delimiter): self
+ {
+ $this->delimiter = $delimiter;
+
+ return $this;
+ }
+
+ public function getEnclosure(): string
+ {
+ return $this->enclosure;
+ }
+
+ public function setEnclosure(string $enclosure): self
+ {
+ if ($enclosure == '') {
+ $enclosure = '"';
+ }
+ $this->enclosure = $enclosure;
+
+ return $this;
+ }
+
+ public function getSheetIndex(): int
+ {
+ return $this->sheetIndex;
+ }
+
+ public function setSheetIndex(int $indexValue): self
+ {
+ $this->sheetIndex = $indexValue;
+
+ return $this;
+ }
+
+ public function setContiguous(bool $contiguous): self
+ {
+ $this->contiguous = $contiguous;
+
+ return $this;
+ }
+
+ public function getContiguous(): bool
+ {
+ return $this->contiguous;
+ }
+
+ /**
+ * Php9 intends to drop support for this parameter in fgetcsv.
+ * Not yet ready to mark deprecated in order to give users
+ * a migration path.
+ */
+ public function setEscapeCharacter(string $escapeCharacter): self
+ {
+ $this->escapeCharacter = $escapeCharacter;
+
+ return $this;
+ }
+
+ public function getEscapeCharacter(): string
+ {
+ return $this->escapeCharacter ?? self::$defaultEscapeCharacter;
+ }
+
+ /**
+ * Can the current IReader read the file?
+ */
+ public function canRead(string $filename): bool
+ {
+ // Check if file exists
+ try {
+ $this->openFile($filename);
+ } catch (ReaderException $e) {
+ return false;
+ }
+
+ fclose($this->fileHandle);
+
+ // Trust file extension if any
+ $extension = strtolower(/** @scrutinizer ignore-type */ pathinfo($filename, PATHINFO_EXTENSION));
+ if (in_array($extension, ['csv', 'tsv'])) {
+ return true;
+ }
+
+ // Attempt to guess mimetype
+ $type = mime_content_type($filename);
+ $supportedTypes = [
+ 'application/csv',
+ 'text/csv',
+ 'text/plain',
+ 'inode/x-empty',
+ ];
+
+ return in_array($type, $supportedTypes, true);
+ }
+
+ private static function guessEncodingTestNoBom(string &$encoding, string &$contents, string $compare, string $setEncoding): void
+ {
+ if ($encoding === '') {
+ $pos = strpos($contents, $compare);
+ if ($pos !== false && $pos % strlen($compare) === 0) {
+ $encoding = $setEncoding;
+ }
+ }
+ }
+
+ private static function guessEncodingNoBom(string $filename): string
+ {
+ $encoding = '';
+ $contents = file_get_contents($filename);
+ self::guessEncodingTestNoBom($encoding, $contents, self::UTF32BE_LF, 'UTF-32BE');
+ self::guessEncodingTestNoBom($encoding, $contents, self::UTF32LE_LF, 'UTF-32LE');
+ self::guessEncodingTestNoBom($encoding, $contents, self::UTF16BE_LF, 'UTF-16BE');
+ self::guessEncodingTestNoBom($encoding, $contents, self::UTF16LE_LF, 'UTF-16LE');
+ if ($encoding === '' && preg_match('//u', $contents) === 1) {
+ $encoding = 'UTF-8';
+ }
+
+ return $encoding;
+ }
+
+ private static function guessEncodingTestBom(string &$encoding, string $first4, string $compare, string $setEncoding): void
+ {
+ if ($encoding === '') {
+ if ($compare === substr($first4, 0, strlen($compare))) {
+ $encoding = $setEncoding;
+ }
+ }
+ }
+
+ private static function guessEncodingBom(string $filename): string
+ {
+ $encoding = '';
+ $first4 = file_get_contents($filename, false, null, 0, 4);
+ if ($first4 !== false) {
+ self::guessEncodingTestBom($encoding, $first4, self::UTF8_BOM, 'UTF-8');
+ self::guessEncodingTestBom($encoding, $first4, self::UTF16BE_BOM, 'UTF-16BE');
+ self::guessEncodingTestBom($encoding, $first4, self::UTF32BE_BOM, 'UTF-32BE');
+ self::guessEncodingTestBom($encoding, $first4, self::UTF32LE_BOM, 'UTF-32LE');
+ self::guessEncodingTestBom($encoding, $first4, self::UTF16LE_BOM, 'UTF-16LE');
+ }
+
+ return $encoding;
+ }
+
+ public static function guessEncoding(string $filename, string $dflt = self::DEFAULT_FALLBACK_ENCODING): string
+ {
+ $encoding = self::guessEncodingBom($filename);
+ if ($encoding === '') {
+ $encoding = self::guessEncodingNoBom($filename);
+ }
+
+ return ($encoding === '') ? $dflt : $encoding;
+ }
+
+ public function setPreserveNullString(bool $value): self
+ {
+ $this->preserveNullString = $value;
+
+ return $this;
+ }
+
+ public function getPreserveNullString(): bool
+ {
+ return $this->preserveNullString;
+ }
+
+ /**
+ * Php8.4 deprecates use of anything other than null string
+ * as escape Character.
+ *
+ * @param resource $stream
+ *
+ * @return array
', '
', $contents);
+ }
+ $rels = @simplexml_load_string(
+ $this->getSecurityScannerOrThrow()->scan($contents),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions(),
+ $ns
+ );
+
+ return self::testSimpleXml($rels);
+ }
+
+ // This function is just to identify cases where I'm not sure
+ // why empty namespace is required.
+ private function loadZipNonamespace(string $filename, string $ns): SimpleXMLElement
+ {
+ $contents = $this->getFromZipArchive($this->zip, $filename);
+ $rels = simplexml_load_string(
+ $this->getSecurityScannerOrThrow()->scan($contents),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions(),
+ ($ns === '' ? $ns : '')
+ );
+
+ return self::testSimpleXml($rels);
+ }
+
+ private const REL_TO_MAIN = [
+ Namespaces::PURL_OFFICE_DOCUMENT => Namespaces::PURL_MAIN,
+ Namespaces::THUMBNAIL => '',
+ ];
+
+ private const REL_TO_DRAWING = [
+ Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_DRAWING,
+ ];
+
+ private const REL_TO_CHART = [
+ Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_CHART,
+ ];
+
+ /**
+ * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
+ *
+ * @param string $filename
+ *
+ * @return array
+ */
+ public function listWorksheetNames($filename)
+ {
+ File::assertFile($filename, self::INITIAL_FILE);
+
+ $worksheetNames = [];
+
+ $this->zip = $zip = new ZipArchive();
+ $zip->open($filename);
+
+ // The files we're looking at here are small enough that simpleXML is more efficient than XMLReader
+ $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS);
+ foreach ($rels->Relationship as $relx) {
+ $rel = self::getAttributes($relx);
+ $relType = (string) $rel['Type'];
+ $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN;
+ if ($mainNS !== '') {
+ $xmlWorkbook = $this->loadZip((string) $rel['Target'], $mainNS);
+
+ if ($xmlWorkbook->sheets) {
+ foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
+ // Check if sheet should be skipped
+ $worksheetNames[] = (string) self::getAttributes($eleSheet)['name'];
+ }
+ }
+ }
+ }
+
+ $zip->close();
+
+ return $worksheetNames;
+ }
+
+ /**
+ * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
+ *
+ * @param string $filename
+ *
+ * @return array
+ */
+ public function listWorksheetInfo($filename)
+ {
+ File::assertFile($filename, self::INITIAL_FILE);
+
+ $worksheetInfo = [];
+
+ $this->zip = $zip = new ZipArchive();
+ $zip->open($filename);
+
+ $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS);
+ foreach ($rels->Relationship as $relx) {
+ $rel = self::getAttributes($relx);
+ $relType = (string) $rel['Type'];
+ $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN;
+ if ($mainNS !== '') {
+ $relTarget = (string) $rel['Target'];
+ $dir = dirname($relTarget);
+ $namespace = dirname($relType);
+ $relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', '');
+
+ $worksheets = [];
+ foreach ($relsWorkbook->Relationship as $elex) {
+ $ele = self::getAttributes($elex);
+ if (
+ ((string) $ele['Type'] === "$namespace/worksheet") ||
+ ((string) $ele['Type'] === "$namespace/chartsheet")
+ ) {
+ $worksheets[(string) $ele['Id']] = $ele['Target'];
+ }
+ }
+
+ $xmlWorkbook = $this->loadZip($relTarget, $mainNS);
+ if ($xmlWorkbook->sheets) {
+ $dir = dirname($relTarget);
+
+ /** @var SimpleXMLElement $eleSheet */
+ foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
+ $tmpInfo = [
+ 'worksheetName' => (string) self::getAttributes($eleSheet)['name'],
+ 'lastColumnLetter' => 'A',
+ 'lastColumnIndex' => 0,
+ 'totalRows' => 0,
+ 'totalColumns' => 0,
+ ];
+
+ $fileWorksheet = (string) $worksheets[(string) self::getArrayItem(self::getAttributes($eleSheet, $namespace), 'id')];
+ $fileWorksheetPath = strpos($fileWorksheet, '/') === 0 ? substr($fileWorksheet, 1) : "$dir/$fileWorksheet";
+
+ $xml = new XMLReader();
+ $xml->xml(
+ $this->getSecurityScannerOrThrow()->scan(
+ $this->getFromZipArchive($this->zip, $fileWorksheetPath)
+ ),
+ null,
+ Settings::getLibXmlLoaderOptions()
+ );
+ $xml->setParserProperty(2, true);
+
+ $currCells = 0;
+ while ($xml->read()) {
+ if ($xml->localName == 'row' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) {
+ $row = $xml->getAttribute('r');
+ $tmpInfo['totalRows'] = $row;
+ $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
+ $currCells = 0;
+ } elseif ($xml->localName == 'c' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) {
+ $cell = $xml->getAttribute('r');
+ $currCells = $cell ? max($currCells, Coordinate::indexesFromString($cell)[0]) : ($currCells + 1);
+ }
+ }
+ $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
+ $xml->close();
+
+ $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
+ $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
+
+ $worksheetInfo[] = $tmpInfo;
+ }
+ }
+ }
+ }
+
+ $zip->close();
+
+ return $worksheetInfo;
+ }
+
+ private static function castToBoolean(SimpleXMLElement $c): bool
+ {
+ $value = isset($c->v) ? (string) $c->v : null;
+ if ($value == '0') {
+ return false;
+ } elseif ($value == '1') {
+ return true;
+ }
+
+ return (bool) $c->v;
+ }
+
+ private static function castToError(?SimpleXMLElement $c): ?string
+ {
+ return isset($c, $c->v) ? (string) $c->v : null;
+ }
+
+ private static function castToString(?SimpleXMLElement $c): ?string
+ {
+ return isset($c, $c->v) ? (string) $c->v : null;
+ }
+
+ /**
+ * @param mixed $value
+ * @param mixed $calculatedValue
+ */
+ private function castToFormula(?SimpleXMLElement $c, string $r, string &$cellDataType, &$value, &$calculatedValue, string $castBaseType, bool $updateSharedCells = true): void
+ {
+ if ($c === null) {
+ return;
+ }
+ $attr = $c->f->attributes();
+ $cellDataType = DataType::TYPE_FORMULA;
+ $value = "={$c->f}";
+ $calculatedValue = self::$castBaseType($c);
+
+ // Shared formula?
+ if (isset($attr['t']) && strtolower((string) $attr['t']) == 'shared') {
+ $instance = (string) $attr['si'];
+
+ if (!isset($this->sharedFormulae[(string) $attr['si']])) {
+ $this->sharedFormulae[$instance] = new SharedFormula($r, $value);
+ } elseif ($updateSharedCells === true) {
+ // It's only worth the overhead of adjusting the shared formula for this cell if we're actually loading
+ // the cell, which may not be the case if we're using a read filter.
+ $master = Coordinate::indexesFromString($this->sharedFormulae[$instance]->master());
+ $current = Coordinate::indexesFromString($r);
+
+ $difference = [0, 0];
+ $difference[0] = $current[0] - $master[0];
+ $difference[1] = $current[1] - $master[1];
+
+ $value = $this->referenceHelper->updateFormulaReferences($this->sharedFormulae[$instance]->formula(), 'A1', $difference[0], $difference[1]);
+ }
+ }
+ }
+
+ /**
+ * @param string $fileName
+ */
+ private function fileExistsInArchive(ZipArchive $archive, $fileName = ''): bool
+ {
+ // Root-relative paths
+ if (strpos($fileName, '//') !== false) {
+ $fileName = substr($fileName, strpos($fileName, '//') + 1);
+ }
+ $fileName = File::realpath($fileName);
+
+ // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming
+ // so we need to load case-insensitively from the zip file
+
+ // Apache POI fixes
+ $contents = $archive->locateName($fileName, ZipArchive::FL_NOCASE);
+ if ($contents === false) {
+ $contents = $archive->locateName(substr($fileName, 1), ZipArchive::FL_NOCASE);
+ }
+
+ return $contents !== false;
+ }
+
+ /**
+ * @param string $fileName
+ *
+ * @return string
+ */
+ private function getFromZipArchive(ZipArchive $archive, $fileName = '')
+ {
+ // Root-relative paths
+ if (strpos($fileName, '//') !== false) {
+ $fileName = substr($fileName, strpos($fileName, '//') + 1);
+ }
+ // Relative paths generated by dirname($filename) when $filename
+ // has no path (i.e.files in root of the zip archive)
+ $fileName = (string) preg_replace('/^\.\//', '', $fileName);
+ $fileName = File::realpath($fileName);
+
+ // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming
+ // so we need to load case-insensitively from the zip file
+
+ $contents = $archive->getFromName($fileName, 0, ZipArchive::FL_NOCASE);
+
+ // Apache POI fixes
+ if ($contents === false) {
+ $contents = $archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE);
+ }
+
+ // Has the file been saved with Windoze directory separators rather than unix?
+ if ($contents === false) {
+ $contents = $archive->getFromName(str_replace('/', '\\', $fileName), 0, ZipArchive::FL_NOCASE);
+ }
+
+ return ($contents === false) ? '' : $contents;
+ }
+
+ /**
+ * Loads Spreadsheet from file.
+ */
+ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
+ {
+ File::assertFile($filename, self::INITIAL_FILE);
+
+ // Initialisations
+ $excel = new Spreadsheet();
+ $excel->removeSheetByIndex(0);
+ $addingFirstCellStyleXf = true;
+ $addingFirstCellXf = true;
+
+ $unparsedLoadedData = [];
+
+ $this->zip = $zip = new ZipArchive();
+ $zip->open($filename);
+
+ // Read the theme first, because we need the colour scheme when reading the styles
+ [$workbookBasename, $xmlNamespaceBase] = $this->getWorkbookBaseName();
+ $drawingNS = self::REL_TO_DRAWING[$xmlNamespaceBase] ?? Namespaces::DRAWINGML;
+ $chartNS = self::REL_TO_CHART[$xmlNamespaceBase] ?? Namespaces::CHART;
+ $wbRels = $this->loadZip("xl/_rels/{$workbookBasename}.rels", Namespaces::RELATIONSHIPS);
+ $theme = null;
+ $this->styleReader = new Styles();
+ foreach ($wbRels->Relationship as $relx) {
+ $rel = self::getAttributes($relx);
+ $relTarget = (string) $rel['Target'];
+ if (substr($relTarget, 0, 4) === '/xl/') {
+ $relTarget = substr($relTarget, 4);
+ }
+ switch ($rel['Type']) {
+ case "$xmlNamespaceBase/theme":
+ $themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2'];
+ $themeOrderAdditional = count($themeOrderArray);
+
+ $xmlTheme = $this->loadZip("xl/{$relTarget}", $drawingNS);
+ $xmlThemeName = self::getAttributes($xmlTheme);
+ $xmlTheme = $xmlTheme->children($drawingNS);
+ $themeName = (string) $xmlThemeName['name'];
+
+ $colourScheme = self::getAttributes($xmlTheme->themeElements->clrScheme);
+ $colourSchemeName = (string) $colourScheme['name'];
+ $excel->getTheme()->setThemeColorName($colourSchemeName);
+ $colourScheme = $xmlTheme->themeElements->clrScheme->children($drawingNS);
+
+ $themeColours = [];
+ foreach ($colourScheme as $k => $xmlColour) {
+ $themePos = array_search($k, $themeOrderArray);
+ if ($themePos === false) {
+ $themePos = $themeOrderAdditional++;
+ }
+ if (isset($xmlColour->sysClr)) {
+ $xmlColourData = self::getAttributes($xmlColour->sysClr);
+ $themeColours[$themePos] = (string) $xmlColourData['lastClr'];
+ $excel->getTheme()->setThemeColor($k, (string) $xmlColourData['lastClr']);
+ } elseif (isset($xmlColour->srgbClr)) {
+ $xmlColourData = self::getAttributes($xmlColour->srgbClr);
+ $themeColours[$themePos] = (string) $xmlColourData['val'];
+ $excel->getTheme()->setThemeColor($k, (string) $xmlColourData['val']);
+ }
+ }
+ $theme = new Theme($themeName, $colourSchemeName, $themeColours);
+ $this->styleReader->setTheme($theme);
+
+ $fontScheme = self::getAttributes($xmlTheme->themeElements->fontScheme);
+ $fontSchemeName = (string) $fontScheme['name'];
+ $excel->getTheme()->setThemeFontName($fontSchemeName);
+ $majorFonts = [];
+ $minorFonts = [];
+ $fontScheme = $xmlTheme->themeElements->fontScheme->children($drawingNS);
+ $majorLatin = self::getAttributes($fontScheme->majorFont->latin)['typeface'] ?? '';
+ $majorEastAsian = self::getAttributes($fontScheme->majorFont->ea)['typeface'] ?? '';
+ $majorComplexScript = self::getAttributes($fontScheme->majorFont->cs)['typeface'] ?? '';
+ $minorLatin = self::getAttributes($fontScheme->minorFont->latin)['typeface'] ?? '';
+ $minorEastAsian = self::getAttributes($fontScheme->minorFont->ea)['typeface'] ?? '';
+ $minorComplexScript = self::getAttributes($fontScheme->minorFont->cs)['typeface'] ?? '';
+
+ foreach ($fontScheme->majorFont->font as $xmlFont) {
+ $fontAttributes = self::getAttributes($xmlFont);
+ $script = (string) ($fontAttributes['script'] ?? '');
+ if (!empty($script)) {
+ $majorFonts[$script] = (string) ($fontAttributes['typeface'] ?? '');
+ }
+ }
+ foreach ($fontScheme->minorFont->font as $xmlFont) {
+ $fontAttributes = self::getAttributes($xmlFont);
+ $script = (string) ($fontAttributes['script'] ?? '');
+ if (!empty($script)) {
+ $minorFonts[$script] = (string) ($fontAttributes['typeface'] ?? '');
+ }
+ }
+ $excel->getTheme()->setMajorFontValues($majorLatin, $majorEastAsian, $majorComplexScript, $majorFonts);
+ $excel->getTheme()->setMinorFontValues($minorLatin, $minorEastAsian, $minorComplexScript, $minorFonts);
+
+ break;
+ }
+ }
+
+ $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS);
+
+ $propertyReader = new PropertyReader($this->getSecurityScannerOrThrow(), $excel->getProperties());
+ $chartDetails = [];
+ foreach ($rels->Relationship as $relx) {
+ $rel = self::getAttributes($relx);
+ $relTarget = (string) $rel['Target'];
+ // issue 3553
+ if ($relTarget[0] === '/') {
+ $relTarget = substr($relTarget, 1);
+ }
+ $relType = (string) $rel['Type'];
+ $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN;
+ switch ($relType) {
+ case Namespaces::CORE_PROPERTIES:
+ $propertyReader->readCoreProperties($this->getFromZipArchive($zip, $relTarget));
+
+ break;
+ case "$xmlNamespaceBase/extended-properties":
+ $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, $relTarget));
+
+ break;
+ case "$xmlNamespaceBase/custom-properties":
+ $propertyReader->readCustomProperties($this->getFromZipArchive($zip, $relTarget));
+
+ break;
+ //Ribbon
+ case Namespaces::EXTENSIBILITY:
+ $customUI = $relTarget;
+ if ($customUI) {
+ $this->readRibbon($excel, $customUI, $zip);
+ }
+
+ break;
+ case "$xmlNamespaceBase/officeDocument":
+ $dir = dirname($relTarget);
+
+ // Do not specify namespace in next stmt - do it in Xpath
+ $relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', '');
+ $relsWorkbook->registerXPathNamespace('rel', Namespaces::RELATIONSHIPS);
+
+ $worksheets = [];
+ $macros = $customUI = null;
+ foreach ($relsWorkbook->Relationship as $elex) {
+ $ele = self::getAttributes($elex);
+ switch ($ele['Type']) {
+ case Namespaces::WORKSHEET:
+ case Namespaces::PURL_WORKSHEET:
+ $worksheets[(string) $ele['Id']] = $ele['Target'];
+
+ break;
+ case Namespaces::CHARTSHEET:
+ if ($this->includeCharts === true) {
+ $worksheets[(string) $ele['Id']] = $ele['Target'];
+ }
+
+ break;
+ // a vbaProject ? (: some macros)
+ case Namespaces::VBA:
+ $macros = $ele['Target'];
+
+ break;
+ }
+ }
+
+ if ($macros !== null) {
+ $macrosCode = $this->getFromZipArchive($zip, 'xl/vbaProject.bin'); //vbaProject.bin always in 'xl' dir and always named vbaProject.bin
+ if ($macrosCode !== false) {
+ $excel->setMacrosCode($macrosCode);
+ $excel->setHasMacros(true);
+ //short-circuit : not reading vbaProject.bin.rel to get Signature =>allways vbaProjectSignature.bin in 'xl' dir
+ $Certificate = $this->getFromZipArchive($zip, 'xl/vbaProjectSignature.bin');
+ if ($Certificate !== false) {
+ $excel->setMacrosCertificate($Certificate);
+ }
+ }
+ }
+
+ $relType = "rel:Relationship[@Type='"
+ . "$xmlNamespaceBase/styles"
+ . "']";
+ $xpath = self::getArrayItem(self::xpathNoFalse($relsWorkbook, $relType));
+
+ if ($xpath === null) {
+ $xmlStyles = self::testSimpleXml(null);
+ } else {
+ $xmlStyles = $this->loadZip("$dir/$xpath[Target]", $mainNS);
+ }
+
+ $palette = self::extractPalette($xmlStyles);
+ $this->styleReader->setWorkbookPalette($palette);
+ $fills = self::extractStyles($xmlStyles, 'fills', 'fill');
+ $fonts = self::extractStyles($xmlStyles, 'fonts', 'font');
+ $borders = self::extractStyles($xmlStyles, 'borders', 'border');
+ $xfTags = self::extractStyles($xmlStyles, 'cellXfs', 'xf');
+ $cellXfTags = self::extractStyles($xmlStyles, 'cellStyleXfs', 'xf');
+
+ $styles = [];
+ $cellStyles = [];
+ $numFmts = null;
+ if (/*$xmlStyles && */ $xmlStyles->numFmts[0]) {
+ $numFmts = $xmlStyles->numFmts[0];
+ }
+ if (isset($numFmts) && ($numFmts !== null)) {
+ $numFmts->registerXPathNamespace('sml', $mainNS);
+ }
+ $this->styleReader->setNamespace($mainNS);
+ if (!$this->readDataOnly/* && $xmlStyles*/) {
+ foreach ($xfTags as $xfTag) {
+ $xf = self::getAttributes($xfTag);
+ $numFmt = null;
+
+ if ($xf['numFmtId']) {
+ if (isset($numFmts)) {
+ $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]"));
+
+ if (isset($tmpNumFmt['formatCode'])) {
+ $numFmt = (string) $tmpNumFmt['formatCode'];
+ }
+ }
+
+ // We shouldn't override any of the built-in MS Excel values (values below id 164)
+ // But there's a lot of naughty homebrew xlsx writers that do use "reserved" id values that aren't actually used
+ // So we make allowance for them rather than lose formatting masks
+ if (
+ $numFmt === null &&
+ (int) $xf['numFmtId'] < 164 &&
+ NumberFormat::builtInFormatCode((int) $xf['numFmtId']) !== ''
+ ) {
+ $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']);
+ }
+ }
+ $quotePrefix = (bool) (string) ($xf['quotePrefix'] ?? '');
+
+ $style = (object) [
+ 'numFmt' => $numFmt ?? NumberFormat::FORMAT_GENERAL,
+ 'font' => $fonts[(int) ($xf['fontId'])],
+ 'fill' => $fills[(int) ($xf['fillId'])],
+ 'border' => $borders[(int) ($xf['borderId'])],
+ 'alignment' => $xfTag->alignment,
+ 'protection' => $xfTag->protection,
+ 'quotePrefix' => $quotePrefix,
+ ];
+ $styles[] = $style;
+
+ // add style to cellXf collection
+ $objStyle = new Style();
+ $this->styleReader->readStyle($objStyle, $style);
+ if ($addingFirstCellXf) {
+ $excel->removeCellXfByIndex(0); // remove the default style
+ $addingFirstCellXf = false;
+ }
+ $excel->addCellXf($objStyle);
+ }
+
+ foreach ($cellXfTags as $xfTag) {
+ $xf = self::getAttributes($xfTag);
+ $numFmt = NumberFormat::FORMAT_GENERAL;
+ if ($numFmts && $xf['numFmtId']) {
+ $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]"));
+ if (isset($tmpNumFmt['formatCode'])) {
+ $numFmt = (string) $tmpNumFmt['formatCode'];
+ } elseif ((int) $xf['numFmtId'] < 165) {
+ $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']);
+ }
+ }
+
+ $quotePrefix = (bool) (string) ($xf['quotePrefix'] ?? '');
+
+ $cellStyle = (object) [
+ 'numFmt' => $numFmt,
+ 'font' => $fonts[(int) ($xf['fontId'])],
+ 'fill' => $fills[((int) $xf['fillId'])],
+ 'border' => $borders[(int) ($xf['borderId'])],
+ 'alignment' => $xfTag->alignment,
+ 'protection' => $xfTag->protection,
+ 'quotePrefix' => $quotePrefix,
+ ];
+ $cellStyles[] = $cellStyle;
+
+ // add style to cellStyleXf collection
+ $objStyle = new Style();
+ $this->styleReader->readStyle($objStyle, $cellStyle);
+ if ($addingFirstCellStyleXf) {
+ $excel->removeCellStyleXfByIndex(0); // remove the default style
+ $addingFirstCellStyleXf = false;
+ }
+ $excel->addCellStyleXf($objStyle);
+ }
+ }
+ $this->styleReader->setStyleXml($xmlStyles);
+ $this->styleReader->setNamespace($mainNS);
+ $this->styleReader->setStyleBaseData($theme, $styles, $cellStyles);
+ $dxfs = $this->styleReader->dxfs($this->readDataOnly);
+ $styles = $this->styleReader->styles();
+
+ // Read content after setting the styles
+ $sharedStrings = [];
+ $relType = "rel:Relationship[@Type='"
+ //. Namespaces::SHARED_STRINGS
+ . "$xmlNamespaceBase/sharedStrings"
+ . "']";
+ $xpath = self::getArrayItem($relsWorkbook->xpath($relType));
+
+ if ($xpath) {
+ $xmlStrings = $this->loadZip("$dir/$xpath[Target]", $mainNS);
+ if (isset($xmlStrings->si)) {
+ foreach ($xmlStrings->si as $val) {
+ if (isset($val->t)) {
+ $sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t);
+ } elseif (isset($val->r)) {
+ $sharedStrings[] = $this->parseRichText($val);
+ }
+ }
+ }
+ }
+
+ $xmlWorkbook = $this->loadZipNoNamespace($relTarget, $mainNS);
+ $xmlWorkbookNS = $this->loadZip($relTarget, $mainNS);
+
+ // Set base date
+ if ($xmlWorkbookNS->workbookPr) {
+ Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900);
+ $attrs1904 = self::getAttributes($xmlWorkbookNS->workbookPr);
+ if (isset($attrs1904['date1904'])) {
+ if (self::boolean((string) $attrs1904['date1904'])) {
+ Date::setExcelCalendar(Date::CALENDAR_MAC_1904);
+ }
+ }
+ }
+
+ // Set protection
+ $this->readProtection($excel, $xmlWorkbook);
+
+ $sheetId = 0; // keep track of new sheet id in final workbook
+ $oldSheetId = -1; // keep track of old sheet id in final workbook
+ $countSkippedSheets = 0; // keep track of number of skipped sheets
+ $mapSheetId = []; // mapping of sheet ids from old to new
+
+ $charts = $chartDetails = [];
+
+ if ($xmlWorkbookNS->sheets) {
+ /** @var SimpleXMLElement $eleSheet */
+ foreach ($xmlWorkbookNS->sheets->sheet as $eleSheet) {
+ $eleSheetAttr = self::getAttributes($eleSheet);
+ ++$oldSheetId;
+
+ // Check if sheet should be skipped
+ if (is_array($this->loadSheetsOnly) && !in_array((string) $eleSheetAttr['name'], $this->loadSheetsOnly)) {
+ ++$countSkippedSheets;
+ $mapSheetId[$oldSheetId] = null;
+
+ continue;
+ }
+
+ $sheetReferenceId = (string) self::getArrayItem(self::getAttributes($eleSheet, $xmlNamespaceBase), 'id');
+ if (isset($worksheets[$sheetReferenceId]) === false) {
+ ++$countSkippedSheets;
+ $mapSheetId[$oldSheetId] = null;
+
+ continue;
+ }
+ // Map old sheet id in original workbook to new sheet id.
+ // They will differ if loadSheetsOnly() is being used
+ $mapSheetId[$oldSheetId] = $oldSheetId - $countSkippedSheets;
+
+ // Load sheet
+ $docSheet = $excel->createSheet();
+ // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet
+ // references in formula cells... during the load, all formulae should be correct,
+ // and we're simply bringing the worksheet name in line with the formula, not the
+ // reverse
+ $docSheet->setTitle((string) $eleSheetAttr['name'], false, false);
+
+ $fileWorksheet = (string) $worksheets[$sheetReferenceId];
+ $xmlSheet = $this->loadZipNoNamespace("$dir/$fileWorksheet", $mainNS);
+ $xmlSheetNS = $this->loadZip("$dir/$fileWorksheet", $mainNS);
+
+ // Shared Formula table is unique to each Worksheet, so we need to reset it here
+ $this->sharedFormulae = [];
+
+ if (isset($eleSheetAttr['state']) && (string) $eleSheetAttr['state'] != '') {
+ $docSheet->setSheetState((string) $eleSheetAttr['state']);
+ }
+ if ($xmlSheetNS) {
+ $xmlSheetMain = $xmlSheetNS->children($mainNS);
+ // Setting Conditional Styles adjusts selected cells, so we need to execute this
+ // before reading the sheet view data to get the actual selected cells
+ if (!$this->readDataOnly && ($xmlSheet->conditionalFormatting)) {
+ (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load();
+ }
+ if (!$this->readDataOnly && $xmlSheet->extLst) {
+ (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->loadFromExt($this->styleReader);
+ }
+ if (isset($xmlSheetMain->sheetViews, $xmlSheetMain->sheetViews->sheetView)) {
+ $sheetViews = new SheetViews($xmlSheetMain->sheetViews->sheetView, $docSheet);
+ $sheetViews->load();
+ }
+
+ $sheetViewOptions = new SheetViewOptions($docSheet, $xmlSheetNS);
+ $sheetViewOptions->load($this->getReadDataOnly(), $this->styleReader);
+
+ (new ColumnAndRowAttributes($docSheet, $xmlSheetNS))
+ ->load($this->getReadFilter(), $this->getReadDataOnly());
+ }
+
+ if ($xmlSheetNS && $xmlSheetNS->sheetData && $xmlSheetNS->sheetData->row) {
+ $cIndex = 1; // Cell Start from 1
+ foreach ($xmlSheetNS->sheetData->row as $row) {
+ $rowIndex = 1;
+ foreach ($row->c as $c) {
+ $cAttr = self::getAttributes($c);
+ $r = (string) $cAttr['r'];
+ if ($r == '') {
+ $r = Coordinate::stringFromColumnIndex($rowIndex) . $cIndex;
+ }
+ $cellDataType = (string) $cAttr['t'];
+ $value = null;
+ $calculatedValue = null;
+
+ // Read cell?
+ if ($this->getReadFilter() !== null) {
+ $coordinates = Coordinate::coordinateFromString($r);
+
+ if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) {
+ // Normally, just testing for the f attribute should identify this cell as containing a formula
+ // that we need to read, even though it is outside of the filter range, in case it is a shared formula.
+ // But in some cases, this attribute isn't set; so we need to delve a level deeper and look at
+ // whether or not the cell has a child formula element that is shared.
+ if (isset($cAttr->f) || (isset($c->f, $c->f->attributes()['t']) && strtolower((string) $c->f->attributes()['t']) === 'shared')) {
+ $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError', false);
+ }
+ ++$rowIndex;
+
+ continue;
+ }
+ }
+
+ // Read cell!
+ switch ($cellDataType) {
+ case 's':
+ if ((string) $c->v != '') {
+ $value = $sharedStrings[(int) ($c->v)];
+
+ if ($value instanceof RichText) {
+ $value = clone $value;
+ }
+ } else {
+ $value = '';
+ }
+
+ break;
+ case 'b':
+ if (!isset($c->f)) {
+ if (isset($c->v)) {
+ $value = self::castToBoolean($c);
+ } else {
+ $value = null;
+ $cellDataType = DATATYPE::TYPE_NULL;
+ }
+ } else {
+ // Formula
+ $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToBoolean');
+ if (isset($c->f['t'])) {
+ $att = $c->f;
+ $docSheet->getCell($r)->setFormulaAttributes($att);
+ }
+ }
+
+ break;
+ case 'inlineStr':
+ if (isset($c->f)) {
+ $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError');
+ } else {
+ $value = $this->parseRichText($c->is);
+ }
+
+ break;
+ case 'e':
+ if (!isset($c->f)) {
+ $value = self::castToError($c);
+ } else {
+ // Formula
+ $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError');
+ }
+
+ break;
+ default:
+ if (!isset($c->f)) {
+ $value = self::castToString($c);
+ } else {
+ // Formula
+ $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToString');
+ if (isset($c->f['t'])) {
+ $attributes = $c->f['t'];
+ $docSheet->getCell($r)->setFormulaAttributes(['t' => (string) $attributes]);
+ }
+ }
+
+ break;
+ }
+
+ // read empty cells or the cells are not empty
+ if ($this->readEmptyCells || ($value !== null && $value !== '')) {
+ // Rich text?
+ if ($value instanceof RichText && $this->readDataOnly) {
+ $value = $value->getPlainText();
+ }
+
+ $cell = $docSheet->getCell($r);
+ // Assign value
+ if ($cellDataType != '') {
+ // it is possible, that datatype is numeric but with an empty string, which result in an error
+ if ($cellDataType === DataType::TYPE_NUMERIC && ($value === '' || $value === null)) {
+ $cellDataType = DataType::TYPE_NULL;
+ }
+ if ($cellDataType !== DataType::TYPE_NULL) {
+ $cell->setValueExplicit($value, $cellDataType);
+ }
+ } else {
+ $cell->setValue($value);
+ }
+ if ($calculatedValue !== null) {
+ $cell->setCalculatedValue($calculatedValue);
+ }
+
+ // Style information?
+ if ($cAttr['s'] && !$this->readDataOnly) {
+ // no style index means 0, it seems
+ $cell->setXfIndex(isset($styles[(int) ($cAttr['s'])]) ?
+ (int) ($cAttr['s']) : 0);
+ // issue 3495
+ if ($cell->getDataType() === DataType::TYPE_FORMULA) {
+ $cell->getStyle()->setQuotePrefix(false);
+ }
+ }
+ }
+ ++$rowIndex;
+ }
+ ++$cIndex;
+ }
+ }
+ if ($xmlSheetNS && $xmlSheetNS->ignoredErrors) {
+ foreach ($xmlSheetNS->ignoredErrors->ignoredError as $ignoredErrorx) {
+ $ignoredError = self::testSimpleXml($ignoredErrorx);
+ $this->processIgnoredErrors($ignoredError, $docSheet);
+ }
+ }
+
+ if (!$this->readDataOnly && $xmlSheetNS && $xmlSheetNS->sheetProtection) {
+ $protAttr = $xmlSheetNS->sheetProtection->attributes() ?? [];
+ foreach ($protAttr as $key => $value) {
+ $method = 'set' . ucfirst($key);
+ $docSheet->getProtection()->$method(self::boolean((string) $value));
+ }
+ }
+
+ if ($xmlSheet) {
+ $this->readSheetProtection($docSheet, $xmlSheet);
+ }
+
+ if ($this->readDataOnly === false) {
+ $this->readAutoFilter($xmlSheet, $docSheet);
+ $this->readTables($xmlSheet, $docSheet, $dir, $fileWorksheet, $zip);
+ }
+
+ if ($xmlSheetNS && $xmlSheetNS->mergeCells && $xmlSheetNS->mergeCells->mergeCell && !$this->readDataOnly) {
+ foreach ($xmlSheetNS->mergeCells->mergeCell as $mergeCellx) {
+ /** @scrutinizer ignore-call */
+ $mergeCell = $mergeCellx->attributes();
+ $mergeRef = (string) ($mergeCell['ref'] ?? '');
+ if (strpos($mergeRef, ':') !== false) {
+ $docSheet->mergeCells($mergeRef, Worksheet::MERGE_CELL_CONTENT_HIDE);
+ }
+ }
+ }
+
+ if ($xmlSheet && !$this->readDataOnly) {
+ $unparsedLoadedData = (new PageSetup($docSheet, $xmlSheet))->load($unparsedLoadedData);
+ }
+
+ if ($xmlSheet !== false && isset($xmlSheet->extLst, $xmlSheet->extLst->ext, $xmlSheet->extLst->ext['uri']) && ($xmlSheet->extLst->ext['uri'] == '{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}')) {
+ // Create dataValidations node if does not exists, maybe is better inside the foreach ?
+ if (!$xmlSheet->dataValidations) {
+ $xmlSheet->addChild('dataValidations');
+ }
+
+ foreach ($xmlSheet->extLst->ext->children(Namespaces::DATA_VALIDATIONS1)->dataValidations->dataValidation as $item) {
+ $item = self::testSimpleXml($item);
+ $node = self::testSimpleXml($xmlSheet->dataValidations)->addChild('dataValidation');
+ foreach ($item->attributes() ?? [] as $attr) {
+ $node->addAttribute($attr->getName(), $attr);
+ }
+ $node->addAttribute('sqref', $item->children(Namespaces::DATA_VALIDATIONS2)->sqref);
+ if (isset($item->formula1)) {
+ $childNode = $node->addChild('formula1');
+ if ($childNode !== null) { // null should never happen
+ $childNode[0] = (string) $item->formula1->children(Namespaces::DATA_VALIDATIONS2)->f; // @phpstan-ignore-line
+ }
+ }
+ }
+ }
+
+ if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) {
+ (new DataValidations($docSheet, $xmlSheet))->load();
+ }
+
+ // unparsed sheet AlternateContent
+ if ($xmlSheet && !$this->readDataOnly) {
+ $mc = $xmlSheet->children(Namespaces::COMPATIBILITY);
+ if ($mc->AlternateContent) {
+ foreach ($mc->AlternateContent as $alternateContent) {
+ $alternateContent = self::testSimpleXml($alternateContent);
+ $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML();
+ }
+ }
+ }
+
+ // Add hyperlinks
+ if (!$this->readDataOnly) {
+ $hyperlinkReader = new Hyperlinks($docSheet);
+ // Locate hyperlink relations
+ $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
+ if ($zip->locateName($relationsFileName)) {
+ $relsWorksheet = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS);
+ $hyperlinkReader->readHyperlinks($relsWorksheet);
+ }
+
+ // Loop through hyperlinks
+ if ($xmlSheetNS && $xmlSheetNS->children($mainNS)->hyperlinks) {
+ $hyperlinkReader->setHyperlinks($xmlSheetNS->children($mainNS)->hyperlinks);
+ }
+ }
+
+ // Add comments
+ $comments = [];
+ $vmlComments = [];
+ if (!$this->readDataOnly) {
+ // Locate comment relations
+ $commentRelations = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
+ if ($zip->locateName($commentRelations)) {
+ $relsWorksheet = $this->loadZip($commentRelations, Namespaces::RELATIONSHIPS);
+ foreach ($relsWorksheet->Relationship as $elex) {
+ $ele = self::getAttributes($elex);
+ if ($ele['Type'] == Namespaces::COMMENTS) {
+ $comments[(string) $ele['Id']] = (string) $ele['Target'];
+ }
+ if ($ele['Type'] == Namespaces::VML) {
+ $vmlComments[(string) $ele['Id']] = (string) $ele['Target'];
+ }
+ }
+ }
+
+ // Loop through comments
+ foreach ($comments as $relName => $relPath) {
+ // Load comments file
+ $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath);
+ // okay to ignore namespace - using xpath
+ $commentsFile = $this->loadZip($relPath, '');
+
+ // Utility variables
+ $authors = [];
+ $commentsFile->registerXpathNamespace('com', $mainNS);
+ $authorPath = self::xpathNoFalse($commentsFile, 'com:authors/com:author');
+ foreach ($authorPath as $author) {
+ $authors[] = (string) $author;
+ }
+
+ // Loop through contents
+ $contentPath = self::xpathNoFalse($commentsFile, 'com:commentList/com:comment');
+ foreach ($contentPath as $comment) {
+ $commentx = $comment->attributes();
+ $commentModel = $docSheet->getComment((string) $commentx['ref']);
+ if (isset($commentx['authorId'])) {
+ $commentModel->setAuthor($authors[(int) $commentx['authorId']]);
+ }
+ $commentModel->setText($this->parseRichText($comment->children($mainNS)->text));
+ }
+ }
+
+ // later we will remove from it real vmlComments
+ $unparsedVmlDrawings = $vmlComments;
+ $vmlDrawingContents = [];
+
+ // Loop through VML comments
+ foreach ($vmlComments as $relName => $relPath) {
+ // Load VML comments file
+ $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath);
+
+ try {
+ // no namespace okay - processed with Xpath
+ $vmlCommentsFile = $this->loadZip($relPath, '', true);
+ $vmlCommentsFile->registerXPathNamespace('v', Namespaces::URN_VML);
+ } catch (Throwable $ex) {
+ //Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData
+ continue;
+ }
+
+ // Locate VML drawings image relations
+ $drowingImages = [];
+ $VMLDrawingsRelations = dirname($relPath) . '/_rels/' . basename($relPath) . '.rels';
+ $vmlDrawingContents[$relName] = $this->getSecurityScannerOrThrow()->scan($this->getFromZipArchive($zip, $relPath));
+ if ($zip->locateName($VMLDrawingsRelations)) {
+ $relsVMLDrawing = $this->loadZip($VMLDrawingsRelations, Namespaces::RELATIONSHIPS);
+ foreach ($relsVMLDrawing->Relationship as $elex) {
+ $ele = self::getAttributes($elex);
+ if ($ele['Type'] == Namespaces::IMAGE) {
+ $drowingImages[(string) $ele['Id']] = (string) $ele['Target'];
+ }
+ }
+ }
+
+ $shapes = self::xpathNoFalse($vmlCommentsFile, '//v:shape');
+ foreach ($shapes as $shape) {
+ $shape->registerXPathNamespace('v', Namespaces::URN_VML);
+
+ if (isset($shape['style'])) {
+ $style = (string) $shape['style'];
+ $fillColor = strtoupper(substr((string) $shape['fillcolor'], 1));
+ $column = null;
+ $row = null;
+ $fillImageRelId = null;
+ $fillImageTitle = '';
+
+ $clientData = $shape->xpath('.//x:ClientData');
+ if (is_array($clientData) && !empty($clientData)) {
+ $clientData = $clientData[0];
+
+ if (isset($clientData['ObjectType']) && (string) $clientData['ObjectType'] == 'Note') {
+ $temp = $clientData->xpath('.//x:Row');
+ if (is_array($temp)) {
+ $row = $temp[0];
+ }
+
+ $temp = $clientData->xpath('.//x:Column');
+ if (is_array($temp)) {
+ $column = $temp[0];
+ }
+ }
+ }
+
+ $fillImageRelNode = $shape->xpath('.//v:fill/@o:relid');
+ if (is_array($fillImageRelNode) && !empty($fillImageRelNode)) {
+ $fillImageRelNode = $fillImageRelNode[0];
+
+ if (isset($fillImageRelNode['relid'])) {
+ $fillImageRelId = (string) $fillImageRelNode['relid'];
+ }
+ }
+
+ $fillImageTitleNode = $shape->xpath('.//v:fill/@o:title');
+ if (is_array($fillImageTitleNode) && !empty($fillImageTitleNode)) {
+ $fillImageTitleNode = $fillImageTitleNode[0];
+
+ if (isset($fillImageTitleNode['title'])) {
+ $fillImageTitle = (string) $fillImageTitleNode['title'];
+ }
+ }
+
+ if (($column !== null) && ($row !== null)) {
+ // Set comment properties
+ $comment = $docSheet->getComment([$column + 1, $row + 1]);
+ $comment->getFillColor()->setRGB($fillColor);
+ if (isset($drowingImages[$fillImageRelId])) {
+ $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
+ $objDrawing->setName($fillImageTitle);
+ $imagePath = str_replace('../', 'xl/', $drowingImages[$fillImageRelId]);
+ $objDrawing->setPath(
+ 'zip://' . File::realpath($filename) . '#' . $imagePath,
+ true,
+ $zip
+ );
+ $comment->setBackgroundImage($objDrawing);
+ }
+
+ // Parse style
+ $styleArray = explode(';', str_replace(' ', '', $style));
+ foreach ($styleArray as $stylePair) {
+ $stylePair = explode(':', $stylePair);
+
+ if ($stylePair[0] == 'margin-left') {
+ $comment->setMarginLeft($stylePair[1]);
+ }
+ if ($stylePair[0] == 'margin-top') {
+ $comment->setMarginTop($stylePair[1]);
+ }
+ if ($stylePair[0] == 'width') {
+ $comment->setWidth($stylePair[1]);
+ }
+ if ($stylePair[0] == 'height') {
+ $comment->setHeight($stylePair[1]);
+ }
+ if ($stylePair[0] == 'visibility') {
+ $comment->setVisible($stylePair[1] == 'visible');
+ }
+ }
+
+ unset($unparsedVmlDrawings[$relName]);
+ }
+ }
+ }
+ }
+
+ // unparsed vmlDrawing
+ if ($unparsedVmlDrawings) {
+ foreach ($unparsedVmlDrawings as $rId => $relPath) {
+ $rId = substr($rId, 3); // rIdXXX
+ $unparsedVmlDrawing = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['vmlDrawings'];
+ $unparsedVmlDrawing[$rId] = [];
+ $unparsedVmlDrawing[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $relPath);
+ $unparsedVmlDrawing[$rId]['relFilePath'] = $relPath;
+ $unparsedVmlDrawing[$rId]['content'] = $this->getSecurityScannerOrThrow()->scan($this->getFromZipArchive($zip, $unparsedVmlDrawing[$rId]['filePath']));
+ unset($unparsedVmlDrawing);
+ }
+ }
+
+ // Header/footer images
+ if ($xmlSheetNS && $xmlSheetNS->legacyDrawingHF) {
+ $vmlHfRid = '';
+ $vmlHfRidAttr = $xmlSheetNS->legacyDrawingHF->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT);
+ if ($vmlHfRidAttr !== null && isset($vmlHfRidAttr['id'])) {
+ $vmlHfRid = (string) $vmlHfRidAttr['id'][0];
+ }
+ if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
+ $relsWorksheet = $this->loadZipNoNamespace(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels', Namespaces::RELATIONSHIPS);
+ $vmlRelationship = '';
+
+ foreach ($relsWorksheet->Relationship as $ele) {
+ if ((string) $ele['Type'] == Namespaces::VML && (string) $ele['Id'] === $vmlHfRid) {
+ $vmlRelationship = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
+
+ break;
+ }
+ }
+
+ if ($vmlRelationship != '') {
+ // Fetch linked images
+ $relsVML = $this->loadZipNoNamespace(dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels', Namespaces::RELATIONSHIPS);
+ $drawings = [];
+ if (isset($relsVML->Relationship)) {
+ foreach ($relsVML->Relationship as $ele) {
+ if ($ele['Type'] == Namespaces::IMAGE) {
+ $drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']);
+ }
+ }
+ }
+ // Fetch VML document
+ $vmlDrawing = $this->loadZipNoNamespace($vmlRelationship, '');
+ $vmlDrawing->registerXPathNamespace('v', Namespaces::URN_VML);
+
+ $hfImages = [];
+
+ $shapes = self::xpathNoFalse($vmlDrawing, '//v:shape');
+ foreach ($shapes as $idx => $shape) {
+ $shape->registerXPathNamespace('v', Namespaces::URN_VML);
+ $imageData = $shape->xpath('//v:imagedata');
+
+ if (empty($imageData)) {
+ continue;
+ }
+
+ $imageData = $imageData[$idx];
+
+ $imageData = self::getAttributes($imageData, Namespaces::URN_MSOFFICE);
+ $style = self::toCSSArray((string) $shape['style']);
+
+ if (array_key_exists((string) $imageData['relid'], $drawings)) {
+ $shapeId = (string) $shape['id'];
+ $hfImages[$shapeId] = new HeaderFooterDrawing();
+ if (isset($imageData['title'])) {
+ $hfImages[$shapeId]->setName((string) $imageData['title']);
+ }
+
+ $hfImages[$shapeId]->setPath('zip://' . File::realpath($filename) . '#' . $drawings[(string) $imageData['relid']], false, $zip);
+ $hfImages[$shapeId]->setResizeProportional(false);
+ $hfImages[$shapeId]->setWidth($style['width']);
+ $hfImages[$shapeId]->setHeight($style['height']);
+ if (isset($style['margin-left'])) {
+ $hfImages[$shapeId]->setOffsetX($style['margin-left']);
+ }
+ $hfImages[$shapeId]->setOffsetY($style['margin-top']);
+ $hfImages[$shapeId]->setResizeProportional(true);
+ }
+ }
+
+ $docSheet->getHeaderFooter()->setImages($hfImages);
+ }
+ }
+ }
+ }
+
+ // TODO: Autoshapes from twoCellAnchors!
+ $drawingFilename = dirname("$dir/$fileWorksheet")
+ . '/_rels/'
+ . basename($fileWorksheet)
+ . '.rels';
+ if (substr($drawingFilename, 0, 7) === 'xl//xl/') {
+ $drawingFilename = substr($drawingFilename, 4);
+ }
+ if (substr($drawingFilename, 0, 8) === '/xl//xl/') {
+ $drawingFilename = substr($drawingFilename, 5);
+ }
+ if ($zip->locateName($drawingFilename)) {
+ $relsWorksheet = $this->loadZipNoNamespace($drawingFilename, Namespaces::RELATIONSHIPS);
+ $drawings = [];
+ foreach ($relsWorksheet->Relationship as $ele) {
+ if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") {
+ $eleTarget = (string) $ele['Target'];
+ if (substr($eleTarget, 0, 4) === '/xl/') {
+ $drawings[(string) $ele['Id']] = substr($eleTarget, 1);
+ } else {
+ $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']);
+ }
+ }
+ }
+
+ if ($xmlSheetNS->drawing && !$this->readDataOnly) {
+ $unparsedDrawings = [];
+ $fileDrawing = null;
+ foreach ($xmlSheetNS->drawing as $drawing) {
+ $drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id');
+ $fileDrawing = $drawings[$drawingRelId];
+ $drawingFilename = dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels';
+ $relsDrawing = $this->loadZipNoNamespace($drawingFilename, $xmlNamespaceBase);
+
+ $images = [];
+ $hyperlinks = [];
+ if ($relsDrawing && $relsDrawing->Relationship) {
+ foreach ($relsDrawing->Relationship as $ele) {
+ $eleType = (string) $ele['Type'];
+ if ($eleType === Namespaces::HYPERLINK) {
+ $hyperlinks[(string) $ele['Id']] = (string) $ele['Target'];
+ }
+ if ($eleType === "$xmlNamespaceBase/image") {
+ $eleTarget = (string) $ele['Target'];
+ if (substr($eleTarget, 0, 4) === '/xl/') {
+ $eleTarget = substr($eleTarget, 1);
+ $images[(string) $ele['Id']] = $eleTarget;
+ } else {
+ $images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $eleTarget);
+ }
+ } elseif ($eleType === "$xmlNamespaceBase/chart") {
+ if ($this->includeCharts) {
+ $eleTarget = (string) $ele['Target'];
+ if (substr($eleTarget, 0, 4) === '/xl/') {
+ $index = substr($eleTarget, 1);
+ } else {
+ $index = self::dirAdd($fileDrawing, $eleTarget);
+ }
+ $charts[$index] = [
+ 'id' => (string) $ele['Id'],
+ 'sheet' => $docSheet->getTitle(),
+ ];
+ }
+ }
+ }
+ }
+
+ $xmlDrawing = $this->loadZipNoNamespace($fileDrawing, '');
+ $xmlDrawingChildren = $xmlDrawing->children(Namespaces::SPREADSHEET_DRAWING);
+
+ if ($xmlDrawingChildren->oneCellAnchor) {
+ foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) {
+ $oneCellAnchor = self::testSimpleXml($oneCellAnchor);
+ if ($oneCellAnchor->pic->blipFill) {
+ /** @var SimpleXMLElement $blip */
+ $blip = $oneCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip;
+ /** @var SimpleXMLElement $xfrm */
+ $xfrm = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm;
+ /** @var SimpleXMLElement $outerShdw */
+ $outerShdw = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw;
+
+ $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
+ $objDrawing->setName((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'name'));
+ $objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'descr'));
+ $embedImageKey = (string) self::getArrayItem(
+ self::getAttributes($blip, $xmlNamespaceBase),
+ 'embed'
+ );
+ if (isset($images[$embedImageKey])) {
+ $objDrawing->setPath(
+ 'zip://' . File::realpath($filename) . '#' .
+ $images[$embedImageKey],
+ false,
+ $zip
+ );
+ } else {
+ $linkImageKey = (string) self::getArrayItem(
+ $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'),
+ 'link'
+ );
+ if (isset($images[$linkImageKey])) {
+ $url = str_replace('xl/drawings/', '', $images[$linkImageKey]);
+ $objDrawing->setPath($url, false);
+ }
+ if ($objDrawing->getPath() === '') {
+ continue;
+ }
+ }
+ $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1));
+
+ $objDrawing->setOffsetX((int) Drawing::EMUToPixels($oneCellAnchor->from->colOff));
+ $objDrawing->setOffsetY(Drawing::EMUToPixels($oneCellAnchor->from->rowOff));
+ $objDrawing->setResizeProportional(false);
+ $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx')));
+ $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cy')));
+ if ($xfrm) {
+ $objDrawing->setRotation((int) Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot')));
+ }
+ if ($outerShdw) {
+ $shadow = $objDrawing->getShadow();
+ $shadow->setVisible(true);
+ $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad')));
+ $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist')));
+ $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir')));
+ $shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn'));
+ $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr;
+ $shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val'));
+ $shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000);
+ }
+
+ $this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks);
+
+ $objDrawing->setWorksheet($docSheet);
+ } elseif ($this->includeCharts && $oneCellAnchor->graphicFrame) {
+ // Exported XLSX from Google Sheets positions charts with a oneCellAnchor
+ $coordinates = Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1);
+ $offsetX = Drawing::EMUToPixels($oneCellAnchor->from->colOff);
+ $offsetY = Drawing::EMUToPixels($oneCellAnchor->from->rowOff);
+ $width = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx'));
+ $height = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cy'));
+
+ $graphic = $oneCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic;
+ /** @var SimpleXMLElement $chartRef */
+ $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart;
+ $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase);
+
+ $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [
+ 'fromCoordinate' => $coordinates,
+ 'fromOffsetX' => $offsetX,
+ 'fromOffsetY' => $offsetY,
+ 'width' => $width,
+ 'height' => $height,
+ 'worksheetTitle' => $docSheet->getTitle(),
+ 'oneCellAnchor' => true,
+ ];
+ }
+ }
+ }
+ if ($xmlDrawingChildren->twoCellAnchor) {
+ foreach ($xmlDrawingChildren->twoCellAnchor as $twoCellAnchor) {
+ $twoCellAnchor = self::testSimpleXml($twoCellAnchor);
+ if ($twoCellAnchor->pic->blipFill) {
+ $blip = $twoCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip;
+ $xfrm = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm;
+ $outerShdw = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw;
+ $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
+ /** @scrutinizer ignore-call */
+ $editAs = $twoCellAnchor->attributes();
+ if (isset($editAs, $editAs['editAs'])) {
+ $objDrawing->setEditAs($editAs['editAs']);
+ }
+ $objDrawing->setName((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'name'));
+ $objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'descr'));
+ $embedImageKey = (string) self::getArrayItem(
+ self::getAttributes($blip, $xmlNamespaceBase),
+ 'embed'
+ );
+ if (isset($images[$embedImageKey])) {
+ $objDrawing->setPath(
+ 'zip://' . File::realpath($filename) . '#' .
+ $images[$embedImageKey],
+ false,
+ $zip
+ );
+ } else {
+ $linkImageKey = (string) self::getArrayItem(
+ $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'),
+ 'link'
+ );
+ if (isset($images[$linkImageKey])) {
+ $url = str_replace('xl/drawings/', '', $images[$linkImageKey]);
+ $objDrawing->setPath($url, false);
+ }
+ if ($objDrawing->getPath() === '') {
+ continue;
+ }
+ }
+ $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1));
+
+ $objDrawing->setOffsetX(Drawing::EMUToPixels($twoCellAnchor->from->colOff));
+ $objDrawing->setOffsetY(Drawing::EMUToPixels($twoCellAnchor->from->rowOff));
+
+ $objDrawing->setCoordinates2(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1));
+
+ $objDrawing->setOffsetX2(Drawing::EMUToPixels($twoCellAnchor->to->colOff));
+ $objDrawing->setOffsetY2(Drawing::EMUToPixels($twoCellAnchor->to->rowOff));
+
+ $objDrawing->setResizeProportional(false);
+
+ if ($xfrm) {
+ $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cx')));
+ $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cy')));
+ $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot')));
+ }
+ if ($outerShdw) {
+ $shadow = $objDrawing->getShadow();
+ $shadow->setVisible(true);
+ $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad')));
+ $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist')));
+ $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir')));
+ $shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn'));
+ $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr;
+ $shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val'));
+ $shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000);
+ }
+
+ $this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks);
+
+ $objDrawing->setWorksheet($docSheet);
+ } elseif (($this->includeCharts) && ($twoCellAnchor->graphicFrame)) {
+ $fromCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1);
+ $fromOffsetX = Drawing::EMUToPixels($twoCellAnchor->from->colOff);
+ $fromOffsetY = Drawing::EMUToPixels($twoCellAnchor->from->rowOff);
+ $toCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1);
+ $toOffsetX = Drawing::EMUToPixels($twoCellAnchor->to->colOff);
+ $toOffsetY = Drawing::EMUToPixels($twoCellAnchor->to->rowOff);
+ $graphic = $twoCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic;
+ /** @var SimpleXMLElement $chartRef */
+ $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart;
+ $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase);
+
+ $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [
+ 'fromCoordinate' => $fromCoordinate,
+ 'fromOffsetX' => $fromOffsetX,
+ 'fromOffsetY' => $fromOffsetY,
+ 'toCoordinate' => $toCoordinate,
+ 'toOffsetX' => $toOffsetX,
+ 'toOffsetY' => $toOffsetY,
+ 'worksheetTitle' => $docSheet->getTitle(),
+ ];
+ }
+ }
+ }
+ if ($xmlDrawingChildren->absoluteAnchor) {
+ foreach ($xmlDrawingChildren->absoluteAnchor as $absoluteAnchor) {
+ if (($this->includeCharts) && ($absoluteAnchor->graphicFrame)) {
+ $graphic = $absoluteAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic;
+ /** @var SimpleXMLElement $chartRef */
+ $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart;
+ $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase);
+ $width = Drawing::EMUToPixels((int) self::getArrayItem(self::getAttributes($absoluteAnchor->ext), 'cx')[0]);
+ $height = Drawing::EMUToPixels((int) self::getArrayItem(self::getAttributes($absoluteAnchor->ext), 'cy')[0]);
+
+ $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [
+ 'fromCoordinate' => 'A1',
+ 'fromOffsetX' => 0,
+ 'fromOffsetY' => 0,
+ 'width' => $width,
+ 'height' => $height,
+ 'worksheetTitle' => $docSheet->getTitle(),
+ ];
+ }
+ }
+ }
+ if (empty($relsDrawing) && $xmlDrawing->count() == 0) {
+ // Save Drawing without rels and children as unparsed
+ $unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML();
+ }
+ }
+
+ // store original rId of drawing files
+ $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = [];
+ foreach ($relsWorksheet->Relationship as $ele) {
+ if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") {
+ $drawingRelId = (string) $ele['Id'];
+ $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = $drawingRelId;
+ if (isset($unparsedDrawings[$drawingRelId])) {
+ $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['Drawings'][$drawingRelId] = $unparsedDrawings[$drawingRelId];
+ }
+ }
+ }
+ if ($xmlSheet->legacyDrawing && !$this->readDataOnly) {
+ foreach ($xmlSheet->legacyDrawing as $drawing) {
+ $drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id');
+ if (isset($vmlDrawingContents[$drawingRelId])) {
+ $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['legacyDrawing'] = $vmlDrawingContents[$drawingRelId];
+ }
+ }
+ }
+
+ // unparsed drawing AlternateContent
+ $xmlAltDrawing = $this->loadZip((string) $fileDrawing, Namespaces::COMPATIBILITY);
+
+ if ($xmlAltDrawing->AlternateContent) {
+ foreach ($xmlAltDrawing->AlternateContent as $alternateContent) {
+ $alternateContent = self::testSimpleXml($alternateContent);
+ $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingAlternateContents'][] = $alternateContent->asXML();
+ }
+ }
+ }
+ }
+
+ $this->readFormControlProperties($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);
+ $this->readPrinterSettings($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData);
+
+ // Loop through definedNames
+ if ($xmlWorkbook->definedNames) {
+ foreach ($xmlWorkbook->definedNames->definedName as $definedName) {
+ // Extract range
+ $extractedRange = (string) $definedName;
+ if (($spos = strpos($extractedRange, '!')) !== false) {
+ $extractedRange = substr($extractedRange, 0, $spos) . str_replace('$', '', substr($extractedRange, $spos));
+ } else {
+ $extractedRange = str_replace('$', '', $extractedRange);
+ }
+
+ // Valid range?
+ if ($extractedRange == '') {
+ continue;
+ }
+
+ // Some definedNames are only applicable if we are on the same sheet...
+ if ((string) $definedName['localSheetId'] != '' && (string) $definedName['localSheetId'] == $oldSheetId) {
+ // Switch on type
+ switch ((string) $definedName['name']) {
+ case '_xlnm._FilterDatabase':
+ if ((string) $definedName['hidden'] !== '1') {
+ $extractedRange = explode(',', $extractedRange);
+ foreach ($extractedRange as $range) {
+ $autoFilterRange = $range;
+ if (strpos($autoFilterRange, ':') !== false) {
+ $docSheet->getAutoFilter()->setRange($autoFilterRange);
+ }
+ }
+ }
+
+ break;
+ case '_xlnm.Print_Titles':
+ // Split $extractedRange
+ $extractedRange = explode(',', $extractedRange);
+
+ // Set print titles
+ foreach ($extractedRange as $range) {
+ $matches = [];
+ $range = str_replace('$', '', $range);
+
+ // check for repeating columns, e g. 'A:A' or 'A:D'
+ if (preg_match('/!?([A-Z]+)\:([A-Z]+)$/', $range, $matches)) {
+ $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$matches[1], $matches[2]]);
+ } elseif (preg_match('/!?(\d+)\:(\d+)$/', $range, $matches)) {
+ // check for repeating rows, e.g. '1:1' or '1:5'
+ $docSheet->getPageSetup()->setRowsToRepeatAtTop([$matches[1], $matches[2]]);
+ }
+ }
+
+ break;
+ case '_xlnm.Print_Area':
+ $rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) ?: [];
+ $newRangeSets = [];
+ foreach ($rangeSets as $rangeSet) {
+ [, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true);
+ if (empty($rangeSet)) {
+ continue;
+ }
+ if (strpos($rangeSet, ':') === false) {
+ $rangeSet = $rangeSet . ':' . $rangeSet;
+ }
+ $newRangeSets[] = str_replace('$', '', $rangeSet);
+ }
+ if (count($newRangeSets) > 0) {
+ $docSheet->getPageSetup()->setPrintArea(implode(',', $newRangeSets));
+ }
+
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ // Next sheet id
+ ++$sheetId;
+ }
+
+ // Loop through definedNames
+ if ($xmlWorkbook->definedNames) {
+ foreach ($xmlWorkbook->definedNames->definedName as $definedName) {
+ // Extract range
+ $extractedRange = (string) $definedName;
+
+ // Valid range?
+ if ($extractedRange == '') {
+ continue;
+ }
+
+ // Some definedNames are only applicable if we are on the same sheet...
+ if ((string) $definedName['localSheetId'] != '') {
+ // Local defined name
+ // Switch on type
+ switch ((string) $definedName['name']) {
+ case '_xlnm._FilterDatabase':
+ case '_xlnm.Print_Titles':
+ case '_xlnm.Print_Area':
+ break;
+ default:
+ if ($mapSheetId[(int) $definedName['localSheetId']] !== null) {
+ $range = Worksheet::extractSheetTitle((string) $definedName, true);
+ $scope = $excel->getSheet($mapSheetId[(int) $definedName['localSheetId']]);
+ if (strpos((string) $definedName, '!') !== false) {
+ $range[0] = str_replace("''", "'", $range[0]);
+ $range[0] = str_replace("'", '', $range[0]);
+ if ($worksheet = $excel->getSheetByName($range[0])) { // @phpstan-ignore-line
+ $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $worksheet, $extractedRange, true, $scope));
+ } else {
+ $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true, $scope));
+ }
+ } else {
+ $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true));
+ }
+ }
+
+ break;
+ }
+ } elseif (!isset($definedName['localSheetId'])) {
+ $definedRange = (string) $definedName;
+ // "Global" definedNames
+ $locatedSheet = null;
+ if (strpos((string) $definedName, '!') !== false) {
+ // Modify range, and extract the first worksheet reference
+ // Need to split on a comma or a space if not in quotes, and extract the first part.
+ $definedNameValueParts = preg_split("/[ ,](?=([^']*'[^']*')*[^']*$)/miuU", $definedRange);
+ // Extract sheet name
+ [$extractedSheetName] = Worksheet::extractSheetTitle((string) $definedNameValueParts[0], true); // @phpstan-ignore-line
+ $extractedSheetName = trim($extractedSheetName, "'");
+
+ // Locate sheet
+ $locatedSheet = $excel->getSheetByName($extractedSheetName);
+ }
+
+ if ($locatedSheet === null && !DefinedName::testIfFormula($definedRange)) {
+ $definedRange = '#REF!';
+ }
+ $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $locatedSheet, $definedRange, false));
+ }
+ }
+ }
+ }
+
+ (new WorkbookView($excel))->viewSettings($xmlWorkbook, $mainNS, $mapSheetId, $this->readDataOnly);
+
+ break;
+ }
+ }
+
+ if (!$this->readDataOnly) {
+ $contentTypes = $this->loadZip('[Content_Types].xml');
+
+ // Default content types
+ foreach ($contentTypes->Default as $contentType) {
+ switch ($contentType['ContentType']) {
+ case 'application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings':
+ $unparsedLoadedData['default_content_types'][(string) $contentType['Extension']] = (string) $contentType['ContentType'];
+
+ break;
+ }
+ }
+
+ // Override content types
+ foreach ($contentTypes->Override as $contentType) {
+ switch ($contentType['ContentType']) {
+ case 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml':
+ if ($this->includeCharts) {
+ $chartEntryRef = ltrim((string) $contentType['PartName'], '/');
+ $chartElements = $this->loadZip($chartEntryRef);
+ $chartReader = new Chart($chartNS, $drawingNS);
+ $objChart = $chartReader->readChart($chartElements, basename($chartEntryRef, '.xml'));
+ if (isset($charts[$chartEntryRef])) {
+ $chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id'];
+ if (isset($chartDetails[$chartPositionRef])) {
+ $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart); // @phpstan-ignore-line
+ $objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet']));
+ // For oneCellAnchor or absoluteAnchor positioned charts,
+ // toCoordinate is not in the data. Does it need to be calculated?
+ if (array_key_exists('toCoordinate', $chartDetails[$chartPositionRef])) {
+ // twoCellAnchor
+ $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']);
+ $objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']);
+ } else {
+ // oneCellAnchor or absoluteAnchor (e.g. Chart sheet)
+ $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']);
+ $objChart->setBottomRightPosition('', $chartDetails[$chartPositionRef]['width'], $chartDetails[$chartPositionRef]['height']);
+ if (array_key_exists('oneCellAnchor', $chartDetails[$chartPositionRef])) {
+ $objChart->setOneCellAnchor($chartDetails[$chartPositionRef]['oneCellAnchor']);
+ }
+ }
+ }
+ }
+ }
+
+ break;
+
+ // unparsed
+ case 'application/vnd.ms-excel.controlproperties+xml':
+ $unparsedLoadedData['override_content_types'][(string) $contentType['PartName']] = (string) $contentType['ContentType'];
+
+ break;
+ }
+ }
+ }
+
+ $excel->setUnparsedLoadedData($unparsedLoadedData);
+
+ $zip->close();
+
+ return $excel;
+ }
+
+ /**
+ * @return RichText
+ */
+ private function parseRichText(?SimpleXMLElement $is)
+ {
+ $value = new RichText();
+
+ if (isset($is->t)) {
+ $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $is->t));
+ } elseif ($is !== null) {
+ if (is_object($is->r)) {
+ /** @var SimpleXMLElement $run */
+ foreach ($is->r as $run) {
+ if (!isset($run->rPr)) {
+ $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $run->t));
+ } else {
+ $objText = $value->createTextRun(StringHelper::controlCharacterOOXML2PHP((string) $run->t));
+ $objFont = $objText->getFont() ?? new StyleFont();
+
+ if (isset($run->rPr->rFont)) {
+ $attr = $run->rPr->rFont->attributes();
+ if (isset($attr['val'])) {
+ $objFont->setName((string) $attr['val']);
+ }
+ }
+ if (isset($run->rPr->sz)) {
+ $attr = $run->rPr->sz->attributes();
+ if (isset($attr['val'])) {
+ $objFont->setSize((float) $attr['val']);
+ }
+ }
+ if (isset($run->rPr->color)) {
+ $objFont->setColor(new Color($this->styleReader->readColor($run->rPr->color)));
+ }
+ if (isset($run->rPr->b)) {
+ $attr = $run->rPr->b->attributes();
+ if (
+ (isset($attr['val']) && self::boolean((string) $attr['val'])) ||
+ (!isset($attr['val']))
+ ) {
+ $objFont->setBold(true);
+ }
+ }
+ if (isset($run->rPr->i)) {
+ $attr = $run->rPr->i->attributes();
+ if (
+ (isset($attr['val']) && self::boolean((string) $attr['val'])) ||
+ (!isset($attr['val']))
+ ) {
+ $objFont->setItalic(true);
+ }
+ }
+ if (isset($run->rPr->vertAlign)) {
+ $attr = $run->rPr->vertAlign->attributes();
+ if (isset($attr['val'])) {
+ $vertAlign = strtolower((string) $attr['val']);
+ if ($vertAlign == 'superscript') {
+ $objFont->setSuperscript(true);
+ }
+ if ($vertAlign == 'subscript') {
+ $objFont->setSubscript(true);
+ }
+ }
+ }
+ if (isset($run->rPr->u)) {
+ $attr = $run->rPr->u->attributes();
+ if (!isset($attr['val'])) {
+ $objFont->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE);
+ } else {
+ $objFont->setUnderline((string) $attr['val']);
+ }
+ }
+ if (isset($run->rPr->strike)) {
+ $attr = $run->rPr->strike->attributes();
+ if (
+ (isset($attr['val']) && self::boolean((string) $attr['val'])) ||
+ (!isset($attr['val']))
+ ) {
+ $objFont->setStrikethrough(true);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return $value;
+ }
+
+ private function readRibbon(Spreadsheet $excel, string $customUITarget, ZipArchive $zip): void
+ {
+ $baseDir = dirname($customUITarget);
+ $nameCustomUI = basename($customUITarget);
+ // get the xml file (ribbon)
+ $localRibbon = $this->getFromZipArchive($zip, $customUITarget);
+ $customUIImagesNames = [];
+ $customUIImagesBinaries = [];
+ // something like customUI/_rels/customUI.xml.rels
+ $pathRels = $baseDir . '/_rels/' . $nameCustomUI . '.rels';
+ $dataRels = $this->getFromZipArchive($zip, $pathRels);
+ if ($dataRels) {
+ // exists and not empty if the ribbon have some pictures (other than internal MSO)
+ $UIRels = simplexml_load_string(
+ $this->getSecurityScannerOrThrow()->scan($dataRels),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ if (false !== $UIRels) {
+ // we need to save id and target to avoid parsing customUI.xml and "guess" if it's a pseudo callback who load the image
+ foreach ($UIRels->Relationship as $ele) {
+ if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/image') {
+ // an image ?
+ $customUIImagesNames[(string) $ele['Id']] = (string) $ele['Target'];
+ $customUIImagesBinaries[(string) $ele['Target']] = $this->getFromZipArchive($zip, $baseDir . '/' . (string) $ele['Target']);
+ }
+ }
+ }
+ }
+ if ($localRibbon) {
+ $excel->setRibbonXMLData($customUITarget, $localRibbon);
+ if (count($customUIImagesNames) > 0 && count($customUIImagesBinaries) > 0) {
+ $excel->setRibbonBinObjects($customUIImagesNames, $customUIImagesBinaries);
+ } else {
+ $excel->setRibbonBinObjects(null, null);
+ }
+ } else {
+ $excel->setRibbonXMLData(null, null);
+ $excel->setRibbonBinObjects(null, null);
+ }
+ }
+
+ /**
+ * @param null|array|bool|SimpleXMLElement $array
+ * @param int|string $key
+ *
+ * @return mixed
+ */
+ private static function getArrayItem($array, $key = 0)
+ {
+ return ($array === null || is_bool($array)) ? null : ($array[$key] ?? null);
+ }
+
+ /**
+ * @param null|SimpleXMLElement|string $base
+ * @param null|SimpleXMLElement|string $add
+ */
+ private static function dirAdd($base, $add): string
+ {
+ $base = (string) $base;
+ $add = (string) $add;
+
+ return (string) preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add");
+ }
+
+ private static function toCSSArray(string $style): array
+ {
+ $style = self::stripWhiteSpaceFromStyleString($style);
+
+ $temp = explode(';', $style);
+ $style = [];
+ foreach ($temp as $item) {
+ $item = explode(':', $item);
+
+ if (strpos($item[1], 'px') !== false) {
+ $item[1] = str_replace('px', '', $item[1]);
+ }
+ if (strpos($item[1], 'pt') !== false) {
+ $item[1] = str_replace('pt', '', $item[1]);
+ $item[1] = (string) Font::fontSizeToPixels((int) $item[1]);
+ }
+ if (strpos($item[1], 'in') !== false) {
+ $item[1] = str_replace('in', '', $item[1]);
+ $item[1] = (string) Font::inchSizeToPixels((int) $item[1]);
+ }
+ if (strpos($item[1], 'cm') !== false) {
+ $item[1] = str_replace('cm', '', $item[1]);
+ $item[1] = (string) Font::centimeterSizeToPixels((int) $item[1]);
+ }
+
+ $style[$item[0]] = $item[1];
+ }
+
+ return $style;
+ }
+
+ public static function stripWhiteSpaceFromStyleString(string $string): string
+ {
+ return trim(str_replace(["\r", "\n", ' '], '', $string), ';');
+ }
+
+ private static function boolean(string $value): bool
+ {
+ if (is_numeric($value)) {
+ return (bool) $value;
+ }
+
+ return $value === 'true' || $value === 'TRUE';
+ }
+
+ /**
+ * @param array $hyperlinks
+ */
+ private function readHyperLinkDrawing(\PhpOffice\PhpSpreadsheet\Worksheet\Drawing $objDrawing, SimpleXMLElement $cellAnchor, $hyperlinks): void
+ {
+ $hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick;
+
+ if ($hlinkClick->count() === 0) {
+ return;
+ }
+
+ $hlinkId = (string) self::getAttributes($hlinkClick, Namespaces::SCHEMA_OFFICE_DOCUMENT)['id'];
+ $hyperlink = new Hyperlink(
+ $hyperlinks[$hlinkId],
+ (string) self::getArrayItem(self::getAttributes($cellAnchor->pic->nvPicPr->cNvPr), 'name')
+ );
+ $objDrawing->setHyperlink($hyperlink);
+ }
+
+ private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkbook): void
+ {
+ if (!$xmlWorkbook->workbookProtection) {
+ return;
+ }
+
+ $excel->getSecurity()->setLockRevision(self::getLockValue($xmlWorkbook->workbookProtection, 'lockRevision'));
+ $excel->getSecurity()->setLockStructure(self::getLockValue($xmlWorkbook->workbookProtection, 'lockStructure'));
+ $excel->getSecurity()->setLockWindows(self::getLockValue($xmlWorkbook->workbookProtection, 'lockWindows'));
+
+ if ($xmlWorkbook->workbookProtection['revisionsPassword']) {
+ $excel->getSecurity()->setRevisionsPassword(
+ (string) $xmlWorkbook->workbookProtection['revisionsPassword'],
+ true
+ );
+ }
+
+ if ($xmlWorkbook->workbookProtection['workbookPassword']) {
+ $excel->getSecurity()->setWorkbookPassword(
+ (string) $xmlWorkbook->workbookProtection['workbookPassword'],
+ true
+ );
+ }
+ }
+
+ private static function getLockValue(SimpleXmlElement $protection, string $key): ?bool
+ {
+ $returnValue = null;
+ $protectKey = $protection[$key];
+ if (!empty($protectKey)) {
+ $protectKey = (string) $protectKey;
+ $returnValue = $protectKey !== 'false' && (bool) $protectKey;
+ }
+
+ return $returnValue;
+ }
+
+ private function readFormControlProperties(Spreadsheet $excel, string $dir, string $fileWorksheet, Worksheet $docSheet, array &$unparsedLoadedData): void
+ {
+ $zip = $this->zip;
+ if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
+ return;
+ }
+
+ $filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
+ $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS);
+ $ctrlProps = [];
+ foreach ($relsWorksheet->Relationship as $ele) {
+ if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/ctrlProp') {
+ $ctrlProps[(string) $ele['Id']] = $ele;
+ }
+ }
+
+ $unparsedCtrlProps = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['ctrlProps'];
+ foreach ($ctrlProps as $rId => $ctrlProp) {
+ $rId = substr($rId, 3); // rIdXXX
+ $unparsedCtrlProps[$rId] = [];
+ $unparsedCtrlProps[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $ctrlProp['Target']);
+ $unparsedCtrlProps[$rId]['relFilePath'] = (string) $ctrlProp['Target'];
+ $unparsedCtrlProps[$rId]['content'] = $this->getSecurityScannerOrThrow()->scan($this->getFromZipArchive($zip, $unparsedCtrlProps[$rId]['filePath']));
+ }
+ unset($unparsedCtrlProps);
+ }
+
+ private function readPrinterSettings(Spreadsheet $excel, string $dir, string $fileWorksheet, Worksheet $docSheet, array &$unparsedLoadedData): void
+ {
+ $zip = $this->zip;
+ if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) {
+ return;
+ }
+
+ $filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
+ $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS);
+ $sheetPrinterSettings = [];
+ foreach ($relsWorksheet->Relationship as $ele) {
+ if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/printerSettings') {
+ $sheetPrinterSettings[(string) $ele['Id']] = $ele;
+ }
+ }
+
+ $unparsedPrinterSettings = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['printerSettings'];
+ foreach ($sheetPrinterSettings as $rId => $printerSettings) {
+ $rId = substr($rId, 3); // rIdXXX
+ if (substr($rId, -2) !== 'ps') {
+ $rId = $rId . 'ps'; // rIdXXX, add 'ps' suffix to avoid identical resource identifier collision with unparsed vmlDrawing
+ }
+ $unparsedPrinterSettings[$rId] = [];
+ $unparsedPrinterSettings[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $printerSettings['Target']);
+ $unparsedPrinterSettings[$rId]['relFilePath'] = (string) $printerSettings['Target'];
+ $unparsedPrinterSettings[$rId]['content'] = $this->getSecurityScannerOrThrow()->scan($this->getFromZipArchive($zip, $unparsedPrinterSettings[$rId]['filePath']));
+ }
+ unset($unparsedPrinterSettings);
+ }
+
+ private function getWorkbookBaseName(): array
+ {
+ $workbookBasename = '';
+ $xmlNamespaceBase = '';
+
+ // check if it is an OOXML archive
+ $rels = $this->loadZip(self::INITIAL_FILE);
+ foreach ($rels->children(Namespaces::RELATIONSHIPS)->Relationship as $rel) {
+ $rel = self::getAttributes($rel);
+ $type = (string) $rel['Type'];
+ switch ($type) {
+ case Namespaces::OFFICE_DOCUMENT:
+ case Namespaces::PURL_OFFICE_DOCUMENT:
+ $basename = basename((string) $rel['Target']);
+ $xmlNamespaceBase = dirname($type);
+ if (preg_match('/workbook.*\.xml/', $basename)) {
+ $workbookBasename = $basename;
+ }
+
+ break;
+ }
+ }
+
+ return [$workbookBasename, $xmlNamespaceBase];
+ }
+
+ private function readSheetProtection(Worksheet $docSheet, SimpleXMLElement $xmlSheet): void
+ {
+ if ($this->readDataOnly || !$xmlSheet->sheetProtection) {
+ return;
+ }
+
+ $algorithmName = (string) $xmlSheet->sheetProtection['algorithmName'];
+ $protection = $docSheet->getProtection();
+ $protection->setAlgorithm($algorithmName);
+
+ if ($algorithmName) {
+ $protection->setPassword((string) $xmlSheet->sheetProtection['hashValue'], true);
+ $protection->setSalt((string) $xmlSheet->sheetProtection['saltValue']);
+ $protection->setSpinCount((int) $xmlSheet->sheetProtection['spinCount']);
+ } else {
+ $protection->setPassword((string) $xmlSheet->sheetProtection['password'], true);
+ }
+
+ if ($xmlSheet->protectedRanges->protectedRange) {
+ foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) {
+ $docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true);
+ }
+ }
+ }
+
+ private function readAutoFilter(
+ SimpleXMLElement $xmlSheet,
+ Worksheet $docSheet
+ ): void {
+ if ($xmlSheet && $xmlSheet->autoFilter) {
+ (new AutoFilter($docSheet, $xmlSheet))->load();
+ }
+ }
+
+ private function readTables(
+ SimpleXMLElement $xmlSheet,
+ Worksheet $docSheet,
+ string $dir,
+ string $fileWorksheet,
+ ZipArchive $zip
+ ): void {
+ if ($xmlSheet && $xmlSheet->tableParts && (int) $xmlSheet->tableParts['count'] > 0) {
+ $this->readTablesInTablesFile($xmlSheet, $dir, $fileWorksheet, $zip, $docSheet);
+ }
+ }
+
+ private function readTablesInTablesFile(
+ SimpleXMLElement $xmlSheet,
+ string $dir,
+ string $fileWorksheet,
+ ZipArchive $zip,
+ Worksheet $docSheet
+ ): void {
+ foreach ($xmlSheet->tableParts->tablePart as $tablePart) {
+ $relation = self::getAttributes($tablePart, Namespaces::SCHEMA_OFFICE_DOCUMENT);
+ $tablePartRel = (string) $relation['id'];
+ $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels';
+
+ if ($zip->locateName($relationsFileName)) {
+ $relsTableReferences = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS);
+ foreach ($relsTableReferences->Relationship as $relationship) {
+ $relationshipAttributes = self::getAttributes($relationship, '');
+
+ if ((string) $relationshipAttributes['Id'] === $tablePartRel) {
+ $relationshipFileName = (string) $relationshipAttributes['Target'];
+ $relationshipFilePath = dirname("$dir/$fileWorksheet") . '/' . $relationshipFileName;
+ $relationshipFilePath = File::realpath($relationshipFilePath);
+
+ if ($this->fileExistsInArchive($this->zip, $relationshipFilePath)) {
+ $tableXml = $this->loadZip($relationshipFilePath);
+ (new TableReader($docSheet, $tableXml))->load();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static function extractStyles(?SimpleXMLElement $sxml, string $node1, string $node2): array
+ {
+ $array = [];
+ if ($sxml && $sxml->{$node1}->{$node2}) {
+ foreach ($sxml->{$node1}->{$node2} as $node) {
+ $array[] = $node;
+ }
+ }
+
+ return $array;
+ }
+
+ private static function extractPalette(?SimpleXMLElement $sxml): array
+ {
+ $array = [];
+ if ($sxml && $sxml->colors->indexedColors) {
+ foreach ($sxml->colors->indexedColors->rgbColor as $node) {
+ if ($node !== null) {
+ $attr = $node->attributes();
+ if (isset($attr['rgb'])) {
+ $array[] = (string) $attr['rgb'];
+ }
+ }
+ }
+ }
+
+ return $array;
+ }
+
+ private function processIgnoredErrors(SimpleXMLElement $xml, Worksheet $sheet): void
+ {
+ $attributes = self::getAttributes($xml);
+ $sqref = (string) ($attributes['sqref'] ?? '');
+ $numberStoredAsText = (string) ($attributes['numberStoredAsText'] ?? '');
+ $formula = (string) ($attributes['formula'] ?? '');
+ $twoDigitTextYear = (string) ($attributes['twoDigitTextYear'] ?? '');
+ $evalError = (string) ($attributes['evalError'] ?? '');
+ if (!empty($sqref)) {
+ $explodedSqref = explode(' ', $sqref);
+ $pattern1 = '/^([A-Z]{1,3})([0-9]{1,7})(:([A-Z]{1,3})([0-9]{1,7}))?$/';
+ foreach ($explodedSqref as $sqref1) {
+ if (preg_match($pattern1, $sqref1, $matches) === 1) {
+ $firstRow = $matches[2];
+ $firstCol = $matches[1];
+ if (array_key_exists(3, $matches)) {
+ $lastCol = $matches[4];
+ $lastRow = $matches[5];
+ } else {
+ $lastCol = $firstCol;
+ $lastRow = $firstRow;
+ }
+ ++$lastCol;
+ for ($row = $firstRow; $row <= $lastRow; ++$row) {
+ for ($col = $firstCol; $col !== $lastCol; ++$col) {
+ if ($numberStoredAsText === '1') {
+ $sheet->getCell("$col$row")->getIgnoredErrors()->setNumberStoredAsText(true);
+ }
+ if ($formula === '1') {
+ $sheet->getCell("$col$row")->getIgnoredErrors()->setFormula(true);
+ }
+ if ($twoDigitTextYear === '1') {
+ $sheet->getCell("$col$row")->getIgnoredErrors()->setTwoDigitTextYear(true);
+ }
+ if ($evalError === '1') {
+ $sheet->getCell("$col$row")->getIgnoredErrors()->setEvalError(true);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
new file mode 100644
index 000000000..a6ab4d894
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php
@@ -0,0 +1,159 @@
+parent = $parent;
+ $this->worksheetXml = $worksheetXml;
+ }
+
+ public function load(): void
+ {
+ // Remove all "$" in the auto filter range
+ $autoFilterRange = (string) preg_replace('/\$/', '', $this->worksheetXml->autoFilter['ref'] ?? '');
+ if (strpos($autoFilterRange, ':') !== false) {
+ $this->readAutoFilter($autoFilterRange, $this->worksheetXml);
+ }
+ }
+
+ private function readAutoFilter(string $autoFilterRange, SimpleXMLElement $xmlSheet): void
+ {
+ $autoFilter = $this->parent->getAutoFilter();
+ $autoFilter->setRange($autoFilterRange);
+
+ foreach ($xmlSheet->autoFilter->filterColumn as $filterColumn) {
+ $column = $autoFilter->getColumnByOffset((int) $filterColumn['colId']);
+ // Check for standard filters
+ if ($filterColumn->filters) {
+ $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER);
+ $filters = $filterColumn->filters;
+ if ((isset($filters['blank'])) && ((int) $filters['blank'] == 1)) {
+ // Operator is undefined, but always treated as EQUAL
+ $column->createRule()->setRule('', '')->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
+ }
+ // Standard filters are always an OR join, so no join rule needs to be set
+ // Entries can be either filter elements
+ foreach ($filters->filter as $filterRule) {
+ // Operator is undefined, but always treated as EQUAL
+ $column->createRule()->setRule('', (string) $filterRule['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER);
+ }
+
+ // Or Date Group elements
+ $this->readDateRangeAutoFilter($filters, $column);
+ }
+
+ // Check for custom filters
+ $this->readCustomAutoFilter($filterColumn, $column);
+ // Check for dynamic filters
+ $this->readDynamicAutoFilter($filterColumn, $column);
+ // Check for dynamic filters
+ $this->readTopTenAutoFilter($filterColumn, $column);
+ }
+ $autoFilter->setEvaluated(true);
+ }
+
+ private function readDateRangeAutoFilter(SimpleXMLElement $filters, Column $column): void
+ {
+ foreach ($filters->dateGroupItem as $dateGroupItem) {
+ // Operator is undefined, but always treated as EQUAL
+ $column->createRule()->setRule(
+ '',
+ [
+ 'year' => (string) $dateGroupItem['year'],
+ 'month' => (string) $dateGroupItem['month'],
+ 'day' => (string) $dateGroupItem['day'],
+ 'hour' => (string) $dateGroupItem['hour'],
+ 'minute' => (string) $dateGroupItem['minute'],
+ 'second' => (string) $dateGroupItem['second'],
+ ],
+ (string) $dateGroupItem['dateTimeGrouping']
+ )->setRuleType(Rule::AUTOFILTER_RULETYPE_DATEGROUP);
+ }
+ }
+
+ private function readCustomAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void
+ {
+ if (isset($filterColumn, $filterColumn->customFilters)) {
+ $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER);
+ $customFilters = $filterColumn->customFilters;
+ // Custom filters can an AND or an OR join;
+ // and there should only ever be one or two entries
+ if ((isset($customFilters['and'])) && ((string) $customFilters['and'] === '1')) {
+ $column->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND);
+ }
+ foreach ($customFilters->customFilter as $filterRule) {
+ $column->createRule()->setRule(
+ (string) $filterRule['operator'],
+ (string) $filterRule['val']
+ )->setRuleType(Rule::AUTOFILTER_RULETYPE_CUSTOMFILTER);
+ }
+ }
+ }
+
+ private function readDynamicAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void
+ {
+ if (isset($filterColumn, $filterColumn->dynamicFilter)) {
+ $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER);
+ // We should only ever have one dynamic filter
+ foreach ($filterColumn->dynamicFilter as $filterRule) {
+ // Operator is undefined, but always treated as EQUAL
+ $column->createRule()->setRule(
+ '',
+ (string) $filterRule['val'],
+ (string) $filterRule['type']
+ )->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER);
+ if (isset($filterRule['val'])) {
+ $column->setAttribute('val', (string) $filterRule['val']);
+ }
+ if (isset($filterRule['maxVal'])) {
+ $column->setAttribute('maxVal', (string) $filterRule['maxVal']);
+ }
+ }
+ }
+ }
+
+ private function readTopTenAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void
+ {
+ if (isset($filterColumn, $filterColumn->top10)) {
+ $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER);
+ // We should only ever have one top10 filter
+ foreach ($filterColumn->top10 as $filterRule) {
+ $column->createRule()->setRule(
+ (
+ ((isset($filterRule['percent'])) && ((string) $filterRule['percent'] === '1'))
+ ? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT
+ : Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE
+ ),
+ (string) $filterRule['val'],
+ (
+ ((isset($filterRule['top'])) && ((string) $filterRule['top'] === '1'))
+ ? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP
+ : Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM
+ )
+ )->setRuleType(Rule::AUTOFILTER_RULETYPE_TOPTENFILTER);
+ }
+ }
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php
new file mode 100644
index 000000000..6b99877c5
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php
@@ -0,0 +1,22 @@
+cNamespace = $cNamespace;
+ $this->aNamespace = $aNamespace;
+ }
+
+ /**
+ * @param string $name
+ * @param string $format
+ *
+ * @return null|bool|float|int|string
+ */
+ private static function getAttribute(SimpleXMLElement $component, $name, $format)
+ {
+ $attributes = $component->attributes();
+ if (@isset($attributes[$name])) {
+ if ($format == 'string') {
+ return (string) $attributes[$name];
+ } elseif ($format == 'integer') {
+ return (int) $attributes[$name];
+ } elseif ($format == 'boolean') {
+ $value = (string) $attributes[$name];
+
+ return $value === 'true' || $value === '1';
+ }
+
+ return (float) $attributes[$name];
+ }
+
+ return null;
+ }
+
+ /**
+ * @param string $chartName
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Chart\Chart
+ */
+ public function readChart(SimpleXMLElement $chartElements, $chartName)
+ {
+ $chartElementsC = $chartElements->children($this->cNamespace);
+
+ $XaxisLabel = $YaxisLabel = $legend = $title = null;
+ $dispBlanksAs = $plotVisOnly = null;
+ $plotArea = null;
+ $rotX = $rotY = $rAngAx = $perspective = null;
+ $xAxis = new Axis();
+ $yAxis = new Axis();
+ $autoTitleDeleted = null;
+ $chartNoFill = false;
+ $chartBorderLines = null;
+ $chartFillColor = null;
+ $gradientArray = [];
+ $gradientLin = null;
+ $roundedCorners = false;
+ $gapWidth = null;
+ $useUpBars = null;
+ $useDownBars = null;
+ foreach ($chartElementsC as $chartElementKey => $chartElement) {
+ switch ($chartElementKey) {
+ case 'spPr':
+ $children = $chartElementsC->spPr->children($this->aNamespace);
+ if (isset($children->noFill)) {
+ $chartNoFill = true;
+ }
+ if (isset($children->solidFill)) {
+ $chartFillColor = $this->readColor($children->solidFill);
+ }
+ if (isset($children->ln)) {
+ $chartBorderLines = new GridLines();
+ $this->readLineStyle($chartElementsC, $chartBorderLines);
+ }
+
+ break;
+ case 'roundedCorners':
+ /** @var bool */
+ $roundedCorners = self::getAttribute($chartElementsC->roundedCorners, 'val', 'boolean');
+
+ break;
+ case 'chart':
+ foreach ($chartElement as $chartDetailsKey => $chartDetails) {
+ $chartDetails = Xlsx::testSimpleXml($chartDetails);
+ switch ($chartDetailsKey) {
+ case 'autoTitleDeleted':
+ /** @var bool */
+ $autoTitleDeleted = self::getAttribute($chartElementsC->chart->autoTitleDeleted, 'val', 'boolean');
+
+ break;
+ case 'view3D':
+ $rotX = self::getAttribute($chartDetails->rotX, 'val', 'integer');
+ $rotY = self::getAttribute($chartDetails->rotY, 'val', 'integer');
+ $rAngAx = self::getAttribute($chartDetails->rAngAx, 'val', 'integer');
+ $perspective = self::getAttribute($chartDetails->perspective, 'val', 'integer');
+
+ break;
+ case 'plotArea':
+ $plotAreaLayout = $XaxisLabel = $YaxisLabel = null;
+ $plotSeries = $plotAttributes = [];
+ $catAxRead = false;
+ $plotNoFill = false;
+ foreach ($chartDetails as $chartDetailKey => $chartDetail) {
+ $chartDetail = Xlsx::testSimpleXml($chartDetail);
+ switch ($chartDetailKey) {
+ case 'spPr':
+ $possibleNoFill = $chartDetails->spPr->children($this->aNamespace);
+ if (isset($possibleNoFill->noFill)) {
+ $plotNoFill = true;
+ }
+ if (isset($possibleNoFill->gradFill->gsLst)) {
+ foreach ($possibleNoFill->gradFill->gsLst->gs as $gradient) {
+ $gradient = Xlsx::testSimpleXml($gradient);
+ /** @var float */
+ $pos = self::getAttribute($gradient, 'pos', 'float');
+ $gradientArray[] = [
+ $pos / ChartProperties::PERCENTAGE_MULTIPLIER,
+ new ChartColor($this->readColor($gradient)),
+ ];
+ }
+ }
+ if (isset($possibleNoFill->gradFill->lin)) {
+ $gradientLin = ChartProperties::XmlToAngle((string) self::getAttribute($possibleNoFill->gradFill->lin, 'ang', 'string'));
+ }
+
+ break;
+ case 'layout':
+ $plotAreaLayout = $this->chartLayoutDetails($chartDetail);
+
+ break;
+ case Axis::AXIS_TYPE_CATEGORY:
+ case Axis::AXIS_TYPE_DATE:
+ $catAxRead = true;
+ if (isset($chartDetail->title)) {
+ $XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
+ }
+ $xAxis->setAxisType($chartDetailKey);
+ $this->readEffects($chartDetail, $xAxis);
+ $this->readLineStyle($chartDetail, $xAxis);
+ if (isset($chartDetail->spPr)) {
+ $sppr = $chartDetail->spPr->children($this->aNamespace);
+ if (isset($sppr->solidFill)) {
+ $axisColorArray = $this->readColor($sppr->solidFill);
+ $xAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']);
+ }
+ if (isset($chartDetail->spPr->ln->noFill)) {
+ $xAxis->setNoFill(true);
+ }
+ }
+ if (isset($chartDetail->majorGridlines)) {
+ $majorGridlines = new GridLines();
+ if (isset($chartDetail->majorGridlines->spPr)) {
+ $this->readEffects($chartDetail->majorGridlines, $majorGridlines);
+ $this->readLineStyle($chartDetail->majorGridlines, $majorGridlines);
+ }
+ $xAxis->setMajorGridlines($majorGridlines);
+ }
+ if (isset($chartDetail->minorGridlines)) {
+ $minorGridlines = new GridLines();
+ $minorGridlines->activateObject();
+ if (isset($chartDetail->minorGridlines->spPr)) {
+ $this->readEffects($chartDetail->minorGridlines, $minorGridlines);
+ $this->readLineStyle($chartDetail->minorGridlines, $minorGridlines);
+ }
+ $xAxis->setMinorGridlines($minorGridlines);
+ }
+ $this->setAxisProperties($chartDetail, $xAxis);
+
+ break;
+ case Axis::AXIS_TYPE_VALUE:
+ $whichAxis = null;
+ $axPos = null;
+ if (isset($chartDetail->axPos)) {
+ $axPos = self::getAttribute($chartDetail->axPos, 'val', 'string');
+ }
+ if ($catAxRead) {
+ $whichAxis = $yAxis;
+ $yAxis->setAxisType($chartDetailKey);
+ } elseif (!empty($axPos)) {
+ switch ($axPos) {
+ case 't':
+ case 'b':
+ $whichAxis = $xAxis;
+ $xAxis->setAxisType($chartDetailKey);
+
+ break;
+ case 'r':
+ case 'l':
+ $whichAxis = $yAxis;
+ $yAxis->setAxisType($chartDetailKey);
+
+ break;
+ }
+ }
+ if (isset($chartDetail->title)) {
+ $axisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace));
+
+ switch ($axPos) {
+ case 't':
+ case 'b':
+ $XaxisLabel = $axisLabel;
+
+ break;
+ case 'r':
+ case 'l':
+ $YaxisLabel = $axisLabel;
+
+ break;
+ }
+ }
+ $this->readEffects($chartDetail, $whichAxis);
+ $this->readLineStyle($chartDetail, $whichAxis);
+ if ($whichAxis !== null && isset($chartDetail->spPr)) {
+ $sppr = $chartDetail->spPr->children($this->aNamespace);
+ if (isset($sppr->solidFill)) {
+ $axisColorArray = $this->readColor($sppr->solidFill);
+ $whichAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']);
+ }
+ if (isset($sppr->ln->noFill)) {
+ $whichAxis->setNoFill(true);
+ }
+ }
+ if ($whichAxis !== null && isset($chartDetail->majorGridlines)) {
+ $majorGridlines = new GridLines();
+ if (isset($chartDetail->majorGridlines->spPr)) {
+ $this->readEffects($chartDetail->majorGridlines, $majorGridlines);
+ $this->readLineStyle($chartDetail->majorGridlines, $majorGridlines);
+ }
+ $whichAxis->setMajorGridlines($majorGridlines);
+ }
+ if ($whichAxis !== null && isset($chartDetail->minorGridlines)) {
+ $minorGridlines = new GridLines();
+ $minorGridlines->activateObject();
+ if (isset($chartDetail->minorGridlines->spPr)) {
+ $this->readEffects($chartDetail->minorGridlines, $minorGridlines);
+ $this->readLineStyle($chartDetail->minorGridlines, $minorGridlines);
+ }
+ $whichAxis->setMinorGridlines($minorGridlines);
+ }
+ $this->setAxisProperties($chartDetail, $whichAxis);
+
+ break;
+ case 'barChart':
+ case 'bar3DChart':
+ $barDirection = self::getAttribute($chartDetail->barDir, 'val', 'string');
+ $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotSer->setPlotDirection("$barDirection");
+ $plotSeries[] = $plotSer;
+ $plotAttributes = $this->readChartAttributes($chartDetail);
+
+ break;
+ case 'lineChart':
+ case 'line3DChart':
+ $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotAttributes = $this->readChartAttributes($chartDetail);
+
+ break;
+ case 'areaChart':
+ case 'area3DChart':
+ $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotAttributes = $this->readChartAttributes($chartDetail);
+
+ break;
+ case 'doughnutChart':
+ case 'pieChart':
+ case 'pie3DChart':
+ $explosion = self::getAttribute($chartDetail->ser->explosion, 'val', 'string');
+ $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotSer->setPlotStyle("$explosion");
+ $plotSeries[] = $plotSer;
+ $plotAttributes = $this->readChartAttributes($chartDetail);
+
+ break;
+ case 'scatterChart':
+ /** @var string */
+ $scatterStyle = self::getAttribute($chartDetail->scatterStyle, 'val', 'string');
+ $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotSer->setPlotStyle($scatterStyle);
+ $plotSeries[] = $plotSer;
+ $plotAttributes = $this->readChartAttributes($chartDetail);
+
+ break;
+ case 'bubbleChart':
+ $bubbleScale = self::getAttribute($chartDetail->bubbleScale, 'val', 'integer');
+ $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotSer->setPlotStyle("$bubbleScale");
+ $plotSeries[] = $plotSer;
+ $plotAttributes = $this->readChartAttributes($chartDetail);
+
+ break;
+ case 'radarChart':
+ /** @var string */
+ $radarStyle = self::getAttribute($chartDetail->radarStyle, 'val', 'string');
+ $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotSer->setPlotStyle($radarStyle);
+ $plotSeries[] = $plotSer;
+ $plotAttributes = $this->readChartAttributes($chartDetail);
+
+ break;
+ case 'surfaceChart':
+ case 'surface3DChart':
+ $wireFrame = self::getAttribute($chartDetail->wireframe, 'val', 'boolean');
+ $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ $plotSer->setPlotStyle("$wireFrame");
+ $plotSeries[] = $plotSer;
+ $plotAttributes = $this->readChartAttributes($chartDetail);
+
+ break;
+ case 'stockChart':
+ $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ if (isset($chartDetail->upDownBars->gapWidth)) {
+ $gapWidth = self::getAttribute($chartDetail->upDownBars->gapWidth, 'val', 'integer');
+ }
+ if (isset($chartDetail->upDownBars->upBars)) {
+ $useUpBars = true;
+ }
+ if (isset($chartDetail->upDownBars->downBars)) {
+ $useDownBars = true;
+ }
+ $plotAttributes = $this->readChartAttributes($chartDetail);
+
+ break;
+ }
+ }
+ if ($plotAreaLayout == null) {
+ $plotAreaLayout = new Layout();
+ }
+ $plotArea = new PlotArea($plotAreaLayout, $plotSeries);
+ $this->setChartAttributes($plotAreaLayout, $plotAttributes);
+ if ($plotNoFill) {
+ $plotArea->setNoFill(true);
+ }
+ if (!empty($gradientArray)) {
+ $plotArea->setGradientFillProperties($gradientArray, $gradientLin);
+ }
+ if (is_int($gapWidth)) {
+ $plotArea->setGapWidth($gapWidth);
+ }
+ if ($useUpBars === true) {
+ $plotArea->setUseUpBars(true);
+ }
+ if ($useDownBars === true) {
+ $plotArea->setUseDownBars(true);
+ }
+
+ break;
+ case 'plotVisOnly':
+ $plotVisOnly = self::getAttribute($chartDetails, 'val', 'string');
+
+ break;
+ case 'dispBlanksAs':
+ $dispBlanksAs = self::getAttribute($chartDetails, 'val', 'string');
+
+ break;
+ case 'title':
+ $title = $this->chartTitle($chartDetails);
+
+ break;
+ case 'legend':
+ $legendPos = 'r';
+ $legendLayout = null;
+ $legendOverlay = false;
+ $legendBorderLines = null;
+ $legendFillColor = null;
+ $legendText = null;
+ $addLegendText = false;
+ foreach ($chartDetails as $chartDetailKey => $chartDetail) {
+ $chartDetail = Xlsx::testSimpleXml($chartDetail);
+ switch ($chartDetailKey) {
+ case 'legendPos':
+ $legendPos = self::getAttribute($chartDetail, 'val', 'string');
+
+ break;
+ case 'overlay':
+ $legendOverlay = self::getAttribute($chartDetail, 'val', 'boolean');
+
+ break;
+ case 'layout':
+ $legendLayout = $this->chartLayoutDetails($chartDetail);
+
+ break;
+ case 'spPr':
+ $children = $chartDetails->spPr->children($this->aNamespace);
+ if (isset($children->solidFill)) {
+ $legendFillColor = $this->readColor($children->solidFill);
+ }
+ if (isset($children->ln)) {
+ $legendBorderLines = new GridLines();
+ $this->readLineStyle($chartDetails, $legendBorderLines);
+ }
+
+ break;
+ case 'txPr':
+ $children = $chartDetails->txPr->children($this->aNamespace);
+ $addLegendText = false;
+ $legendText = new AxisText();
+ if (isset($children->p->pPr->defRPr->solidFill)) {
+ $colorArray = $this->readColor($children->p->pPr->defRPr->solidFill);
+ $legendText->getFillColorObject()->setColorPropertiesArray($colorArray);
+ $addLegendText = true;
+ }
+ if (isset($children->p->pPr->defRPr->effectLst)) {
+ $this->readEffects($children->p->pPr->defRPr, $legendText, false);
+ $addLegendText = true;
+ }
+
+ break;
+ }
+ }
+ $legend = new Legend("$legendPos", $legendLayout, (bool) $legendOverlay);
+ if ($legendFillColor !== null) {
+ $legend->getFillColor()->setColorPropertiesArray($legendFillColor);
+ }
+ if ($legendBorderLines !== null) {
+ $legend->setBorderLines($legendBorderLines);
+ }
+ if ($addLegendText) {
+ $legend->setLegendText($legendText);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel, $xAxis, $yAxis);
+ if ($chartNoFill) {
+ $chart->setNoFill(true);
+ }
+ if ($chartFillColor !== null) {
+ $chart->getFillColor()->setColorPropertiesArray($chartFillColor);
+ }
+ if ($chartBorderLines !== null) {
+ $chart->setBorderLines($chartBorderLines);
+ }
+ $chart->setRoundedCorners($roundedCorners);
+ if (is_bool($autoTitleDeleted)) {
+ $chart->setAutoTitleDeleted($autoTitleDeleted);
+ }
+ if (is_int($rotX)) {
+ $chart->setRotX($rotX);
+ }
+ if (is_int($rotY)) {
+ $chart->setRotY($rotY);
+ }
+ if (is_int($rAngAx)) {
+ $chart->setRAngAx($rAngAx);
+ }
+ if (is_int($perspective)) {
+ $chart->setPerspective($perspective);
+ }
+
+ return $chart;
+ }
+
+ private function chartTitle(SimpleXMLElement $titleDetails): Title
+ {
+ $caption = [];
+ $titleLayout = null;
+ $titleOverlay = false;
+ foreach ($titleDetails as $titleDetailKey => $chartDetail) {
+ $chartDetail = Xlsx::testSimpleXml($chartDetail);
+ switch ($titleDetailKey) {
+ case 'tx':
+ if (isset($chartDetail->rich)) {
+ $titleDetails = $chartDetail->rich->children($this->aNamespace);
+ foreach ($titleDetails as $titleKey => $titleDetail) {
+ $titleDetail = Xlsx::testSimpleXml($titleDetail);
+ switch ($titleKey) {
+ case 'p':
+ $titleDetailPart = $titleDetail->children($this->aNamespace);
+ $caption[] = $this->parseRichText($titleDetailPart);
+ }
+ }
+ } elseif (isset($chartDetail->strRef->strCache)) {
+ foreach ($chartDetail->strRef->strCache->pt as $pt) {
+ if (isset($pt->v)) {
+ $caption[] = (string) $pt->v;
+ }
+ }
+ }
+
+ break;
+ case 'overlay':
+ $titleOverlay = self::getAttribute($chartDetail, 'val', 'boolean');
+
+ break;
+ case 'layout':
+ $titleLayout = $this->chartLayoutDetails($chartDetail);
+
+ break;
+ }
+ }
+
+ return new Title($caption, $titleLayout, (bool) $titleOverlay);
+ }
+
+ private function chartLayoutDetails(SimpleXMLElement $chartDetail): ?Layout
+ {
+ if (!isset($chartDetail->manualLayout)) {
+ return null;
+ }
+ $details = $chartDetail->manualLayout->children($this->cNamespace);
+ if ($details === null) {
+ return null;
+ }
+ $layout = [];
+ foreach ($details as $detailKey => $detail) {
+ $detail = Xlsx::testSimpleXml($detail);
+ $layout[$detailKey] = self::getAttribute($detail, 'val', 'string');
+ }
+
+ return new Layout($layout);
+ }
+
+ private function chartDataSeries(SimpleXMLElement $chartDetail, string $plotType): DataSeries
+ {
+ $multiSeriesType = null;
+ $smoothLine = false;
+ $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = $seriesBubbles = [];
+
+ $seriesDetailSet = $chartDetail->children($this->cNamespace);
+ foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) {
+ switch ($seriesDetailKey) {
+ case 'grouping':
+ $multiSeriesType = self::getAttribute($chartDetail->grouping, 'val', 'string');
+
+ break;
+ case 'ser':
+ $marker = null;
+ $seriesIndex = '';
+ $fillColor = null;
+ $pointSize = null;
+ $noFill = false;
+ $bubble3D = false;
+ $dptColors = [];
+ $markerFillColor = null;
+ $markerBorderColor = null;
+ $lineStyle = null;
+ $labelLayout = null;
+ $trendLines = [];
+ foreach ($seriesDetails as $seriesKey => $seriesDetail) {
+ $seriesDetail = Xlsx::testSimpleXml($seriesDetail);
+ switch ($seriesKey) {
+ case 'idx':
+ $seriesIndex = self::getAttribute($seriesDetail, 'val', 'integer');
+
+ break;
+ case 'order':
+ $seriesOrder = self::getAttribute($seriesDetail, 'val', 'integer');
+ $plotOrder[$seriesIndex] = $seriesOrder;
+
+ break;
+ case 'tx':
+ $seriesLabel[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail);
+
+ break;
+ case 'spPr':
+ $children = $seriesDetail->children($this->aNamespace);
+ if (isset($children->ln)) {
+ $ln = $children->ln;
+ if (is_countable($ln->noFill) && count($ln->noFill) === 1) {
+ $noFill = true;
+ }
+ $lineStyle = new GridLines();
+ $this->readLineStyle($seriesDetails, $lineStyle);
+ }
+ if (isset($children->effectLst)) {
+ if ($lineStyle === null) {
+ $lineStyle = new GridLines();
+ }
+ $this->readEffects($seriesDetails, $lineStyle);
+ }
+ if (isset($children->solidFill)) {
+ $fillColor = new ChartColor($this->readColor($children->solidFill));
+ }
+
+ break;
+ case 'dPt':
+ $dptIdx = (int) self::getAttribute($seriesDetail->idx, 'val', 'string');
+ if (isset($seriesDetail->spPr)) {
+ $children = $seriesDetail->spPr->children($this->aNamespace);
+ if (isset($children->solidFill)) {
+ $arrayColors = $this->readColor($children->solidFill);
+ $dptColors[$dptIdx] = new ChartColor($arrayColors);
+ }
+ }
+
+ break;
+ case 'trendline':
+ $trendLine = new TrendLine();
+ $this->readLineStyle($seriesDetail, $trendLine);
+ /** @var ?string */
+ $trendLineType = self::getAttribute($seriesDetail->trendlineType, 'val', 'string');
+ /** @var ?bool */
+ $dispRSqr = self::getAttribute($seriesDetail->dispRSqr, 'val', 'boolean');
+ /** @var ?bool */
+ $dispEq = self::getAttribute($seriesDetail->dispEq, 'val', 'boolean');
+ /** @var ?int */
+ $order = self::getAttribute($seriesDetail->order, 'val', 'integer');
+ /** @var ?int */
+ $period = self::getAttribute($seriesDetail->period, 'val', 'integer');
+ /** @var ?float */
+ $forward = self::getAttribute($seriesDetail->forward, 'val', 'float');
+ /** @var ?float */
+ $backward = self::getAttribute($seriesDetail->backward, 'val', 'float');
+ /** @var ?float */
+ $intercept = self::getAttribute($seriesDetail->intercept, 'val', 'float');
+ /** @var ?string */
+ $name = (string) $seriesDetail->name;
+ $trendLine->setTrendLineProperties(
+ $trendLineType,
+ $order,
+ $period,
+ $dispRSqr,
+ $dispEq,
+ $backward,
+ $forward,
+ $intercept,
+ $name
+ );
+ $trendLines[] = $trendLine;
+
+ break;
+ case 'marker':
+ $marker = self::getAttribute($seriesDetail->symbol, 'val', 'string');
+ $pointSize = self::getAttribute($seriesDetail->size, 'val', 'string');
+ $pointSize = is_numeric($pointSize) ? ((int) $pointSize) : null;
+ if (isset($seriesDetail->spPr)) {
+ $children = $seriesDetail->spPr->children($this->aNamespace);
+ if (isset($children->solidFill)) {
+ $markerFillColor = $this->readColor($children->solidFill);
+ }
+ if (isset($children->ln->solidFill)) {
+ $markerBorderColor = $this->readColor($children->ln->solidFill);
+ }
+ }
+
+ break;
+ case 'smooth':
+ $smoothLine = self::getAttribute($seriesDetail, 'val', 'boolean');
+
+ break;
+ case 'cat':
+ $seriesCategory[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail);
+
+ break;
+ case 'val':
+ $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
+
+ break;
+ case 'xVal':
+ $seriesCategory[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
+
+ break;
+ case 'yVal':
+ $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
+
+ break;
+ case 'bubbleSize':
+ $seriesBubbles[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize");
+
+ break;
+ case 'bubble3D':
+ $bubble3D = self::getAttribute($seriesDetail, 'val', 'boolean');
+
+ break;
+ case 'dLbls':
+ $labelLayout = new Layout($this->readChartAttributes($seriesDetails));
+
+ break;
+ }
+ }
+ if ($labelLayout) {
+ if (isset($seriesLabel[$seriesIndex])) {
+ $seriesLabel[$seriesIndex]->setLabelLayout($labelLayout);
+ }
+ if (isset($seriesCategory[$seriesIndex])) {
+ $seriesCategory[$seriesIndex]->setLabelLayout($labelLayout);
+ }
+ if (isset($seriesValues[$seriesIndex])) {
+ $seriesValues[$seriesIndex]->setLabelLayout($labelLayout);
+ }
+ }
+ if ($noFill) {
+ if (isset($seriesLabel[$seriesIndex])) {
+ $seriesLabel[$seriesIndex]->setScatterLines(false);
+ }
+ if (isset($seriesCategory[$seriesIndex])) {
+ $seriesCategory[$seriesIndex]->setScatterLines(false);
+ }
+ if (isset($seriesValues[$seriesIndex])) {
+ $seriesValues[$seriesIndex]->setScatterLines(false);
+ }
+ }
+ if ($lineStyle !== null) {
+ if (isset($seriesLabel[$seriesIndex])) {
+ $seriesLabel[$seriesIndex]->copyLineStyles($lineStyle);
+ }
+ if (isset($seriesCategory[$seriesIndex])) {
+ $seriesCategory[$seriesIndex]->copyLineStyles($lineStyle);
+ }
+ if (isset($seriesValues[$seriesIndex])) {
+ $seriesValues[$seriesIndex]->copyLineStyles($lineStyle);
+ }
+ }
+ if ($bubble3D) {
+ if (isset($seriesLabel[$seriesIndex])) {
+ $seriesLabel[$seriesIndex]->setBubble3D($bubble3D);
+ }
+ if (isset($seriesCategory[$seriesIndex])) {
+ $seriesCategory[$seriesIndex]->setBubble3D($bubble3D);
+ }
+ if (isset($seriesValues[$seriesIndex])) {
+ $seriesValues[$seriesIndex]->setBubble3D($bubble3D);
+ }
+ }
+ if (!empty($dptColors)) {
+ if (isset($seriesLabel[$seriesIndex])) {
+ $seriesLabel[$seriesIndex]->setFillColor($dptColors);
+ }
+ if (isset($seriesCategory[$seriesIndex])) {
+ $seriesCategory[$seriesIndex]->setFillColor($dptColors);
+ }
+ if (isset($seriesValues[$seriesIndex])) {
+ $seriesValues[$seriesIndex]->setFillColor($dptColors);
+ }
+ }
+ if ($markerFillColor !== null) {
+ if (isset($seriesLabel[$seriesIndex])) {
+ $seriesLabel[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
+ }
+ if (isset($seriesCategory[$seriesIndex])) {
+ $seriesCategory[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
+ }
+ if (isset($seriesValues[$seriesIndex])) {
+ $seriesValues[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor);
+ }
+ }
+ if ($markerBorderColor !== null) {
+ if (isset($seriesLabel[$seriesIndex])) {
+ $seriesLabel[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
+ }
+ if (isset($seriesCategory[$seriesIndex])) {
+ $seriesCategory[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
+ }
+ if (isset($seriesValues[$seriesIndex])) {
+ $seriesValues[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor);
+ }
+ }
+ if ($smoothLine) {
+ if (isset($seriesLabel[$seriesIndex])) {
+ $seriesLabel[$seriesIndex]->setSmoothLine(true);
+ }
+ if (isset($seriesCategory[$seriesIndex])) {
+ $seriesCategory[$seriesIndex]->setSmoothLine(true);
+ }
+ if (isset($seriesValues[$seriesIndex])) {
+ $seriesValues[$seriesIndex]->setSmoothLine(true);
+ }
+ }
+ if (!empty($trendLines)) {
+ if (isset($seriesLabel[$seriesIndex])) {
+ $seriesLabel[$seriesIndex]->setTrendLines($trendLines);
+ }
+ if (isset($seriesCategory[$seriesIndex])) {
+ $seriesCategory[$seriesIndex]->setTrendLines($trendLines);
+ }
+ if (isset($seriesValues[$seriesIndex])) {
+ $seriesValues[$seriesIndex]->setTrendLines($trendLines);
+ }
+ }
+ }
+ }
+ /** @phpstan-ignore-next-line */
+ $series = new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine);
+ $series->setPlotBubbleSizes($seriesBubbles);
+
+ return $series;
+ }
+
+ /**
+ * @return mixed
+ */
+ private function chartDataSeriesValueSet(SimpleXMLElement $seriesDetail, ?string $marker = null, ?ChartColor $fillColor = null, ?string $pointSize = null)
+ {
+ if (isset($seriesDetail->strRef)) {
+ $seriesSource = (string) $seriesDetail->strRef->f;
+ $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
+
+ if (isset($seriesDetail->strRef->strCache)) {
+ $seriesData = $this->chartDataSeriesValues($seriesDetail->strRef->strCache->children($this->cNamespace), 's');
+ $seriesValues
+ ->setFormatCode($seriesData['formatCode'])
+ ->setDataValues($seriesData['dataValues']);
+ }
+
+ return $seriesValues;
+ } elseif (isset($seriesDetail->numRef)) {
+ $seriesSource = (string) $seriesDetail->numRef->f;
+ $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
+ if (isset($seriesDetail->numRef->numCache)) {
+ $seriesData = $this->chartDataSeriesValues($seriesDetail->numRef->numCache->children($this->cNamespace));
+ $seriesValues
+ ->setFormatCode($seriesData['formatCode'])
+ ->setDataValues($seriesData['dataValues']);
+ }
+
+ return $seriesValues;
+ } elseif (isset($seriesDetail->multiLvlStrRef)) {
+ $seriesSource = (string) $seriesDetail->multiLvlStrRef->f;
+ $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
+
+ if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) {
+ $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($this->cNamespace), 's');
+ $seriesValues
+ ->setFormatCode($seriesData['formatCode'])
+ ->setDataValues($seriesData['dataValues']);
+ }
+
+ return $seriesValues;
+ } elseif (isset($seriesDetail->multiLvlNumRef)) {
+ $seriesSource = (string) $seriesDetail->multiLvlNumRef->f;
+ $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize");
+
+ if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) {
+ $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($this->cNamespace), 's');
+ $seriesValues
+ ->setFormatCode($seriesData['formatCode'])
+ ->setDataValues($seriesData['dataValues']);
+ }
+
+ return $seriesValues;
+ }
+
+ if (isset($seriesDetail->v)) {
+ return new DataSeriesValues(
+ DataSeriesValues::DATASERIES_TYPE_STRING,
+ null,
+ null,
+ 1,
+ [(string) $seriesDetail->v]
+ );
+ }
+
+ return null;
+ }
+
+ private function chartDataSeriesValues(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array
+ {
+ $seriesVal = [];
+ $formatCode = '';
+ $pointCount = 0;
+
+ foreach ($seriesValueSet as $seriesValueIdx => $seriesValue) {
+ $seriesValue = Xlsx::testSimpleXml($seriesValue);
+ switch ($seriesValueIdx) {
+ case 'ptCount':
+ $pointCount = self::getAttribute($seriesValue, 'val', 'integer');
+
+ break;
+ case 'formatCode':
+ $formatCode = (string) $seriesValue;
+
+ break;
+ case 'pt':
+ $pointVal = self::getAttribute($seriesValue, 'idx', 'integer');
+ if ($dataType == 's') {
+ $seriesVal[$pointVal] = (string) $seriesValue->v;
+ } elseif ((string) $seriesValue->v === ExcelError::NA()) {
+ $seriesVal[$pointVal] = null;
+ } else {
+ $seriesVal[$pointVal] = (float) $seriesValue->v;
+ }
+
+ break;
+ }
+ }
+
+ return [
+ 'formatCode' => $formatCode,
+ 'pointCount' => $pointCount,
+ 'dataValues' => $seriesVal,
+ ];
+ }
+
+ private function chartDataSeriesValuesMultiLevel(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array
+ {
+ $seriesVal = [];
+ $formatCode = '';
+ $pointCount = 0;
+
+ foreach ($seriesValueSet->lvl as $seriesLevelIdx => $seriesLevel) {
+ foreach ($seriesLevel as $seriesValueIdx => $seriesValue) {
+ $seriesValue = Xlsx::testSimpleXml($seriesValue);
+ switch ($seriesValueIdx) {
+ case 'ptCount':
+ $pointCount = self::getAttribute($seriesValue, 'val', 'integer');
+
+ break;
+ case 'formatCode':
+ $formatCode = (string) $seriesValue;
+
+ break;
+ case 'pt':
+ $pointVal = self::getAttribute($seriesValue, 'idx', 'integer');
+ if ($dataType == 's') {
+ $seriesVal[$pointVal][] = (string) $seriesValue->v;
+ } elseif ((string) $seriesValue->v === ExcelError::NA()) {
+ $seriesVal[$pointVal] = null;
+ } else {
+ $seriesVal[$pointVal][] = (float) $seriesValue->v;
+ }
+
+ break;
+ }
+ }
+ }
+
+ return [
+ 'formatCode' => $formatCode,
+ 'pointCount' => $pointCount,
+ 'dataValues' => $seriesVal,
+ ];
+ }
+
+ private function parseRichText(SimpleXMLElement $titleDetailPart): RichText
+ {
+ $value = new RichText();
+ $defaultFontSize = null;
+ $defaultBold = null;
+ $defaultItalic = null;
+ $defaultUnderscore = null;
+ $defaultStrikethrough = null;
+ $defaultBaseline = null;
+ $defaultFontName = null;
+ $defaultLatin = null;
+ $defaultEastAsian = null;
+ $defaultComplexScript = null;
+ $defaultFontColor = null;
+ if (isset($titleDetailPart->pPr->defRPr)) {
+ /** @var ?int */
+ $defaultFontSize = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer');
+ /** @var ?bool */
+ $defaultBold = self::getAttribute($titleDetailPart->pPr->defRPr, 'b', 'boolean');
+ /** @var ?bool */
+ $defaultItalic = self::getAttribute($titleDetailPart->pPr->defRPr, 'i', 'boolean');
+ /** @var ?string */
+ $defaultUnderscore = self::getAttribute($titleDetailPart->pPr->defRPr, 'u', 'string');
+ /** @var ?string */
+ $defaultStrikethrough = self::getAttribute($titleDetailPart->pPr->defRPr, 'strike', 'string');
+ /** @var ?int */
+ $defaultBaseline = self::getAttribute($titleDetailPart->pPr->defRPr, 'baseline', 'integer');
+ if (isset($titleDetailPart->defRPr->rFont['val'])) {
+ $defaultFontName = (string) $titleDetailPart->defRPr->rFont['val'];
+ }
+ if (isset($titleDetailPart->pPr->defRPr->latin)) {
+ /** @var ?string */
+ $defaultLatin = self::getAttribute($titleDetailPart->pPr->defRPr->latin, 'typeface', 'string');
+ }
+ if (isset($titleDetailPart->pPr->defRPr->ea)) {
+ /** @var ?string */
+ $defaultEastAsian = self::getAttribute($titleDetailPart->pPr->defRPr->ea, 'typeface', 'string');
+ }
+ if (isset($titleDetailPart->pPr->defRPr->cs)) {
+ /** @var ?string */
+ $defaultComplexScript = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string');
+ }
+ if (isset($titleDetailPart->pPr->defRPr->solidFill)) {
+ $defaultFontColor = $this->readColor($titleDetailPart->pPr->defRPr->solidFill);
+ }
+ }
+ foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) {
+ if (
+ (string) $titleDetailElementKey !== 'r'
+ || !isset($titleDetailElement->t)
+ ) {
+ continue;
+ }
+ $objText = $value->createTextRun((string) $titleDetailElement->t);
+ if ($objText->getFont() === null) {
+ // @codeCoverageIgnoreStart
+ continue;
+ // @codeCoverageIgnoreEnd
+ }
+ $fontSize = null;
+ $bold = null;
+ $italic = null;
+ $underscore = null;
+ $strikethrough = null;
+ $baseline = null;
+ $fontName = null;
+ $latinName = null;
+ $eastAsian = null;
+ $complexScript = null;
+ $fontColor = null;
+ $underlineColor = null;
+ if (isset($titleDetailElement->rPr)) {
+ // not used now, not sure it ever was, grandfathering
+ if (isset($titleDetailElement->rPr->rFont['val'])) {
+ // @codeCoverageIgnoreStart
+ $fontName = (string) $titleDetailElement->rPr->rFont['val'];
+ // @codeCoverageIgnoreEnd
+ }
+ if (isset($titleDetailElement->rPr->latin)) {
+ /** @var ?string */
+ $latinName = self::getAttribute($titleDetailElement->rPr->latin, 'typeface', 'string');
+ }
+ if (isset($titleDetailElement->rPr->ea)) {
+ /** @var ?string */
+ $eastAsian = self::getAttribute($titleDetailElement->rPr->ea, 'typeface', 'string');
+ }
+ if (isset($titleDetailElement->rPr->cs)) {
+ /** @var ?string */
+ $complexScript = self::getAttribute($titleDetailElement->rPr->cs, 'typeface', 'string');
+ }
+ /** @var ?int */
+ $fontSize = self::getAttribute($titleDetailElement->rPr, 'sz', 'integer');
+
+ // not used now, not sure it ever was, grandfathering
+ if (isset($titleDetailElement->rPr->solidFill)) {
+ $fontColor = $this->readColor($titleDetailElement->rPr->solidFill);
+ }
+
+ /** @var ?bool */
+ $bold = self::getAttribute($titleDetailElement->rPr, 'b', 'boolean');
+
+ /** @var ?bool */
+ $italic = self::getAttribute($titleDetailElement->rPr, 'i', 'boolean');
+
+ /** @var ?int */
+ $baseline = self::getAttribute($titleDetailElement->rPr, 'baseline', 'integer');
+
+ /** @var ?string */
+ $underscore = self::getAttribute($titleDetailElement->rPr, 'u', 'string');
+ if (isset($titleDetailElement->rPr->uFill->solidFill)) {
+ $underlineColor = $this->readColor($titleDetailElement->rPr->uFill->solidFill);
+ }
+
+ /** @var ?string */
+ $strikethrough = self::getAttribute($titleDetailElement->rPr, 'strike', 'string');
+ }
+
+ $fontFound = false;
+ $latinName = $latinName ?? $defaultLatin;
+ if ($latinName !== null) {
+ $objText->getFont()->setLatin($latinName);
+ $fontFound = true;
+ }
+ $eastAsian = $eastAsian ?? $defaultEastAsian;
+ if ($eastAsian !== null) {
+ $objText->getFont()->setEastAsian($eastAsian);
+ $fontFound = true;
+ }
+ $complexScript = $complexScript ?? $defaultComplexScript;
+ if ($complexScript !== null) {
+ $objText->getFont()->setComplexScript($complexScript);
+ $fontFound = true;
+ }
+ $fontName = $fontName ?? $defaultFontName;
+ if ($fontName !== null) {
+ // @codeCoverageIgnoreStart
+ $objText->getFont()->setName($fontName);
+ $fontFound = true;
+ // @codeCoverageIgnoreEnd
+ }
+
+ $fontSize = $fontSize ?? $defaultFontSize;
+ if (is_int($fontSize)) {
+ $objText->getFont()->setSize(floor($fontSize / 100));
+ $fontFound = true;
+ } else {
+ $objText->getFont()->setSize(null, true);
+ }
+
+ $fontColor = $fontColor ?? $defaultFontColor;
+ if (!empty($fontColor)) {
+ $objText->getFont()->setChartColor($fontColor);
+ $fontFound = true;
+ }
+
+ $bold = $bold ?? $defaultBold;
+ if ($bold !== null) {
+ $objText->getFont()->setBold($bold);
+ $fontFound = true;
+ }
+
+ $italic = $italic ?? $defaultItalic;
+ if ($italic !== null) {
+ $objText->getFont()->setItalic($italic);
+ $fontFound = true;
+ }
+
+ $baseline = $baseline ?? $defaultBaseline;
+ if ($baseline !== null) {
+ $objText->getFont()->setBaseLine($baseline);
+ if ($baseline > 0) {
+ $objText->getFont()->setSuperscript(true);
+ } elseif ($baseline < 0) {
+ $objText->getFont()->setSubscript(true);
+ }
+ $fontFound = true;
+ }
+
+ $underscore = $underscore ?? $defaultUnderscore;
+ if ($underscore !== null) {
+ if ($underscore == 'sng') {
+ $objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
+ } elseif ($underscore == 'dbl') {
+ $objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE);
+ } elseif ($underscore !== '') {
+ $objText->getFont()->setUnderline($underscore);
+ } else {
+ $objText->getFont()->setUnderline(Font::UNDERLINE_NONE);
+ }
+ $fontFound = true;
+ if ($underlineColor) {
+ $objText->getFont()->setUnderlineColor($underlineColor);
+ }
+ }
+
+ $strikethrough = $strikethrough ?? $defaultStrikethrough;
+ if ($strikethrough !== null) {
+ $objText->getFont()->setStrikeType($strikethrough);
+ if ($strikethrough == 'noStrike') {
+ $objText->getFont()->setStrikethrough(false);
+ } else {
+ $objText->getFont()->setStrikethrough(true);
+ }
+ $fontFound = true;
+ }
+ if ($fontFound === false) {
+ $objText->setFont(null);
+ }
+ }
+
+ return $value;
+ }
+
+ private function parseFont(SimpleXMLElement $titleDetailPart): ?Font
+ {
+ if (!isset($titleDetailPart->pPr->defRPr)) {
+ return null;
+ }
+ $fontArray = [];
+ $fontArray['size'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer');
+ $fontArray['bold'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'b', 'boolean');
+ $fontArray['italic'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'i', 'boolean');
+ $fontArray['underscore'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'u', 'string');
+ $fontArray['strikethrough'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'strike', 'string');
+
+ if (isset($titleDetailPart->pPr->defRPr->latin)) {
+ $fontArray['latin'] = self::getAttribute($titleDetailPart->pPr->defRPr->latin, 'typeface', 'string');
+ }
+ if (isset($titleDetailPart->pPr->defRPr->ea)) {
+ $fontArray['eastAsian'] = self::getAttribute($titleDetailPart->pPr->defRPr->ea, 'typeface', 'string');
+ }
+ if (isset($titleDetailPart->pPr->defRPr->cs)) {
+ $fontArray['complexScript'] = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string');
+ }
+ if (isset($titleDetailPart->pPr->defRPr->solidFill)) {
+ $fontArray['chartColor'] = new ChartColor($this->readColor($titleDetailPart->pPr->defRPr->solidFill));
+ }
+ $font = new Font();
+ $font->setSize(null, true);
+ $font->applyFromArray($fontArray);
+
+ return $font;
+ }
+
+ /**
+ * @param ?SimpleXMLElement $chartDetail
+ */
+ private function readChartAttributes($chartDetail): array
+ {
+ $plotAttributes = [];
+ if (isset($chartDetail->dLbls)) {
+ if (isset($chartDetail->dLbls->dLblPos)) {
+ $plotAttributes['dLblPos'] = self::getAttribute($chartDetail->dLbls->dLblPos, 'val', 'string');
+ }
+ if (isset($chartDetail->dLbls->numFmt)) {
+ $plotAttributes['numFmtCode'] = self::getAttribute($chartDetail->dLbls->numFmt, 'formatCode', 'string');
+ $plotAttributes['numFmtLinked'] = self::getAttribute($chartDetail->dLbls->numFmt, 'sourceLinked', 'boolean');
+ }
+ if (isset($chartDetail->dLbls->showLegendKey)) {
+ $plotAttributes['showLegendKey'] = self::getAttribute($chartDetail->dLbls->showLegendKey, 'val', 'string');
+ }
+ if (isset($chartDetail->dLbls->showVal)) {
+ $plotAttributes['showVal'] = self::getAttribute($chartDetail->dLbls->showVal, 'val', 'string');
+ }
+ if (isset($chartDetail->dLbls->showCatName)) {
+ $plotAttributes['showCatName'] = self::getAttribute($chartDetail->dLbls->showCatName, 'val', 'string');
+ }
+ if (isset($chartDetail->dLbls->showSerName)) {
+ $plotAttributes['showSerName'] = self::getAttribute($chartDetail->dLbls->showSerName, 'val', 'string');
+ }
+ if (isset($chartDetail->dLbls->showPercent)) {
+ $plotAttributes['showPercent'] = self::getAttribute($chartDetail->dLbls->showPercent, 'val', 'string');
+ }
+ if (isset($chartDetail->dLbls->showBubbleSize)) {
+ $plotAttributes['showBubbleSize'] = self::getAttribute($chartDetail->dLbls->showBubbleSize, 'val', 'string');
+ }
+ if (isset($chartDetail->dLbls->showLeaderLines)) {
+ $plotAttributes['showLeaderLines'] = self::getAttribute($chartDetail->dLbls->showLeaderLines, 'val', 'string');
+ }
+ if (isset($chartDetail->dLbls->spPr)) {
+ $sppr = $chartDetail->dLbls->spPr->children($this->aNamespace);
+ if (isset($sppr->solidFill)) {
+ $plotAttributes['labelFillColor'] = new ChartColor($this->readColor($sppr->solidFill));
+ }
+ if (isset($sppr->ln->solidFill)) {
+ $plotAttributes['labelBorderColor'] = new ChartColor($this->readColor($sppr->ln->solidFill));
+ }
+ }
+ if (isset($chartDetail->dLbls->txPr)) {
+ $txpr = $chartDetail->dLbls->txPr->children($this->aNamespace);
+ if (isset($txpr->p)) {
+ $plotAttributes['labelFont'] = $this->parseFont($txpr->p);
+ if (isset($txpr->p->pPr->defRPr->effectLst)) {
+ $labelEffects = new GridLines();
+ $this->readEffects($txpr->p->pPr->defRPr, $labelEffects, false);
+ $plotAttributes['labelEffects'] = $labelEffects;
+ }
+ }
+ }
+ }
+
+ return $plotAttributes;
+ }
+
+ /**
+ * @param mixed $plotAttributes
+ */
+ private function setChartAttributes(Layout $plotArea, $plotAttributes): void
+ {
+ foreach ($plotAttributes as $plotAttributeKey => $plotAttributeValue) {
+ switch ($plotAttributeKey) {
+ case 'showLegendKey':
+ $plotArea->setShowLegendKey($plotAttributeValue);
+
+ break;
+ case 'showVal':
+ $plotArea->setShowVal($plotAttributeValue);
+
+ break;
+ case 'showCatName':
+ $plotArea->setShowCatName($plotAttributeValue);
+
+ break;
+ case 'showSerName':
+ $plotArea->setShowSerName($plotAttributeValue);
+
+ break;
+ case 'showPercent':
+ $plotArea->setShowPercent($plotAttributeValue);
+
+ break;
+ case 'showBubbleSize':
+ $plotArea->setShowBubbleSize($plotAttributeValue);
+
+ break;
+ case 'showLeaderLines':
+ $plotArea->setShowLeaderLines($plotAttributeValue);
+
+ break;
+ }
+ }
+ }
+
+ private function readEffects(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject, bool $getSppr = true): void
+ {
+ if (!isset($chartObject)) {
+ return;
+ }
+ if ($getSppr) {
+ if (!isset($chartDetail->spPr)) {
+ return;
+ }
+ $sppr = $chartDetail->spPr->children($this->aNamespace);
+ } else {
+ $sppr = $chartDetail;
+ }
+ if (isset($sppr->effectLst->glow)) {
+ $axisGlowSize = (float) self::getAttribute($sppr->effectLst->glow, 'rad', 'integer') / ChartProperties::POINTS_WIDTH_MULTIPLIER;
+ if ($axisGlowSize != 0.0) {
+ $colorArray = $this->readColor($sppr->effectLst->glow);
+ $chartObject->setGlowProperties($axisGlowSize, $colorArray['value'], $colorArray['alpha'], $colorArray['type']);
+ }
+ }
+
+ if (isset($sppr->effectLst->softEdge)) {
+ /** @var string */
+ $softEdgeSize = self::getAttribute($sppr->effectLst->softEdge, 'rad', 'string');
+ if (is_numeric($softEdgeSize)) {
+ $chartObject->setSoftEdges((float) ChartProperties::xmlToPoints($softEdgeSize));
+ }
+ }
+
+ $type = '';
+ foreach (self::SHADOW_TYPES as $shadowType) {
+ if (isset($sppr->effectLst->$shadowType)) {
+ $type = $shadowType;
+
+ break;
+ }
+ }
+ if ($type !== '') {
+ /** @var string */
+ $blur = self::getAttribute($sppr->effectLst->$type, 'blurRad', 'string');
+ $blur = is_numeric($blur) ? ChartProperties::xmlToPoints($blur) : null;
+ /** @var string */
+ $dist = self::getAttribute($sppr->effectLst->$type, 'dist', 'string');
+ $dist = is_numeric($dist) ? ChartProperties::xmlToPoints($dist) : null;
+ /** @var string */
+ $direction = self::getAttribute($sppr->effectLst->$type, 'dir', 'string');
+ $direction = is_numeric($direction) ? ChartProperties::xmlToAngle($direction) : null;
+ $algn = self::getAttribute($sppr->effectLst->$type, 'algn', 'string');
+ $rot = self::getAttribute($sppr->effectLst->$type, 'rotWithShape', 'string');
+ $size = [];
+ foreach (['sx', 'sy'] as $sizeType) {
+ $sizeValue = self::getAttribute($sppr->effectLst->$type, $sizeType, 'string');
+ if (is_numeric($sizeValue)) {
+ $size[$sizeType] = ChartProperties::xmlToTenthOfPercent((string) $sizeValue);
+ } else {
+ $size[$sizeType] = null;
+ }
+ }
+ foreach (['kx', 'ky'] as $sizeType) {
+ $sizeValue = self::getAttribute($sppr->effectLst->$type, $sizeType, 'string');
+ if (is_numeric($sizeValue)) {
+ $size[$sizeType] = ChartProperties::xmlToAngle((string) $sizeValue);
+ } else {
+ $size[$sizeType] = null;
+ }
+ }
+ $colorArray = $this->readColor($sppr->effectLst->$type);
+ $chartObject
+ ->setShadowProperty('effect', $type)
+ ->setShadowProperty('blur', $blur)
+ ->setShadowProperty('direction', $direction)
+ ->setShadowProperty('distance', $dist)
+ ->setShadowProperty('algn', $algn)
+ ->setShadowProperty('rotWithShape', $rot)
+ ->setShadowProperty('size', $size)
+ ->setShadowProperty('color', $colorArray);
+ }
+ }
+
+ private const SHADOW_TYPES = [
+ 'outerShdw',
+ 'innerShdw',
+ ];
+
+ private function readColor(SimpleXMLElement $colorXml): array
+ {
+ $result = [
+ 'type' => null,
+ 'value' => null,
+ 'alpha' => null,
+ 'brightness' => null,
+ ];
+ foreach (ChartColor::EXCEL_COLOR_TYPES as $type) {
+ if (isset($colorXml->$type)) {
+ $result['type'] = $type;
+ $result['value'] = self::getAttribute($colorXml->$type, 'val', 'string');
+ if (isset($colorXml->$type->alpha)) {
+ /** @var string */
+ $alpha = self::getAttribute($colorXml->$type->alpha, 'val', 'string');
+ if (is_numeric($alpha)) {
+ $result['alpha'] = ChartColor::alphaFromXml($alpha);
+ }
+ }
+ if (isset($colorXml->$type->lumMod)) {
+ /** @var string */
+ $brightness = self::getAttribute($colorXml->$type->lumMod, 'val', 'string');
+ if (is_numeric($brightness)) {
+ $result['brightness'] = ChartColor::alphaFromXml($brightness);
+ }
+ }
+
+ break;
+ }
+ }
+
+ return $result;
+ }
+
+ private function readLineStyle(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject): void
+ {
+ if (!isset($chartObject, $chartDetail->spPr)) {
+ return;
+ }
+ $sppr = $chartDetail->spPr->children($this->aNamespace);
+
+ if (!isset($sppr->ln)) {
+ return;
+ }
+ $lineWidth = null;
+ /** @var string */
+ $lineWidthTemp = self::getAttribute($sppr->ln, 'w', 'string');
+ if (is_numeric($lineWidthTemp)) {
+ $lineWidth = ChartProperties::xmlToPoints($lineWidthTemp);
+ }
+ /** @var string */
+ $compoundType = self::getAttribute($sppr->ln, 'cmpd', 'string');
+ /** @var string */
+ $dashType = self::getAttribute($sppr->ln->prstDash, 'val', 'string');
+ /** @var string */
+ $capType = self::getAttribute($sppr->ln, 'cap', 'string');
+ if (isset($sppr->ln->miter)) {
+ $joinType = ChartProperties::LINE_STYLE_JOIN_MITER;
+ } elseif (isset($sppr->ln->bevel)) {
+ $joinType = ChartProperties::LINE_STYLE_JOIN_BEVEL;
+ } else {
+ $joinType = '';
+ }
+ $headArrowSize = '';
+ $endArrowSize = '';
+ /** @var string */
+ $headArrowType = self::getAttribute($sppr->ln->headEnd, 'type', 'string');
+ /** @var string */
+ $headArrowWidth = self::getAttribute($sppr->ln->headEnd, 'w', 'string');
+ /** @var string */
+ $headArrowLength = self::getAttribute($sppr->ln->headEnd, 'len', 'string');
+ /** @var string */
+ $endArrowType = self::getAttribute($sppr->ln->tailEnd, 'type', 'string');
+ /** @var string */
+ $endArrowWidth = self::getAttribute($sppr->ln->tailEnd, 'w', 'string');
+ /** @var string */
+ $endArrowLength = self::getAttribute($sppr->ln->tailEnd, 'len', 'string');
+ $chartObject->setLineStyleProperties(
+ $lineWidth,
+ $compoundType,
+ $dashType,
+ $capType,
+ $joinType,
+ $headArrowType,
+ $headArrowSize,
+ $endArrowType,
+ $endArrowSize,
+ $headArrowWidth,
+ $headArrowLength,
+ $endArrowWidth,
+ $endArrowLength
+ );
+ $colorArray = $this->readColor($sppr->ln->solidFill);
+ $chartObject->getLineColor()->setColorPropertiesArray($colorArray);
+ }
+
+ private function setAxisProperties(SimpleXMLElement $chartDetail, ?Axis $whichAxis): void
+ {
+ if (!isset($whichAxis)) {
+ return;
+ }
+ if (isset($chartDetail->delete)) {
+ $whichAxis->setAxisOption('hidden', (string) self::getAttribute($chartDetail->delete, 'val', 'string'));
+ }
+ if (isset($chartDetail->numFmt)) {
+ $whichAxis->setAxisNumberProperties(
+ (string) self::getAttribute($chartDetail->numFmt, 'formatCode', 'string'),
+ null,
+ (int) self::getAttribute($chartDetail->numFmt, 'sourceLinked', 'int')
+ );
+ }
+ if (isset($chartDetail->crossBetween)) {
+ $whichAxis->setCrossBetween((string) self::getAttribute($chartDetail->crossBetween, 'val', 'string'));
+ }
+ if (isset($chartDetail->majorTickMark)) {
+ $whichAxis->setAxisOption('major_tick_mark', (string) self::getAttribute($chartDetail->majorTickMark, 'val', 'string'));
+ }
+ if (isset($chartDetail->minorTickMark)) {
+ $whichAxis->setAxisOption('minor_tick_mark', (string) self::getAttribute($chartDetail->minorTickMark, 'val', 'string'));
+ }
+ if (isset($chartDetail->tickLblPos)) {
+ $whichAxis->setAxisOption('axis_labels', (string) self::getAttribute($chartDetail->tickLblPos, 'val', 'string'));
+ }
+ if (isset($chartDetail->crosses)) {
+ $whichAxis->setAxisOption('horizontal_crosses', (string) self::getAttribute($chartDetail->crosses, 'val', 'string'));
+ }
+ if (isset($chartDetail->crossesAt)) {
+ $whichAxis->setAxisOption('horizontal_crosses_value', (string) self::getAttribute($chartDetail->crossesAt, 'val', 'string'));
+ }
+ if (isset($chartDetail->scaling->orientation)) {
+ $whichAxis->setAxisOption('orientation', (string) self::getAttribute($chartDetail->scaling->orientation, 'val', 'string'));
+ }
+ if (isset($chartDetail->scaling->max)) {
+ $whichAxis->setAxisOption('maximum', (string) self::getAttribute($chartDetail->scaling->max, 'val', 'string'));
+ }
+ if (isset($chartDetail->scaling->min)) {
+ $whichAxis->setAxisOption('minimum', (string) self::getAttribute($chartDetail->scaling->min, 'val', 'string'));
+ }
+ if (isset($chartDetail->scaling->min)) {
+ $whichAxis->setAxisOption('minimum', (string) self::getAttribute($chartDetail->scaling->min, 'val', 'string'));
+ }
+ if (isset($chartDetail->majorUnit)) {
+ $whichAxis->setAxisOption('major_unit', (string) self::getAttribute($chartDetail->majorUnit, 'val', 'string'));
+ }
+ if (isset($chartDetail->minorUnit)) {
+ $whichAxis->setAxisOption('minor_unit', (string) self::getAttribute($chartDetail->minorUnit, 'val', 'string'));
+ }
+ if (isset($chartDetail->baseTimeUnit)) {
+ $whichAxis->setAxisOption('baseTimeUnit', (string) self::getAttribute($chartDetail->baseTimeUnit, 'val', 'string'));
+ }
+ if (isset($chartDetail->majorTimeUnit)) {
+ $whichAxis->setAxisOption('majorTimeUnit', (string) self::getAttribute($chartDetail->majorTimeUnit, 'val', 'string'));
+ }
+ if (isset($chartDetail->minorTimeUnit)) {
+ $whichAxis->setAxisOption('minorTimeUnit', (string) self::getAttribute($chartDetail->minorTimeUnit, 'val', 'string'));
+ }
+ if (isset($chartDetail->txPr)) {
+ $children = $chartDetail->txPr->children($this->aNamespace);
+ $addAxisText = false;
+ $axisText = new AxisText();
+ if (isset($children->bodyPr)) {
+ /** @var string */
+ $textRotation = self::getAttribute($children->bodyPr, 'rot', 'string');
+ if (is_numeric($textRotation)) {
+ $axisText->setRotation((int) ChartProperties::xmlToAngle($textRotation));
+ $addAxisText = true;
+ }
+ }
+ if (isset($children->p->pPr->defRPr)) {
+ $font = $this->parseFont($children->p);
+ if ($font !== null) {
+ $axisText->setFont($font);
+ $addAxisText = true;
+ }
+ }
+ if (isset($children->p->pPr->defRPr->effectLst)) {
+ $this->readEffects($children->p->pPr->defRPr, $axisText, false);
+ $addAxisText = true;
+ }
+ if ($addAxisText) {
+ $whichAxis->setAxisText($axisText);
+ }
+ }
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php
new file mode 100644
index 000000000..2b14eab7c
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php
@@ -0,0 +1,223 @@
+worksheet = $workSheet;
+ $this->worksheetXml = $worksheetXml;
+ }
+
+ /**
+ * Set Worksheet column attributes by attributes array passed.
+ *
+ * @param string $columnAddress A, B, ... DX, ...
+ * @param array $columnAttributes array of attributes (indexes are attribute name, values are value)
+ * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'width', ... ?
+ */
+ private function setColumnAttributes($columnAddress, array $columnAttributes): void
+ {
+ if (isset($columnAttributes['xfIndex'])) {
+ $this->worksheet->getColumnDimension($columnAddress)->setXfIndex($columnAttributes['xfIndex']);
+ }
+ if (isset($columnAttributes['visible'])) {
+ $this->worksheet->getColumnDimension($columnAddress)->setVisible($columnAttributes['visible']);
+ }
+ if (isset($columnAttributes['collapsed'])) {
+ $this->worksheet->getColumnDimension($columnAddress)->setCollapsed($columnAttributes['collapsed']);
+ }
+ if (isset($columnAttributes['outlineLevel'])) {
+ $this->worksheet->getColumnDimension($columnAddress)->setOutlineLevel($columnAttributes['outlineLevel']);
+ }
+ if (isset($columnAttributes['width'])) {
+ $this->worksheet->getColumnDimension($columnAddress)->setWidth($columnAttributes['width']);
+ }
+ }
+
+ /**
+ * Set Worksheet row attributes by attributes array passed.
+ *
+ * @param int $rowNumber 1, 2, 3, ... 99, ...
+ * @param array $rowAttributes array of attributes (indexes are attribute name, values are value)
+ * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ?
+ */
+ private function setRowAttributes($rowNumber, array $rowAttributes): void
+ {
+ if (isset($rowAttributes['xfIndex'])) {
+ $this->worksheet->getRowDimension($rowNumber)->setXfIndex($rowAttributes['xfIndex']);
+ }
+ if (isset($rowAttributes['visible'])) {
+ $this->worksheet->getRowDimension($rowNumber)->setVisible($rowAttributes['visible']);
+ }
+ if (isset($rowAttributes['collapsed'])) {
+ $this->worksheet->getRowDimension($rowNumber)->setCollapsed($rowAttributes['collapsed']);
+ }
+ if (isset($rowAttributes['outlineLevel'])) {
+ $this->worksheet->getRowDimension($rowNumber)->setOutlineLevel($rowAttributes['outlineLevel']);
+ }
+ if (isset($rowAttributes['rowHeight'])) {
+ $this->worksheet->getRowDimension($rowNumber)->setRowHeight($rowAttributes['rowHeight']);
+ }
+ }
+
+ public function load(?IReadFilter $readFilter = null, bool $readDataOnly = false): void
+ {
+ if ($this->worksheetXml === null) {
+ return;
+ }
+
+ $columnsAttributes = [];
+ $rowsAttributes = [];
+ if (isset($this->worksheetXml->cols)) {
+ $columnsAttributes = $this->readColumnAttributes($this->worksheetXml->cols, $readDataOnly);
+ }
+
+ if ($this->worksheetXml->sheetData && $this->worksheetXml->sheetData->row) {
+ $rowsAttributes = $this->readRowAttributes($this->worksheetXml->sheetData->row, $readDataOnly);
+ }
+
+ if ($readFilter !== null && get_class($readFilter) === DefaultReadFilter::class) {
+ $readFilter = null;
+ }
+
+ // set columns/rows attributes
+ $columnsAttributesAreSet = [];
+ foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) {
+ if (
+ $readFilter === null ||
+ !$this->isFilteredColumn($readFilter, $columnCoordinate, $rowsAttributes)
+ ) {
+ if (!isset($columnsAttributesAreSet[$columnCoordinate])) {
+ $this->setColumnAttributes($columnCoordinate, $columnAttributes);
+ $columnsAttributesAreSet[$columnCoordinate] = true;
+ }
+ }
+ }
+
+ $rowsAttributesAreSet = [];
+ foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) {
+ if (
+ $readFilter === null ||
+ !$this->isFilteredRow($readFilter, $rowCoordinate, $columnsAttributes)
+ ) {
+ if (!isset($rowsAttributesAreSet[$rowCoordinate])) {
+ $this->setRowAttributes($rowCoordinate, $rowAttributes);
+ $rowsAttributesAreSet[$rowCoordinate] = true;
+ }
+ }
+ }
+ }
+
+ private function isFilteredColumn(IReadFilter $readFilter, string $columnCoordinate, array $rowsAttributes): bool
+ {
+ foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) {
+ if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private function readColumnAttributes(SimpleXMLElement $worksheetCols, bool $readDataOnly): array
+ {
+ $columnAttributes = [];
+
+ foreach ($worksheetCols->col as $columnx) {
+ /** @scrutinizer ignore-call */
+ $column = $columnx->attributes();
+ if ($column !== null) {
+ $startColumn = Coordinate::stringFromColumnIndex((int) $column['min']);
+ $endColumn = Coordinate::stringFromColumnIndex((int) $column['max']);
+ ++$endColumn;
+ for ($columnAddress = $startColumn; $columnAddress !== $endColumn; ++$columnAddress) {
+ $columnAttributes[$columnAddress] = $this->readColumnRangeAttributes($column, $readDataOnly);
+
+ if ((int) ($column['max']) == 16384) {
+ break;
+ }
+ }
+ }
+ }
+
+ return $columnAttributes;
+ }
+
+ private function readColumnRangeAttributes(?SimpleXMLElement $column, bool $readDataOnly): array
+ {
+ $columnAttributes = [];
+ if ($column !== null) {
+ if (isset($column['style']) && !$readDataOnly) {
+ $columnAttributes['xfIndex'] = (int) $column['style'];
+ }
+ if (isset($column['hidden']) && self::boolean($column['hidden'])) {
+ $columnAttributes['visible'] = false;
+ }
+ if (isset($column['collapsed']) && self::boolean($column['collapsed'])) {
+ $columnAttributes['collapsed'] = true;
+ }
+ if (isset($column['outlineLevel']) && ((int) $column['outlineLevel']) > 0) {
+ $columnAttributes['outlineLevel'] = (int) $column['outlineLevel'];
+ }
+ if (isset($column['width'])) {
+ $columnAttributes['width'] = (float) $column['width'];
+ }
+ }
+
+ return $columnAttributes;
+ }
+
+ private function isFilteredRow(IReadFilter $readFilter, int $rowCoordinate, array $columnsAttributes): bool
+ {
+ foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) {
+ if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private function readRowAttributes(SimpleXMLElement $worksheetRow, bool $readDataOnly): array
+ {
+ $rowAttributes = [];
+
+ foreach ($worksheetRow as $rowx) {
+ /** @scrutinizer ignore-call */
+ $row = $rowx->attributes();
+ if ($row !== null) {
+ if (isset($row['ht']) && !$readDataOnly) {
+ $rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht'];
+ }
+ if (isset($row['hidden']) && self::boolean($row['hidden'])) {
+ $rowAttributes[(int) $row['r']]['visible'] = false;
+ }
+ if (isset($row['collapsed']) && self::boolean($row['collapsed'])) {
+ $rowAttributes[(int) $row['r']]['collapsed'] = true;
+ }
+ if (isset($row['outlineLevel']) && (int) $row['outlineLevel'] > 0) {
+ $rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel'];
+ }
+ if (isset($row['s']) && !$readDataOnly) {
+ $rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s'];
+ }
+ }
+ }
+
+ return $rowAttributes;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php
new file mode 100644
index 000000000..59bf5b84e
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php
@@ -0,0 +1,291 @@
+worksheet = $workSheet;
+ $this->worksheetXml = $worksheetXml;
+ $this->dxfs = $dxfs;
+ }
+
+ public function load(): void
+ {
+ $selectedCells = $this->worksheet->getSelectedCells();
+
+ $this->setConditionalStyles(
+ $this->worksheet,
+ $this->readConditionalStyles($this->worksheetXml),
+ $this->worksheetXml->extLst
+ );
+
+ $this->worksheet->setSelectedCells($selectedCells);
+ }
+
+ public function loadFromExt(StyleReader $styleReader): void
+ {
+ $selectedCells = $this->worksheet->getSelectedCells();
+
+ $this->ns = $this->worksheetXml->getNamespaces(true);
+ $this->setConditionalsFromExt(
+ $this->readConditionalsFromExt($this->worksheetXml->extLst, $styleReader)
+ );
+
+ $this->worksheet->setSelectedCells($selectedCells);
+ }
+
+ private function setConditionalsFromExt(array $conditionals): void
+ {
+ foreach ($conditionals as $conditionalRange => $cfRules) {
+ ksort($cfRules);
+ // Priority is used as the key for sorting; but may not start at 0,
+ // so we use array_values to reset the index after sorting.
+ $this->worksheet->getStyle($conditionalRange)
+ ->setConditionalStyles(array_values($cfRules));
+ }
+ }
+
+ private function readConditionalsFromExt(SimpleXMLElement $extLst, StyleReader $styleReader): array
+ {
+ $conditionals = [];
+
+ if (isset($extLst->ext['uri']) && (string) $extLst->ext['uri'] === '{78C0D931-6437-407d-A8EE-F0AAD7539E65}') {
+ $conditionalFormattingRuleXml = $extLst->ext->children($this->ns['x14']);
+ if (!$conditionalFormattingRuleXml->conditionalFormattings) {
+ return [];
+ }
+
+ foreach ($conditionalFormattingRuleXml->children($this->ns['x14']) as $extFormattingXml) {
+ $extFormattingRangeXml = $extFormattingXml->children($this->ns['xm']);
+ if (!$extFormattingRangeXml->sqref) {
+ continue;
+ }
+
+ $sqref = (string) $extFormattingRangeXml->sqref;
+ $extCfRuleXml = $extFormattingXml->cfRule;
+
+ $attributes = $extCfRuleXml->attributes();
+ if (!$attributes) {
+ continue;
+ }
+ $conditionType = (string) $attributes->type;
+ if (
+ !Conditional::isValidConditionType($conditionType) ||
+ $conditionType === Conditional::CONDITION_DATABAR
+ ) {
+ continue;
+ }
+
+ $priority = (int) $attributes->priority;
+
+ $conditional = $this->readConditionalRuleFromExt($extCfRuleXml, $attributes);
+ $cfStyle = $this->readStyleFromExt($extCfRuleXml, $styleReader);
+ $conditional->setStyle($cfStyle);
+ $conditionals[$sqref][$priority] = $conditional;
+ }
+ }
+
+ return $conditionals;
+ }
+
+ private function readConditionalRuleFromExt(SimpleXMLElement $cfRuleXml, SimpleXMLElement $attributes): Conditional
+ {
+ $conditionType = (string) $attributes->type;
+ $operatorType = (string) $attributes->operator;
+
+ $operands = [];
+ foreach ($cfRuleXml->children($this->ns['xm']) as $cfRuleOperandsXml) {
+ $operands[] = (string) $cfRuleOperandsXml;
+ }
+
+ $conditional = new Conditional();
+ $conditional->setConditionType($conditionType);
+ $conditional->setOperatorType($operatorType);
+ if (
+ $conditionType === Conditional::CONDITION_CONTAINSTEXT ||
+ $conditionType === Conditional::CONDITION_NOTCONTAINSTEXT ||
+ $conditionType === Conditional::CONDITION_BEGINSWITH ||
+ $conditionType === Conditional::CONDITION_ENDSWITH ||
+ $conditionType === Conditional::CONDITION_TIMEPERIOD
+ ) {
+ $conditional->setText(array_pop($operands) ?? '');
+ }
+ $conditional->setConditions($operands);
+
+ return $conditional;
+ }
+
+ private function readStyleFromExt(SimpleXMLElement $extCfRuleXml, StyleReader $styleReader): Style
+ {
+ $cfStyle = new Style(false, true);
+ if ($extCfRuleXml->dxf) {
+ $styleXML = $extCfRuleXml->dxf->children();
+
+ if ($styleXML->borders) {
+ $styleReader->readBorderStyle($cfStyle->getBorders(), $styleXML->borders);
+ }
+ if ($styleXML->fill) {
+ $styleReader->readFillStyle($cfStyle->getFill(), $styleXML->fill);
+ }
+ }
+
+ return $cfStyle;
+ }
+
+ private function readConditionalStyles(SimpleXMLElement $xmlSheet): array
+ {
+ $conditionals = [];
+ foreach ($xmlSheet->conditionalFormatting as $conditional) {
+ foreach ($conditional->cfRule as $cfRule) {
+ if (Conditional::isValidConditionType((string) $cfRule['type']) && (!isset($cfRule['dxfId']) || isset($this->dxfs[(int) ($cfRule['dxfId'])]))) {
+ $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule;
+ } elseif ((string) $cfRule['type'] == Conditional::CONDITION_DATABAR) {
+ $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule;
+ }
+ }
+ }
+
+ return $conditionals;
+ }
+
+ private function setConditionalStyles(Worksheet $worksheet, array $conditionals, SimpleXMLElement $xmlExtLst): void
+ {
+ foreach ($conditionals as $cellRangeReference => $cfRules) {
+ ksort($cfRules);
+ $conditionalStyles = $this->readStyleRules($cfRules, $xmlExtLst);
+
+ // Extract all cell references in $cellRangeReference
+ $cellBlocks = explode(' ', str_replace('$', '', strtoupper($cellRangeReference)));
+ foreach ($cellBlocks as $cellBlock) {
+ $worksheet->getStyle($cellBlock)->setConditionalStyles($conditionalStyles);
+ }
+ }
+ }
+
+ private function readStyleRules(array $cfRules, SimpleXMLElement $extLst): array
+ {
+ $conditionalFormattingRuleExtensions = ConditionalFormattingRuleExtension::parseExtLstXml($extLst);
+ $conditionalStyles = [];
+
+ foreach ($cfRules as $cfRule) {
+ $objConditional = new Conditional();
+ $objConditional->setConditionType((string) $cfRule['type']);
+ $objConditional->setOperatorType((string) $cfRule['operator']);
+ $objConditional->setNoFormatSet(!isset($cfRule['dxfId']));
+
+ if ((string) $cfRule['text'] != '') {
+ $objConditional->setText((string) $cfRule['text']);
+ } elseif ((string) $cfRule['timePeriod'] != '') {
+ $objConditional->setText((string) $cfRule['timePeriod']);
+ }
+
+ if (isset($cfRule['stopIfTrue']) && (int) $cfRule['stopIfTrue'] === 1) {
+ $objConditional->setStopIfTrue(true);
+ }
+
+ if (count($cfRule->formula) >= 1) {
+ foreach ($cfRule->formula as $formulax) {
+ $formula = (string) $formulax;
+ if ($formula === 'TRUE') {
+ $objConditional->addCondition(true);
+ } elseif ($formula === 'FALSE') {
+ $objConditional->addCondition(false);
+ } else {
+ $objConditional->addCondition($formula);
+ }
+ }
+ } else {
+ $objConditional->addCondition('');
+ }
+
+ if (isset($cfRule->dataBar)) {
+ $objConditional->setDataBar(
+ $this->readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions) // @phpstan-ignore-line
+ );
+ } elseif (isset($cfRule['dxfId'])) {
+ $objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]);
+ }
+
+ $conditionalStyles[] = $objConditional;
+ }
+
+ return $conditionalStyles;
+ }
+
+ /**
+ * @param SimpleXMLElement|stdClass $cfRule
+ */
+ private function readDataBarOfConditionalRule($cfRule, array $conditionalFormattingRuleExtensions): ConditionalDataBar
+ {
+ $dataBar = new ConditionalDataBar();
+ //dataBar attribute
+ if (isset($cfRule->dataBar['showValue'])) {
+ $dataBar->setShowValue((bool) $cfRule->dataBar['showValue']);
+ }
+
+ //dataBar children
+ //conditionalFormatValueObjects
+ $cfvoXml = $cfRule->dataBar->cfvo;
+ $cfvoIndex = 0;
+ foreach ((count($cfvoXml) > 1 ? $cfvoXml : [$cfvoXml]) as $cfvo) {
+ if ($cfvoIndex === 0) {
+ $dataBar->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val']));
+ }
+ if ($cfvoIndex === 1) {
+ $dataBar->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val']));
+ }
+ ++$cfvoIndex;
+ }
+
+ //color
+ if (isset($cfRule->dataBar->color)) {
+ $dataBar->setColor((string) $cfRule->dataBar->color['rgb']);
+ }
+ //extLst
+ $this->readDataBarExtLstOfConditionalRule($dataBar, $cfRule, $conditionalFormattingRuleExtensions);
+
+ return $dataBar;
+ }
+
+ /**
+ * @param SimpleXMLElement|stdClass $cfRule
+ */
+ private function readDataBarExtLstOfConditionalRule(ConditionalDataBar $dataBar, $cfRule, array $conditionalFormattingRuleExtensions): void
+ {
+ if (isset($cfRule->extLst)) {
+ $ns = $cfRule->extLst->getNamespaces(true);
+ foreach ((count($cfRule->extLst) > 0 ? $cfRule->extLst->ext : [$cfRule->extLst->ext]) as $ext) {
+ $extId = (string) $ext->children($ns['x14'])->id;
+ if (isset($conditionalFormattingRuleExtensions[$extId]) && (string) $ext['uri'] === '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}') {
+ $dataBar->setConditionalFormattingRuleExt($conditionalFormattingRuleExtensions[$extId]);
+ }
+ }
+ }
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php
new file mode 100644
index 000000000..210c322f9
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php
@@ -0,0 +1,67 @@
+worksheet = $workSheet;
+ $this->worksheetXml = $worksheetXml;
+ }
+
+ public function load(): void
+ {
+ foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) {
+ // Uppercase coordinate
+ $range = strtoupper((string) $dataValidation['sqref']);
+ $rangeSet = explode(' ', $range);
+ foreach ($rangeSet as $range) {
+ if (preg_match('/^[A-Z]{1,3}\\d{1,7}/', $range, $matches) === 1) {
+ // Ensure left/top row of range exists, thereby
+ // adjusting high row/column.
+ $this->worksheet->getCell($matches[0]);
+ }
+ }
+ }
+ foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) {
+ // Uppercase coordinate
+ $range = strtoupper((string) $dataValidation['sqref']);
+ $rangeSet = explode(' ', $range);
+ foreach ($rangeSet as $range) {
+ $stRange = $this->worksheet->shrinkRangeToFit($range);
+
+ // Extract all cell references in $range
+ foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $reference) {
+ // Create validation
+ $docValidation = $this->worksheet->getCell($reference)->getDataValidation();
+ $docValidation->setType((string) $dataValidation['type']);
+ $docValidation->setErrorStyle((string) $dataValidation['errorStyle']);
+ $docValidation->setOperator((string) $dataValidation['operator']);
+ $docValidation->setAllowBlank(filter_var($dataValidation['allowBlank'], FILTER_VALIDATE_BOOLEAN));
+ // showDropDown is inverted (works as hideDropDown if true)
+ $docValidation->setShowDropDown(!filter_var($dataValidation['showDropDown'], FILTER_VALIDATE_BOOLEAN));
+ $docValidation->setShowInputMessage(filter_var($dataValidation['showInputMessage'], FILTER_VALIDATE_BOOLEAN));
+ $docValidation->setShowErrorMessage(filter_var($dataValidation['showErrorMessage'], FILTER_VALIDATE_BOOLEAN));
+ $docValidation->setErrorTitle((string) $dataValidation['errorTitle']);
+ $docValidation->setError((string) $dataValidation['error']);
+ $docValidation->setPromptTitle((string) $dataValidation['promptTitle']);
+ $docValidation->setPrompt((string) $dataValidation['prompt']);
+ $docValidation->setFormula1((string) $dataValidation->formula1);
+ $docValidation->setFormula2((string) $dataValidation->formula2);
+ $docValidation->setSqref($range);
+ }
+ }
+ }
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php
new file mode 100644
index 000000000..7d48c7967
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php
@@ -0,0 +1,66 @@
+worksheet = $workSheet;
+ }
+
+ public function readHyperlinks(SimpleXMLElement $relsWorksheet): void
+ {
+ foreach ($relsWorksheet->children(Namespaces::RELATIONSHIPS)->Relationship as $elementx) {
+ $element = Xlsx::getAttributes($elementx);
+ if ($element->Type == Namespaces::HYPERLINK) {
+ $this->hyperlinks[(string) $element->Id] = (string) $element->Target;
+ }
+ }
+ }
+
+ public function setHyperlinks(SimpleXMLElement $worksheetXml): void
+ {
+ foreach ($worksheetXml->children(Namespaces::MAIN)->hyperlink as $hyperlink) {
+ if ($hyperlink !== null) {
+ $this->setHyperlink($hyperlink, $this->worksheet);
+ }
+ }
+ }
+
+ private function setHyperlink(SimpleXMLElement $hyperlink, Worksheet $worksheet): void
+ {
+ // Link url
+ $linkRel = Xlsx::getAttributes($hyperlink, Namespaces::SCHEMA_OFFICE_DOCUMENT);
+
+ $attributes = Xlsx::getAttributes($hyperlink);
+ foreach (Coordinate::extractAllCellReferencesInRange($attributes->ref) as $cellReference) {
+ $cell = $worksheet->getCell($cellReference);
+ if (isset($linkRel['id'])) {
+ $hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']] ?? null;
+ if (isset($attributes['location'])) {
+ $hyperlinkUrl .= '#' . (string) $attributes['location'];
+ }
+ $cell->getHyperlink()->setUrl($hyperlinkUrl);
+ } elseif (isset($attributes['location'])) {
+ $cell->getHyperlink()->setUrl('sheet://' . (string) $attributes['location']);
+ }
+
+ // Tooltip
+ if (isset($attributes['tooltip'])) {
+ $cell->getHyperlink()->setTooltip((string) $attributes['tooltip']);
+ }
+ }
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php
new file mode 100644
index 000000000..fa3e57e7b
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php
@@ -0,0 +1,118 @@
+worksheet = $workSheet;
+ $this->worksheetXml = $worksheetXml;
+ }
+
+ public function load(array $unparsedLoadedData): array
+ {
+ $worksheetXml = $this->worksheetXml;
+ if ($worksheetXml === null) {
+ return $unparsedLoadedData;
+ }
+
+ $this->margins($worksheetXml, $this->worksheet);
+ $unparsedLoadedData = $this->pageSetup($worksheetXml, $this->worksheet, $unparsedLoadedData);
+ $this->headerFooter($worksheetXml, $this->worksheet);
+ $this->pageBreaks($worksheetXml, $this->worksheet);
+
+ return $unparsedLoadedData;
+ }
+
+ private function margins(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ if ($xmlSheet->pageMargins) {
+ $docPageMargins = $worksheet->getPageMargins();
+ $docPageMargins->setLeft((float) ($xmlSheet->pageMargins['left']));
+ $docPageMargins->setRight((float) ($xmlSheet->pageMargins['right']));
+ $docPageMargins->setTop((float) ($xmlSheet->pageMargins['top']));
+ $docPageMargins->setBottom((float) ($xmlSheet->pageMargins['bottom']));
+ $docPageMargins->setHeader((float) ($xmlSheet->pageMargins['header']));
+ $docPageMargins->setFooter((float) ($xmlSheet->pageMargins['footer']));
+ }
+ }
+
+ private function pageSetup(SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData): array
+ {
+ if ($xmlSheet->pageSetup) {
+ $docPageSetup = $worksheet->getPageSetup();
+
+ if (isset($xmlSheet->pageSetup['orientation'])) {
+ $docPageSetup->setOrientation((string) $xmlSheet->pageSetup['orientation']);
+ }
+ if (isset($xmlSheet->pageSetup['paperSize'])) {
+ $docPageSetup->setPaperSize((int) ($xmlSheet->pageSetup['paperSize']));
+ }
+ if (isset($xmlSheet->pageSetup['scale'])) {
+ $docPageSetup->setScale((int) ($xmlSheet->pageSetup['scale']), false);
+ }
+ if (isset($xmlSheet->pageSetup['fitToHeight']) && (int) ($xmlSheet->pageSetup['fitToHeight']) >= 0) {
+ $docPageSetup->setFitToHeight((int) ($xmlSheet->pageSetup['fitToHeight']), false);
+ }
+ if (isset($xmlSheet->pageSetup['fitToWidth']) && (int) ($xmlSheet->pageSetup['fitToWidth']) >= 0) {
+ $docPageSetup->setFitToWidth((int) ($xmlSheet->pageSetup['fitToWidth']), false);
+ }
+ if (
+ isset($xmlSheet->pageSetup['firstPageNumber'], $xmlSheet->pageSetup['useFirstPageNumber']) &&
+ self::boolean((string) $xmlSheet->pageSetup['useFirstPageNumber'])
+ ) {
+ $docPageSetup->setFirstPageNumber((int) ($xmlSheet->pageSetup['firstPageNumber']));
+ }
+ if (isset($xmlSheet->pageSetup['pageOrder'])) {
+ $docPageSetup->setPageOrder((string) $xmlSheet->pageSetup['pageOrder']);
+ }
+
+ $relAttributes = $xmlSheet->pageSetup->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT);
+ if (isset($relAttributes['id'])) {
+ $relid = (string) $relAttributes['id'];
+ if (substr($relid, -2) !== 'ps') {
+ $relid .= 'ps';
+ }
+ $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = $relid;
+ }
+ }
+
+ return $unparsedLoadedData;
+ }
+
+ private function headerFooter(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ if ($xmlSheet->headerFooter) {
+ $docHeaderFooter = $worksheet->getHeaderFooter();
+
+ if (
+ isset($xmlSheet->headerFooter['differentOddEven']) &&
+ self::boolean((string) $xmlSheet->headerFooter['differentOddEven'])
+ ) {
+ $docHeaderFooter->setDifferentOddEven(true);
+ } else {
+ $docHeaderFooter->setDifferentOddEven(false);
+ }
+ if (
+ isset($xmlSheet->headerFooter['differentFirst']) &&
+ self::boolean((string) $xmlSheet->headerFooter['differentFirst'])
+ ) {
+ $docHeaderFooter->setDifferentFirst(true);
+ } else {
+ $docHeaderFooter->setDifferentFirst(false);
+ }
+ if (
+ isset($xmlSheet->headerFooter['scaleWithDoc']) &&
+ !self::boolean((string) $xmlSheet->headerFooter['scaleWithDoc'])
+ ) {
+ $docHeaderFooter->setScaleWithDocument(false);
+ } else {
+ $docHeaderFooter->setScaleWithDocument(true);
+ }
+ if (
+ isset($xmlSheet->headerFooter['alignWithMargins']) &&
+ !self::boolean((string) $xmlSheet->headerFooter['alignWithMargins'])
+ ) {
+ $docHeaderFooter->setAlignWithMargins(false);
+ } else {
+ $docHeaderFooter->setAlignWithMargins(true);
+ }
+
+ $docHeaderFooter->setOddHeader((string) $xmlSheet->headerFooter->oddHeader);
+ $docHeaderFooter->setOddFooter((string) $xmlSheet->headerFooter->oddFooter);
+ $docHeaderFooter->setEvenHeader((string) $xmlSheet->headerFooter->evenHeader);
+ $docHeaderFooter->setEvenFooter((string) $xmlSheet->headerFooter->evenFooter);
+ $docHeaderFooter->setFirstHeader((string) $xmlSheet->headerFooter->firstHeader);
+ $docHeaderFooter->setFirstFooter((string) $xmlSheet->headerFooter->firstFooter);
+ }
+ }
+
+ private function pageBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ if ($xmlSheet->rowBreaks && $xmlSheet->rowBreaks->brk) {
+ $this->rowBreaks($xmlSheet, $worksheet);
+ }
+ if ($xmlSheet->colBreaks && $xmlSheet->colBreaks->brk) {
+ $this->columnBreaks($xmlSheet, $worksheet);
+ }
+ }
+
+ private function rowBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ foreach ($xmlSheet->rowBreaks->brk as $brk) {
+ $rowBreakMax = isset($brk['max']) ? ((int) $brk['max']) : -1;
+ if ($brk['man']) {
+ $worksheet->setBreak("A{$brk['id']}", Worksheet::BREAK_ROW, $rowBreakMax);
+ }
+ }
+ }
+
+ private function columnBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ foreach ($xmlSheet->colBreaks->brk as $brk) {
+ if ($brk['man']) {
+ $worksheet->setBreak(
+ Coordinate::stringFromColumnIndex(((int) $brk['id']) + 1) . '1',
+ Worksheet::BREAK_COLUMN
+ );
+ }
+ }
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php
new file mode 100644
index 000000000..0d4701afa
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php
@@ -0,0 +1,112 @@
+securityScanner = $securityScanner;
+ $this->docProps = $docProps;
+ }
+
+ /**
+ * @param mixed $obj
+ */
+ private static function nullOrSimple($obj): ?SimpleXMLElement
+ {
+ return ($obj instanceof SimpleXMLElement) ? $obj : null;
+ }
+
+ private function extractPropertyData(string $propertyData): ?SimpleXMLElement
+ {
+ // okay to omit namespace because everything will be processed by xpath
+ $obj = simplexml_load_string(
+ $this->securityScanner->scan($propertyData),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+
+ return self::nullOrSimple($obj);
+ }
+
+ public function readCoreProperties(string $propertyData): void
+ {
+ $xmlCore = $this->extractPropertyData($propertyData);
+
+ if (is_object($xmlCore)) {
+ $xmlCore->registerXPathNamespace('dc', Namespaces::DC_ELEMENTS);
+ $xmlCore->registerXPathNamespace('dcterms', Namespaces::DC_TERMS);
+ $xmlCore->registerXPathNamespace('cp', Namespaces::CORE_PROPERTIES2);
+
+ $this->docProps->setCreator((string) self::getArrayItem($xmlCore->xpath('dc:creator')));
+ $this->docProps->setLastModifiedBy((string) self::getArrayItem($xmlCore->xpath('cp:lastModifiedBy')));
+ $this->docProps->setCreated((string) self::getArrayItem($xmlCore->xpath('dcterms:created'))); //! respect xsi:type
+ $this->docProps->setModified((string) self::getArrayItem($xmlCore->xpath('dcterms:modified'))); //! respect xsi:type
+ $this->docProps->setTitle((string) self::getArrayItem($xmlCore->xpath('dc:title')));
+ $this->docProps->setDescription((string) self::getArrayItem($xmlCore->xpath('dc:description')));
+ $this->docProps->setSubject((string) self::getArrayItem($xmlCore->xpath('dc:subject')));
+ $this->docProps->setKeywords((string) self::getArrayItem($xmlCore->xpath('cp:keywords')));
+ $this->docProps->setCategory((string) self::getArrayItem($xmlCore->xpath('cp:category')));
+ }
+ }
+
+ public function readExtendedProperties(string $propertyData): void
+ {
+ $xmlCore = $this->extractPropertyData($propertyData);
+
+ if (is_object($xmlCore)) {
+ if (isset($xmlCore->Company)) {
+ $this->docProps->setCompany((string) $xmlCore->Company);
+ }
+ if (isset($xmlCore->Manager)) {
+ $this->docProps->setManager((string) $xmlCore->Manager);
+ }
+ if (isset($xmlCore->HyperlinkBase)) {
+ $this->docProps->setHyperlinkBase((string) $xmlCore->HyperlinkBase);
+ }
+ }
+ }
+
+ public function readCustomProperties(string $propertyData): void
+ {
+ $xmlCore = $this->extractPropertyData($propertyData);
+
+ if (is_object($xmlCore)) {
+ foreach ($xmlCore as $xmlProperty) {
+ /** @var SimpleXMLElement $xmlProperty */
+ $cellDataOfficeAttributes = $xmlProperty->attributes();
+ if (isset($cellDataOfficeAttributes['name'])) {
+ $propertyName = (string) $cellDataOfficeAttributes['name'];
+ $cellDataOfficeChildren = $xmlProperty->children('http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes');
+
+ $attributeType = $cellDataOfficeChildren->getName();
+ $attributeValue = (string) $cellDataOfficeChildren->{$attributeType};
+ $attributeValue = DocumentProperties::convertProperty($attributeValue, $attributeType);
+ $attributeType = DocumentProperties::convertPropertyType($attributeType);
+ $this->docProps->setCustomProperty($propertyName, $attributeValue, $attributeType);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param null|array|false $array
+ * @param mixed $key
+ */
+ private static function getArrayItem($array, $key = 0): ?SimpleXMLElement
+ {
+ return is_array($array) ? ($array[$key] ?? null) : null;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php
new file mode 100644
index 000000000..fb7a39320
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php
@@ -0,0 +1,26 @@
+master = $master;
+ $this->formula = $formula;
+ }
+
+ public function master(): string
+ {
+ return $this->master;
+ }
+
+ public function formula(): string
+ {
+ return $this->formula;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php
new file mode 100644
index 000000000..5a496444f
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php
@@ -0,0 +1,140 @@
+worksheet = $workSheet;
+ $this->worksheetXml = $worksheetXml;
+ }
+
+ public function load(bool $readDataOnly, Styles $styleReader): void
+ {
+ if ($this->worksheetXml === null) {
+ return;
+ }
+
+ if (isset($this->worksheetXml->sheetPr)) {
+ $sheetPr = $this->worksheetXml->sheetPr;
+ $this->tabColor($sheetPr, $styleReader);
+ $this->codeName($sheetPr);
+ $this->outlines($sheetPr);
+ $this->pageSetup($sheetPr);
+ }
+
+ if (isset($this->worksheetXml->sheetFormatPr)) {
+ $this->sheetFormat($this->worksheetXml->sheetFormatPr);
+ }
+
+ if (!$readDataOnly && isset($this->worksheetXml->printOptions)) {
+ $this->printOptions($this->worksheetXml->printOptions);
+ }
+ }
+
+ private function tabColor(SimpleXMLElement $sheetPr, Styles $styleReader): void
+ {
+ if (isset($sheetPr->tabColor)) {
+ $this->worksheet->getTabColor()->setARGB($styleReader->readColor($sheetPr->tabColor));
+ }
+ }
+
+ private function codeName(SimpleXMLElement $sheetPrx): void
+ {
+ $sheetPr = $sheetPrx->attributes() ?? [];
+ if (isset($sheetPr['codeName'])) {
+ $this->worksheet->setCodeName((string) $sheetPr['codeName'], false);
+ }
+ }
+
+ private function outlines(SimpleXMLElement $sheetPr): void
+ {
+ if (isset($sheetPr->outlinePr)) {
+ $attr = $sheetPr->outlinePr->attributes() ?? [];
+ if (
+ isset($attr['summaryRight']) &&
+ !self::boolean((string) $attr['summaryRight'])
+ ) {
+ $this->worksheet->setShowSummaryRight(false);
+ } else {
+ $this->worksheet->setShowSummaryRight(true);
+ }
+
+ if (
+ isset($attr['summaryBelow']) &&
+ !self::boolean((string) $attr['summaryBelow'])
+ ) {
+ $this->worksheet->setShowSummaryBelow(false);
+ } else {
+ $this->worksheet->setShowSummaryBelow(true);
+ }
+ }
+ }
+
+ private function pageSetup(SimpleXMLElement $sheetPr): void
+ {
+ if (isset($sheetPr->pageSetUpPr)) {
+ $attr = $sheetPr->pageSetUpPr->attributes() ?? [];
+ if (
+ isset($attr['fitToPage']) &&
+ !self::boolean((string) $attr['fitToPage'])
+ ) {
+ $this->worksheet->getPageSetup()->setFitToPage(false);
+ } else {
+ $this->worksheet->getPageSetup()->setFitToPage(true);
+ }
+ }
+ }
+
+ private function sheetFormat(SimpleXMLElement $sheetFormatPrx): void
+ {
+ $sheetFormatPr = $sheetFormatPrx->attributes() ?? [];
+ if (
+ isset($sheetFormatPr['customHeight']) &&
+ self::boolean((string) $sheetFormatPr['customHeight']) &&
+ isset($sheetFormatPr['defaultRowHeight'])
+ ) {
+ $this->worksheet->getDefaultRowDimension()
+ ->setRowHeight((float) $sheetFormatPr['defaultRowHeight']);
+ }
+
+ if (isset($sheetFormatPr['defaultColWidth'])) {
+ $this->worksheet->getDefaultColumnDimension()
+ ->setWidth((float) $sheetFormatPr['defaultColWidth']);
+ }
+
+ if (
+ isset($sheetFormatPr['zeroHeight']) &&
+ ((string) $sheetFormatPr['zeroHeight'] === '1')
+ ) {
+ $this->worksheet->getDefaultRowDimension()->setZeroHeight(true);
+ }
+ }
+
+ private function printOptions(SimpleXMLElement $printOptionsx): void
+ {
+ $printOptions = $printOptionsx->attributes() ?? [];
+ if (isset($printOptions['gridLinesSet']) && self::boolean((string) $printOptions['gridLinesSet'])) {
+ $this->worksheet->setShowGridlines(true);
+ }
+ if (isset($printOptions['gridLines']) && self::boolean((string) $printOptions['gridLines'])) {
+ $this->worksheet->setPrintGridlines(true);
+ }
+ if (isset($printOptions['horizontalCentered']) && self::boolean((string) $printOptions['horizontalCentered'])) {
+ $this->worksheet->getPageSetup()->setHorizontalCentered(true);
+ }
+ if (isset($printOptions['verticalCentered']) && self::boolean((string) $printOptions['verticalCentered'])) {
+ $this->worksheet->getPageSetup()->setVerticalCentered(true);
+ }
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php
new file mode 100644
index 000000000..b2bc99f05
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php
@@ -0,0 +1,156 @@
+sheetViewXml = $sheetViewXml;
+ $this->sheetViewAttributes = Xlsx::testSimpleXml($sheetViewXml->attributes());
+ $this->worksheet = $workSheet;
+ }
+
+ public function load(): void
+ {
+ $this->topLeft();
+ $this->zoomScale();
+ $this->view();
+ $this->gridLines();
+ $this->headers();
+ $this->direction();
+ $this->showZeros();
+
+ if (isset($this->sheetViewXml->pane)) {
+ $this->pane();
+ }
+ if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection->attributes()->sqref)) {
+ $this->selection();
+ }
+ }
+
+ private function zoomScale(): void
+ {
+ if (isset($this->sheetViewAttributes->zoomScale)) {
+ $zoomScale = (int) ($this->sheetViewAttributes->zoomScale);
+ if ($zoomScale <= 0) {
+ // setZoomScale will throw an Exception if the scale is less than or equals 0
+ // that is OK when manually creating documents, but we should be able to read all documents
+ $zoomScale = 100;
+ }
+
+ $this->worksheet->getSheetView()->setZoomScale($zoomScale);
+ }
+
+ if (isset($this->sheetViewAttributes->zoomScaleNormal)) {
+ $zoomScaleNormal = (int) ($this->sheetViewAttributes->zoomScaleNormal);
+ if ($zoomScaleNormal <= 0) {
+ // setZoomScaleNormal will throw an Exception if the scale is less than or equals 0
+ // that is OK when manually creating documents, but we should be able to read all documents
+ $zoomScaleNormal = 100;
+ }
+
+ $this->worksheet->getSheetView()->setZoomScaleNormal($zoomScaleNormal);
+ }
+ }
+
+ private function view(): void
+ {
+ if (isset($this->sheetViewAttributes->view)) {
+ $this->worksheet->getSheetView()->setView((string) $this->sheetViewAttributes->view);
+ }
+ }
+
+ private function topLeft(): void
+ {
+ if (isset($this->sheetViewAttributes->topLeftCell)) {
+ $this->worksheet->setTopLeftCell($this->sheetViewAttributes->topLeftCell);
+ }
+ }
+
+ private function gridLines(): void
+ {
+ if (isset($this->sheetViewAttributes->showGridLines)) {
+ $this->worksheet->setShowGridLines(
+ self::boolean((string) $this->sheetViewAttributes->showGridLines)
+ );
+ }
+ }
+
+ private function headers(): void
+ {
+ if (isset($this->sheetViewAttributes->showRowColHeaders)) {
+ $this->worksheet->setShowRowColHeaders(
+ self::boolean((string) $this->sheetViewAttributes->showRowColHeaders)
+ );
+ }
+ }
+
+ private function direction(): void
+ {
+ if (isset($this->sheetViewAttributes->rightToLeft)) {
+ $this->worksheet->setRightToLeft(
+ self::boolean((string) $this->sheetViewAttributes->rightToLeft)
+ );
+ }
+ }
+
+ private function showZeros(): void
+ {
+ if (isset($this->sheetViewAttributes->showZeros)) {
+ $this->worksheet->getSheetView()->setShowZeros(
+ self::boolean((string) $this->sheetViewAttributes->showZeros)
+ );
+ }
+ }
+
+ private function pane(): void
+ {
+ $xSplit = 0;
+ $ySplit = 0;
+ $topLeftCell = null;
+ $paneAttributes = $this->sheetViewXml->pane->attributes();
+
+ if (isset($paneAttributes->xSplit)) {
+ $xSplit = (int) ($paneAttributes->xSplit);
+ }
+
+ if (isset($paneAttributes->ySplit)) {
+ $ySplit = (int) ($paneAttributes->ySplit);
+ }
+
+ if (isset($paneAttributes->topLeftCell)) {
+ $topLeftCell = (string) $paneAttributes->topLeftCell;
+ }
+
+ $this->worksheet->freezePane(
+ Coordinate::stringFromColumnIndex($xSplit + 1) . ($ySplit + 1),
+ $topLeftCell
+ );
+ }
+
+ private function selection(): void
+ {
+ $attributes = $this->sheetViewXml->selection->attributes();
+ if ($attributes !== null) {
+ $sqref = (string) $attributes->sqref;
+ $sqref = explode(' ', $sqref);
+ $sqref = $sqref[0];
+ $this->worksheet->setSelectedCells($sqref);
+ }
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php
new file mode 100644
index 000000000..5a360fda1
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php
@@ -0,0 +1,458 @@
+namespace = $namespace;
+ }
+
+ public function setWorkbookPalette(array $palette): void
+ {
+ $this->workbookPalette = $palette;
+ }
+
+ /**
+ * Cast SimpleXMLElement to bool to overcome Scrutinizer problem.
+ *
+ * @param mixed $value
+ */
+ private static function castBool($value): bool
+ {
+ return (bool) $value;
+ }
+
+ private function getStyleAttributes(SimpleXMLElement $value): SimpleXMLElement
+ {
+ $attr = null;
+ if (self::castBool($value)) {
+ $attr = $value->attributes('');
+ if ($attr === null || count($attr) === 0) {
+ $attr = $value->attributes($this->namespace);
+ }
+ }
+
+ return Xlsx::testSimpleXml($attr);
+ }
+
+ public function setStyleXml(SimpleXmlElement $styleXml): void
+ {
+ $this->styleXml = $styleXml;
+ }
+
+ public function setTheme(Theme $theme): void
+ {
+ $this->theme = $theme;
+ }
+
+ public function setStyleBaseData(?Theme $theme = null, array $styles = [], array $cellStyles = []): void
+ {
+ $this->theme = $theme;
+ $this->styles = $styles;
+ $this->cellStyles = $cellStyles;
+ }
+
+ public function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml): void
+ {
+ if (isset($fontStyleXml->name)) {
+ $attr = $this->getStyleAttributes($fontStyleXml->name);
+ if (isset($attr['val'])) {
+ $fontStyle->setName((string) $attr['val']);
+ }
+ }
+ if (isset($fontStyleXml->sz)) {
+ $attr = $this->getStyleAttributes($fontStyleXml->sz);
+ if (isset($attr['val'])) {
+ $fontStyle->setSize((float) $attr['val']);
+ }
+ }
+ if (isset($fontStyleXml->b)) {
+ $attr = $this->getStyleAttributes($fontStyleXml->b);
+ $fontStyle->setBold(!isset($attr['val']) || self::boolean((string) $attr['val']));
+ }
+ if (isset($fontStyleXml->i)) {
+ $attr = $this->getStyleAttributes($fontStyleXml->i);
+ $fontStyle->setItalic(!isset($attr['val']) || self::boolean((string) $attr['val']));
+ }
+ if (isset($fontStyleXml->strike)) {
+ $attr = $this->getStyleAttributes($fontStyleXml->strike);
+ $fontStyle->setStrikethrough(!isset($attr['val']) || self::boolean((string) $attr['val']));
+ }
+ $fontStyle->getColor()->setARGB($this->readColor($fontStyleXml->color));
+
+ if (isset($fontStyleXml->u)) {
+ $attr = $this->getStyleAttributes($fontStyleXml->u);
+ if (!isset($attr['val'])) {
+ $fontStyle->setUnderline(Font::UNDERLINE_SINGLE);
+ } else {
+ $fontStyle->setUnderline((string) $attr['val']);
+ }
+ }
+ if (isset($fontStyleXml->vertAlign)) {
+ $attr = $this->getStyleAttributes($fontStyleXml->vertAlign);
+ if (isset($attr['val'])) {
+ $verticalAlign = strtolower((string) $attr['val']);
+ if ($verticalAlign === 'superscript') {
+ $fontStyle->setSuperscript(true);
+ } elseif ($verticalAlign === 'subscript') {
+ $fontStyle->setSubscript(true);
+ }
+ }
+ }
+ if (isset($fontStyleXml->scheme)) {
+ $attr = $this->getStyleAttributes($fontStyleXml->scheme);
+ $fontStyle->setScheme((string) $attr['val']);
+ }
+ }
+
+ private function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void
+ {
+ if ((string) $numfmtStyleXml['formatCode'] !== '') {
+ $numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmtStyleXml['formatCode']));
+
+ return;
+ }
+ $numfmt = $this->getStyleAttributes($numfmtStyleXml);
+ if (isset($numfmt['formatCode'])) {
+ $numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmt['formatCode']));
+ }
+ }
+
+ public function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillStyleXml): void
+ {
+ if ($fillStyleXml->gradientFill) {
+ /** @var SimpleXMLElement $gradientFill */
+ $gradientFill = $fillStyleXml->gradientFill[0];
+ $attr = $this->getStyleAttributes($gradientFill);
+ if (!empty($attr['type'])) {
+ $fillStyle->setFillType((string) $attr['type']);
+ }
+ $fillStyle->setRotation((float) ($attr['degree']));
+ $gradientFill->registerXPathNamespace('sml', Namespaces::MAIN);
+ $fillStyle->getStartColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color));
+ $fillStyle->getEndColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color));
+ } elseif ($fillStyleXml->patternFill) {
+ $defaultFillStyle = Fill::FILL_NONE;
+ if ($fillStyleXml->patternFill->fgColor) {
+ $fillStyle->getStartColor()->setARGB($this->readColor($fillStyleXml->patternFill->fgColor, true));
+ $defaultFillStyle = Fill::FILL_SOLID;
+ }
+ if ($fillStyleXml->patternFill->bgColor) {
+ $fillStyle->getEndColor()->setARGB($this->readColor($fillStyleXml->patternFill->bgColor, true));
+ $defaultFillStyle = Fill::FILL_SOLID;
+ }
+
+ $type = '';
+ if ((string) $fillStyleXml->patternFill['patternType'] !== '') {
+ $type = (string) $fillStyleXml->patternFill['patternType'];
+ } else {
+ $attr = $this->getStyleAttributes($fillStyleXml->patternFill);
+ $type = (string) $attr['patternType'];
+ }
+ $patternType = ($type === '') ? $defaultFillStyle : $type;
+
+ $fillStyle->setFillType($patternType);
+ }
+ }
+
+ public function readBorderStyle(Borders $borderStyle, SimpleXMLElement $borderStyleXml): void
+ {
+ $diagonalUp = $this->getAttribute($borderStyleXml, 'diagonalUp');
+ $diagonalUp = self::boolean($diagonalUp);
+ $diagonalDown = $this->getAttribute($borderStyleXml, 'diagonalDown');
+ $diagonalDown = self::boolean($diagonalDown);
+ if ($diagonalUp === false) {
+ if ($diagonalDown === false) {
+ $borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE);
+ } else {
+ $borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN);
+ }
+ } elseif ($diagonalDown === false) {
+ $borderStyle->setDiagonalDirection(Borders::DIAGONAL_UP);
+ } else {
+ $borderStyle->setDiagonalDirection(Borders::DIAGONAL_BOTH);
+ }
+
+ if (isset($borderStyleXml->left)) {
+ $this->readBorder($borderStyle->getLeft(), $borderStyleXml->left);
+ }
+ if (isset($borderStyleXml->right)) {
+ $this->readBorder($borderStyle->getRight(), $borderStyleXml->right);
+ }
+ if (isset($borderStyleXml->top)) {
+ $this->readBorder($borderStyle->getTop(), $borderStyleXml->top);
+ }
+ if (isset($borderStyleXml->bottom)) {
+ $this->readBorder($borderStyle->getBottom(), $borderStyleXml->bottom);
+ }
+ if (isset($borderStyleXml->diagonal)) {
+ $this->readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal);
+ }
+ }
+
+ private function getAttribute(SimpleXMLElement $xml, string $attribute): string
+ {
+ $style = '';
+ if ((string) $xml[$attribute] !== '') {
+ $style = (string) $xml[$attribute];
+ } else {
+ $attr = $this->getStyleAttributes($xml);
+ if (isset($attr[$attribute])) {
+ $style = (string) $attr[$attribute];
+ }
+ }
+
+ return $style;
+ }
+
+ private function readBorder(Border $border, SimpleXMLElement $borderXml): void
+ {
+ $style = $this->getAttribute($borderXml, 'style');
+ if ($style !== '') {
+ $border->setBorderStyle((string) $style);
+ } else {
+ $border->setBorderStyle(Border::BORDER_NONE);
+ }
+ if (isset($borderXml->color)) {
+ $border->getColor()->setARGB($this->readColor($borderXml->color));
+ }
+ }
+
+ public function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void
+ {
+ $horizontal = (string) $this->getAttribute($alignmentXml, 'horizontal');
+ if ($horizontal !== '') {
+ $alignment->setHorizontal($horizontal);
+ }
+ $vertical = (string) $this->getAttribute($alignmentXml, 'vertical');
+ if ($vertical !== '') {
+ $alignment->setVertical($vertical);
+ }
+
+ $textRotation = (int) $this->getAttribute($alignmentXml, 'textRotation');
+ if ($textRotation > 90) {
+ $textRotation = 90 - $textRotation;
+ }
+ $alignment->setTextRotation($textRotation);
+
+ $wrapText = $this->getAttribute($alignmentXml, 'wrapText');
+ $alignment->setWrapText(self::boolean((string) $wrapText));
+ $shrinkToFit = $this->getAttribute($alignmentXml, 'shrinkToFit');
+ $alignment->setShrinkToFit(self::boolean((string) $shrinkToFit));
+ $indent = (int) $this->getAttribute($alignmentXml, 'indent');
+ $alignment->setIndent(max($indent, 0));
+ $readingOrder = (int) $this->getAttribute($alignmentXml, 'readingOrder');
+ $alignment->setReadOrder(max($readingOrder, 0));
+ }
+
+ private static function formatGeneral(string $formatString): string
+ {
+ if ($formatString === 'GENERAL') {
+ $formatString = NumberFormat::FORMAT_GENERAL;
+ }
+
+ return $formatString;
+ }
+
+ /**
+ * Read style.
+ *
+ * @param SimpleXMLElement|stdClass $style
+ */
+ public function readStyle(Style $docStyle, $style): void
+ {
+ if ($style instanceof SimpleXMLElement) {
+ $this->readNumberFormat($docStyle->getNumberFormat(), $style->numFmt);
+ } else {
+ $docStyle->getNumberFormat()->setFormatCode(self::formatGeneral((string) $style->numFmt));
+ }
+
+ if (isset($style->font)) {
+ $this->readFontStyle($docStyle->getFont(), $style->font);
+ }
+
+ if (isset($style->fill)) {
+ $this->readFillStyle($docStyle->getFill(), $style->fill);
+ }
+
+ if (isset($style->border)) {
+ $this->readBorderStyle($docStyle->getBorders(), $style->border);
+ }
+
+ if (isset($style->alignment)) {
+ $this->readAlignmentStyle($docStyle->getAlignment(), $style->alignment);
+ }
+
+ // protection
+ if (isset($style->protection)) {
+ $this->readProtectionLocked($docStyle, $style->protection);
+ $this->readProtectionHidden($docStyle, $style->protection);
+ }
+
+ // top-level style settings
+ if (isset($style->quotePrefix)) {
+ $docStyle->setQuotePrefix((bool) $style->quotePrefix);
+ }
+ }
+
+ /**
+ * Read protection locked attribute.
+ */
+ public function readProtectionLocked(Style $docStyle, SimpleXMLElement $style): void
+ {
+ $locked = '';
+ if ((string) $style['locked'] !== '') {
+ $locked = (string) $style['locked'];
+ } else {
+ $attr = $this->getStyleAttributes($style);
+ if (isset($attr['locked'])) {
+ $locked = (string) $attr['locked'];
+ }
+ }
+ if ($locked !== '') {
+ if (self::boolean($locked)) {
+ $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED);
+ } else {
+ $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED);
+ }
+ }
+ }
+
+ /**
+ * Read protection hidden attribute.
+ */
+ public function readProtectionHidden(Style $docStyle, SimpleXMLElement $style): void
+ {
+ $hidden = '';
+ if ((string) $style['hidden'] !== '') {
+ $hidden = (string) $style['hidden'];
+ } else {
+ $attr = $this->getStyleAttributes($style);
+ if (isset($attr['hidden'])) {
+ $hidden = (string) $attr['hidden'];
+ }
+ }
+ if ($hidden !== '') {
+ if (self::boolean((string) $hidden)) {
+ $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED);
+ } else {
+ $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED);
+ }
+ }
+ }
+
+ public function readColor(SimpleXMLElement $color, bool $background = false): string
+ {
+ $attr = $this->getStyleAttributes($color);
+ if (isset($attr['rgb'])) {
+ return (string) $attr['rgb'];
+ }
+ if (isset($attr['indexed'])) {
+ $indexedColor = (int) $attr['indexed'];
+ if ($indexedColor >= count($this->workbookPalette)) {
+ return Color::indexedColor($indexedColor - 7, $background)->getARGB() ?? '';
+ }
+
+ return Color::indexedColor($indexedColor, $background, $this->workbookPalette)->getARGB() ?? '';
+ }
+ if (isset($attr['theme'])) {
+ if ($this->theme !== null) {
+ $returnColour = $this->theme->getColourByIndex((int) $attr['theme']);
+ if (isset($attr['tint'])) {
+ $tintAdjust = (float) $attr['tint'];
+ $returnColour = Color::changeBrightness($returnColour ?? '', $tintAdjust);
+ }
+
+ return 'FF' . $returnColour;
+ }
+ }
+
+ return ($background) ? 'FFFFFFFF' : 'FF000000';
+ }
+
+ public function dxfs(bool $readDataOnly = false): array
+ {
+ $dxfs = [];
+ if (!$readDataOnly && $this->styleXml) {
+ // Conditional Styles
+ if ($this->styleXml->dxfs) {
+ foreach ($this->styleXml->dxfs->dxf as $dxf) {
+ $style = new Style(false, true);
+ $this->readStyle($style, $dxf);
+ $dxfs[] = $style;
+ }
+ }
+ // Cell Styles
+ if ($this->styleXml->cellStyles) {
+ foreach ($this->styleXml->cellStyles->cellStyle as $cellStylex) {
+ $cellStyle = Xlsx::getAttributes($cellStylex);
+ if ((int) ($cellStyle['builtinId']) == 0) {
+ if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) {
+ // Set default style
+ $style = new Style();
+ $this->readStyle($style, $this->cellStyles[(int) ($cellStyle['xfId'])]);
+
+ // normal style, currently not using it for anything
+ }
+ }
+ }
+ }
+ }
+
+ return $dxfs;
+ }
+
+ public function styles(): array
+ {
+ return $this->styles;
+ }
+
+ /**
+ * Get array item.
+ *
+ * @param mixed $array (usually array, in theory can be false)
+ *
+ * @return stdClass
+ */
+ private static function getArrayItem($array, int $key = 0)
+ {
+ return is_array($array) ? ($array[$key] ?? null) : null;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php
new file mode 100644
index 000000000..cf89e995f
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/TableReader.php
@@ -0,0 +1,113 @@
+worksheet = $workSheet;
+ $this->tableXml = $tableXml;
+ }
+
+ /**
+ * Loads Table into the Worksheet.
+ */
+ public function load(): void
+ {
+ // Remove all "$" in the table range
+ $tableRange = (string) preg_replace('/\$/', '', $this->tableXml['ref'] ?? '');
+ if (strpos($tableRange, ':') !== false) {
+ $this->readTable($tableRange, $this->tableXml);
+ }
+ }
+
+ /**
+ * Read Table from xml.
+ */
+ private function readTable(string $tableRange, SimpleXMLElement $tableXml): void
+ {
+ $table = new Table($tableRange);
+ $table->setName((string) $tableXml['displayName']);
+ $table->setShowHeaderRow((string) $tableXml['headerRowCount'] !== '0');
+ $table->setShowTotalsRow((string) $tableXml['totalsRowCount'] === '1');
+
+ $this->readTableAutoFilter($table, $tableXml->autoFilter);
+ $this->readTableColumns($table, $tableXml->tableColumns);
+ $this->readTableStyle($table, $tableXml->tableStyleInfo);
+
+ (new AutoFilter($table, $tableXml))->load();
+ $this->worksheet->addTable($table);
+ }
+
+ /**
+ * Reads TableAutoFilter from xml.
+ */
+ private function readTableAutoFilter(Table $table, SimpleXMLElement $autoFilterXml): void
+ {
+ if ($autoFilterXml->filterColumn === null) {
+ $table->setAllowFilter(false);
+
+ return;
+ }
+
+ foreach ($autoFilterXml->filterColumn as $filterColumn) {
+ $column = $table->getColumnByOffset((int) $filterColumn['colId']);
+ $column->setShowFilterButton((string) $filterColumn['hiddenButton'] !== '1');
+ }
+ }
+
+ /**
+ * Reads TableColumns from xml.
+ */
+ private function readTableColumns(Table $table, SimpleXMLElement $tableColumnsXml): void
+ {
+ $offset = 0;
+ foreach ($tableColumnsXml->tableColumn as $tableColumn) {
+ $column = $table->getColumnByOffset($offset++);
+
+ if ($table->getShowTotalsRow()) {
+ if ($tableColumn['totalsRowLabel']) {
+ $column->setTotalsRowLabel((string) $tableColumn['totalsRowLabel']);
+ }
+
+ if ($tableColumn['totalsRowFunction']) {
+ $column->setTotalsRowFunction((string) $tableColumn['totalsRowFunction']);
+ }
+ }
+
+ if ($tableColumn->calculatedColumnFormula) {
+ $column->setColumnFormula((string) $tableColumn->calculatedColumnFormula);
+ }
+ }
+ }
+
+ /**
+ * Reads TableStyle from xml.
+ */
+ private function readTableStyle(Table $table, SimpleXMLElement $tableStyleInfoXml): void
+ {
+ $tableStyle = new TableStyle();
+ $tableStyle->setTheme((string) $tableStyleInfoXml['name']);
+ $tableStyle->setShowRowStripes((string) $tableStyleInfoXml['showRowStripes'] === '1');
+ $tableStyle->setShowColumnStripes((string) $tableStyleInfoXml['showColumnStripes'] === '1');
+ $tableStyle->setShowFirstColumn((string) $tableStyleInfoXml['showFirstColumn'] === '1');
+ $tableStyle->setShowLastColumn((string) $tableStyleInfoXml['showLastColumn'] === '1');
+ $table->setStyle($tableStyle);
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php
new file mode 100644
index 000000000..706c4d193
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Theme.php
@@ -0,0 +1,78 @@
+themeName = $themeName;
+ $this->colourSchemeName = $colourSchemeName;
+ $this->colourMap = $colourMap;
+ }
+
+ /**
+ * Not called by Reader, never accessible any other time.
+ *
+ * @return string
+ *
+ * @codeCoverageIgnore
+ */
+ public function getThemeName()
+ {
+ return $this->themeName;
+ }
+
+ /**
+ * Not called by Reader, never accessible any other time.
+ *
+ * @return string
+ *
+ * @codeCoverageIgnore
+ */
+ public function getColourSchemeName()
+ {
+ return $this->colourSchemeName;
+ }
+
+ /**
+ * Get colour Map Value by Position.
+ *
+ * @param int $index
+ *
+ * @return null|string
+ */
+ public function getColourByIndex($index)
+ {
+ return $this->colourMap[$index] ?? null;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php
new file mode 100644
index 000000000..4743afbf9
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php
@@ -0,0 +1,153 @@
+spreadsheet = $spreadsheet;
+ }
+
+ /**
+ * @param mixed $mainNS
+ */
+ public function viewSettings(SimpleXMLElement $xmlWorkbook, $mainNS, array $mapSheetId, bool $readDataOnly): void
+ {
+ if ($this->spreadsheet->getSheetCount() == 0) {
+ $this->spreadsheet->createSheet();
+ }
+ // Default active sheet index to the first loaded worksheet from the file
+ $this->spreadsheet->setActiveSheetIndex(0);
+
+ $workbookView = $xmlWorkbook->children($mainNS)->bookViews->workbookView;
+ if ($readDataOnly !== true && !empty($workbookView)) {
+ $workbookViewAttributes = self::testSimpleXml(self::getAttributes($workbookView));
+ // active sheet index
+ $activeTab = (int) $workbookViewAttributes->activeTab; // refers to old sheet index
+ // keep active sheet index if sheet is still loaded, else first sheet is set as the active worksheet
+ if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) {
+ $this->spreadsheet->setActiveSheetIndex($mapSheetId[$activeTab]);
+ }
+
+ $this->horizontalScroll($workbookViewAttributes);
+ $this->verticalScroll($workbookViewAttributes);
+ $this->sheetTabs($workbookViewAttributes);
+ $this->minimized($workbookViewAttributes);
+ $this->autoFilterDateGrouping($workbookViewAttributes);
+ $this->firstSheet($workbookViewAttributes);
+ $this->visibility($workbookViewAttributes);
+ $this->tabRatio($workbookViewAttributes);
+ }
+ }
+
+ /**
+ * @param mixed $value
+ */
+ public static function testSimpleXml($value): SimpleXMLElement
+ {
+ return ($value instanceof SimpleXMLElement)
+ ? $value
+ : new SimpleXMLElement('
+ *
.
+ *
+ * @param string $folderPath
+ */
+ public static function setTrueTypeFontPath($folderPath): void
+ {
+ self::$trueTypeFontPath = $folderPath;
+ }
+
+ /**
+ * Get the path to the folder containing .ttf files.
+ *
+ * @return string
+ */
+ public static function getTrueTypeFontPath()
+ {
+ return self::$trueTypeFontPath;
+ }
+
+ /**
+ * Calculate an (approximate) OpenXML column width, based on font size and text contained.
+ *
+ * @param FontStyle $font Font object
+ * @param null|RichText|string $cellText Text to calculate width
+ * @param int $rotation Rotation angle
+ * @param null|FontStyle $defaultFont Font object
+ * @param bool $filterAdjustment Add space for Autofilter or Table dropdown
+ */
+ public static function calculateColumnWidth(
+ FontStyle $font,
+ $cellText = '',
+ $rotation = 0,
+ ?FontStyle $defaultFont = null,
+ bool $filterAdjustment = false,
+ int $indentAdjustment = 0
+ ): float {
+ // If it is rich text, use plain text
+ if ($cellText instanceof RichText) {
+ $cellText = $cellText->getPlainText();
+ }
+
+ // Special case if there are one or more newline characters ("\n")
+ $cellText = (string) $cellText;
+ if (strpos($cellText, "\n") !== false) {
+ $lineTexts = explode("\n", $cellText);
+ $lineWidths = [];
+ foreach ($lineTexts as $lineText) {
+ $lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont, $filterAdjustment);
+ }
+
+ return max($lineWidths); // width of longest line in cell
+ }
+
+ // Try to get the exact text width in pixels
+ $approximate = self::$autoSizeMethod === self::AUTOSIZE_METHOD_APPROX;
+ $columnWidth = 0;
+ if (!$approximate) {
+ try {
+ $columnWidthAdjust = ceil(
+ self::getTextWidthPixelsExact(
+ str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
+ $font,
+ 0
+ ) * 1.07
+ );
+
+ // Width of text in pixels excl. padding
+ // and addition because Excel adds some padding, just use approx width of 'n' glyph
+ $columnWidth = self::getTextWidthPixelsExact($cellText, $font, $rotation) + $columnWidthAdjust;
+ } catch (PhpSpreadsheetException $e) {
+ $approximate = true;
+ }
+ }
+
+ if ($approximate) {
+ $columnWidthAdjust = self::getTextWidthPixelsApprox(
+ str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
+ $font,
+ 0
+ );
+ // Width of text in pixels excl. padding, approximation
+ // and addition because Excel adds some padding, just use approx width of 'n' glyph
+ $columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust;
+ }
+
+ // Convert from pixel width to column width
+ $columnWidth = Drawing::pixelsToCellDimension((int) $columnWidth, $defaultFont ?? new FontStyle());
+
+ // Return
+ return round($columnWidth, 4);
+ }
+
+ /**
+ * Get GD text width in pixels for a string of text in a certain font at a certain rotation angle.
+ */
+ public static function getTextWidthPixelsExact(string $text, FontStyle $font, int $rotation = 0): float
+ {
+ // font size should really be supplied in pixels in GD2,
+ // but since GD2 seems to assume 72dpi, pixels and points are the same
+ $fontFile = self::getTrueTypeFontFileFromFont($font);
+ $textBox = imagettfbbox($font->getSize() ?? 10.0, $rotation, $fontFile, $text);
+ if ($textBox === false) {
+ // @codeCoverageIgnoreStart
+ throw new PhpSpreadsheetException('imagettfbbox failed');
+ // @codeCoverageIgnoreEnd
+ }
+
+ // Get corners positions
+ $lowerLeftCornerX = $textBox[0];
+ $lowerRightCornerX = $textBox[2];
+ $upperRightCornerX = $textBox[4];
+ $upperLeftCornerX = $textBox[6];
+
+ // Consider the rotation when calculating the width
+ return round(max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX), 4);
+ }
+
+ /**
+ * Get approximate width in pixels for a string of text in a certain font at a certain rotation angle.
+ *
+ * @param string $columnText
+ * @param int $rotation
+ *
+ * @return int Text width in pixels (no padding added)
+ */
+ public static function getTextWidthPixelsApprox($columnText, FontStyle $font, $rotation = 0)
+ {
+ $fontName = $font->getName();
+ $fontSize = $font->getSize();
+
+ // Calculate column width in pixels.
+ // We assume fixed glyph width, but count double for "fullwidth" characters.
+ // Result varies with font name and size.
+ switch ($fontName) {
+ case 'Arial':
+ // value 8 was set because of experience in different exports at Arial 10 font.
+ $columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
+ $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
+
+ break;
+ case 'Verdana':
+ // value 8 was found via interpolation by inspecting real Excel files with Verdana 10 font.
+ $columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
+ $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
+
+ break;
+ default:
+ // just assume Calibri
+ // value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
+ $columnWidth = (int) (8.26 * StringHelper::countCharactersDbcs($columnText));
+ $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
+
+ break;
+ }
+
+ // Calculate approximate rotated column width
+ if ($rotation !== 0) {
+ if ($rotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) {
+ // stacked text
+ $columnWidth = 4; // approximation
+ } else {
+ // rotated text
+ $columnWidth = $columnWidth * cos(deg2rad($rotation))
+ + $fontSize * abs(sin(deg2rad($rotation))) / 5; // approximation
+ }
+ }
+
+ // pixel width is an integer
+ return (int) $columnWidth;
+ }
+
+ /**
+ * Calculate an (approximate) pixel size, based on a font points size.
+ *
+ * @param int $fontSizeInPoints Font size (in points)
+ *
+ * @return int Font size (in pixels)
+ */
+ public static function fontSizeToPixels($fontSizeInPoints)
+ {
+ return (int) ((4 / 3) * $fontSizeInPoints);
+ }
+
+ /**
+ * Calculate an (approximate) pixel size, based on inch size.
+ *
+ * @param int $sizeInInch Font size (in inch)
+ *
+ * @return int Size (in pixels)
+ */
+ public static function inchSizeToPixels($sizeInInch)
+ {
+ return $sizeInInch * 96;
+ }
+
+ /**
+ * Calculate an (approximate) pixel size, based on centimeter size.
+ *
+ * @param int $sizeInCm Font size (in centimeters)
+ *
+ * @return float Size (in pixels)
+ */
+ public static function centimeterSizeToPixels($sizeInCm)
+ {
+ return $sizeInCm * 37.795275591;
+ }
+
+ /**
+ * Returns the font path given the font.
+ *
+ * @return string Path to TrueType font file
+ */
+ public static function getTrueTypeFontFileFromFont(FontStyle $font, bool $checkPath = true)
+ {
+ if ($checkPath && (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath))) {
+ throw new PhpSpreadsheetException('Valid directory to TrueType Font files not specified');
+ }
+
+ $name = $font->getName();
+ $fontArray = array_merge(self::FONT_FILE_NAMES, self::$extraFontArray);
+ if (!isset($fontArray[$name])) {
+ throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file');
+ }
+ $bold = $font->getBold();
+ $italic = $font->getItalic();
+ $index = 'x';
+ if ($bold) {
+ $index .= 'b';
+ }
+ if ($italic) {
+ $index .= 'i';
+ }
+ $fontFile = $fontArray[$name][$index];
+
+ $separator = '';
+ if (mb_strlen(self::$trueTypeFontPath) > 1 && mb_substr(self::$trueTypeFontPath, -1) !== '/' && mb_substr(self::$trueTypeFontPath, -1) !== '\\') {
+ $separator = DIRECTORY_SEPARATOR;
+ }
+ $fontFileAbsolute = preg_match('~^([A-Za-z]:)?[/\\\\]~', $fontFile) === 1;
+ if (!$fontFileAbsolute) {
+ $fontFile = self::$trueTypeFontPath . $separator . $fontFile;
+ }
+
+ // Check if file actually exists
+ if ($checkPath && !file_exists($fontFile) && !$fontFileAbsolute) {
+ $alternateName = $name;
+ if ($index !== 'x' && $fontArray[$name][$index] !== $fontArray[$name]['x']) {
+ // Bold but no italic:
+ // Comic Sans
+ // Tahoma
+ // Neither bold nor italic:
+ // Impact
+ // Lucida Console
+ // Lucida Sans Unicode
+ // Microsoft Sans Serif
+ // Symbol
+ if ($index === 'xb') {
+ $alternateName .= ' Bold';
+ } elseif ($index === 'xi') {
+ $alternateName .= ' Italic';
+ } elseif ($fontArray[$name]['xb'] === $fontArray[$name]['xbi']) {
+ $alternateName .= ' Bold';
+ } else {
+ $alternateName .= ' Bold Italic';
+ }
+ }
+ $fontFile = self::$trueTypeFontPath . $separator . $alternateName . '.ttf';
+ if (!file_exists($fontFile)) {
+ throw new PhpSpreadsheetException('TrueType Font file not found');
+ }
+ }
+
+ return $fontFile;
+ }
+
+ public const CHARSET_FROM_FONT_NAME = [
+ 'EucrosiaUPC' => self::CHARSET_ANSI_THAI,
+ 'Wingdings' => self::CHARSET_SYMBOL,
+ 'Wingdings 2' => self::CHARSET_SYMBOL,
+ 'Wingdings 3' => self::CHARSET_SYMBOL,
+ ];
+
+ /**
+ * Returns the associated charset for the font name.
+ *
+ * @param string $fontName Font name
+ *
+ * @return int Character set code
+ */
+ public static function getCharsetFromFontName($fontName)
+ {
+ return self::CHARSET_FROM_FONT_NAME[$fontName] ?? self::CHARSET_ANSI_LATIN;
+ }
+
+ /**
+ * Get the effective column width for columns without a column dimension or column with width -1
+ * For example, for Calibri 11 this is 9.140625 (64 px).
+ *
+ * @param FontStyle $font The workbooks default font
+ * @param bool $returnAsPixels true = return column width in pixels, false = return in OOXML units
+ *
+ * @return mixed Column width
+ */
+ public static function getDefaultColumnWidthByFont(FontStyle $font, $returnAsPixels = false)
+ {
+ if (isset(self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()])) {
+ // Exact width can be determined
+ $columnWidth = $returnAsPixels ?
+ self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['px']
+ : self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['width'];
+ } else {
+ // We don't have data for this particular font and size, use approximation by
+ // extrapolating from Calibri 11
+ $columnWidth = $returnAsPixels ?
+ self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['px']
+ : self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['width'];
+ $columnWidth = $columnWidth * $font->getSize() / 11;
+
+ // Round pixels to closest integer
+ if ($returnAsPixels) {
+ $columnWidth = (int) round($columnWidth);
+ }
+ }
+
+ return $columnWidth;
+ }
+
+ /**
+ * Get the effective row height for rows without a row dimension or rows with height -1
+ * For example, for Calibri 11 this is 15 points.
+ *
+ * @param FontStyle $font The workbooks default font
+ *
+ * @return float Row height in points
+ */
+ public static function getDefaultRowHeightByFont(FontStyle $font)
+ {
+ $name = $font->getName();
+ $size = $font->getSize();
+ if (isset(self::DEFAULT_COLUMN_WIDTHS[$name][$size])) {
+ $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][$size]['height'];
+ } elseif ($name === 'Arial' || $name === 'Verdana') {
+ $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][10]['height'] * $size / 10.0;
+ } else {
+ $rowHeight = self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['height'] * $size / 11.0;
+ }
+
+ return $rowHeight;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php
new file mode 100644
index 000000000..060f09c88
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php
@@ -0,0 +1,21 @@
+ |
+// | Based on OLE::Storage_Lite by Kawai, Takanori |
+// +----------------------------------------------------------------------+
+//
+
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
+use PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream;
+use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root;
+
+/*
+ * Array for storing OLE instances that are accessed from
+ * OLE_ChainedBlockStream::stream_open().
+ *
+ * @var array
+ */
+$GLOBALS['_OLE_INSTANCES'] = [];
+
+/**
+ * OLE package base class.
+ *
+ * @author Xavier Noguer
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getAlignment()->applyFromArray(
+ * [
+ * 'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
+ * 'vertical' => \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER,
+ * 'textRotation' => 0,
+ * 'wrapText' => TRUE
+ * ]
+ * );
+ *
+ *
+ * @param array $styleArray Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $styleArray)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())
+ ->applyFromArray($this->getStyleArray($styleArray));
+ } else {
+ if (isset($styleArray['horizontal'])) {
+ $this->setHorizontal($styleArray['horizontal']);
+ }
+ if (isset($styleArray['vertical'])) {
+ $this->setVertical($styleArray['vertical']);
+ }
+ if (isset($styleArray['textRotation'])) {
+ $this->setTextRotation($styleArray['textRotation']);
+ }
+ if (isset($styleArray['wrapText'])) {
+ $this->setWrapText($styleArray['wrapText']);
+ }
+ if (isset($styleArray['shrinkToFit'])) {
+ $this->setShrinkToFit($styleArray['shrinkToFit']);
+ }
+ if (isset($styleArray['indent'])) {
+ $this->setIndent($styleArray['indent']);
+ }
+ if (isset($styleArray['readOrder'])) {
+ $this->setReadOrder($styleArray['readOrder']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Horizontal.
+ *
+ * @return null|string
+ */
+ public function getHorizontal()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHorizontal();
+ }
+
+ return $this->horizontal;
+ }
+
+ /**
+ * Set Horizontal.
+ *
+ * @param string $horizontalAlignment see self::HORIZONTAL_*
+ *
+ * @return $this
+ */
+ public function setHorizontal(string $horizontalAlignment)
+ {
+ $horizontalAlignment = strtolower($horizontalAlignment);
+ if ($horizontalAlignment === self::HORIZONTAL_CENTER_CONTINUOUS_LC) {
+ $horizontalAlignment = self::HORIZONTAL_CENTER_CONTINUOUS;
+ }
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['horizontal' => $horizontalAlignment]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->horizontal = $horizontalAlignment;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Vertical.
+ *
+ * @return null|string
+ */
+ public function getVertical()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getVertical();
+ }
+
+ return $this->vertical;
+ }
+
+ /**
+ * Set Vertical.
+ *
+ * @param string $verticalAlignment see self::VERTICAL_*
+ *
+ * @return $this
+ */
+ public function setVertical($verticalAlignment)
+ {
+ $verticalAlignment = strtolower($verticalAlignment);
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['vertical' => $verticalAlignment]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->vertical = $verticalAlignment;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get TextRotation.
+ *
+ * @return null|int
+ */
+ public function getTextRotation()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getTextRotation();
+ }
+
+ return $this->textRotation;
+ }
+
+ /**
+ * Set TextRotation.
+ *
+ * @param int $angleInDegrees
+ *
+ * @return $this
+ */
+ public function setTextRotation($angleInDegrees)
+ {
+ // Excel2007 value 255 => PhpSpreadsheet value -165
+ if ($angleInDegrees == self::TEXTROTATION_STACK_EXCEL) {
+ $angleInDegrees = self::TEXTROTATION_STACK_PHPSPREADSHEET;
+ }
+
+ // Set rotation
+ if (($angleInDegrees >= -90 && $angleInDegrees <= 90) || $angleInDegrees == self::TEXTROTATION_STACK_PHPSPREADSHEET) {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['textRotation' => $angleInDegrees]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->textRotation = $angleInDegrees;
+ }
+ } else {
+ throw new PhpSpreadsheetException('Text rotation should be a value between -90 and 90.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Wrap Text.
+ *
+ * @return bool
+ */
+ public function getWrapText()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getWrapText();
+ }
+
+ return $this->wrapText;
+ }
+
+ /**
+ * Set Wrap Text.
+ *
+ * @param bool $wrapped
+ *
+ * @return $this
+ */
+ public function setWrapText($wrapped)
+ {
+ if ($wrapped == '') {
+ $wrapped = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['wrapText' => $wrapped]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->wrapText = $wrapped;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Shrink to fit.
+ *
+ * @return bool
+ */
+ public function getShrinkToFit()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getShrinkToFit();
+ }
+
+ return $this->shrinkToFit;
+ }
+
+ /**
+ * Set Shrink to fit.
+ *
+ * @param bool $shrink
+ *
+ * @return $this
+ */
+ public function setShrinkToFit($shrink)
+ {
+ if ($shrink == '') {
+ $shrink = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['shrinkToFit' => $shrink]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->shrinkToFit = $shrink;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get indent.
+ *
+ * @return int
+ */
+ public function getIndent()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getIndent();
+ }
+
+ return $this->indent;
+ }
+
+ /**
+ * Set indent.
+ *
+ * @param int $indent
+ *
+ * @return $this
+ */
+ public function setIndent($indent)
+ {
+ if ($indent > 0) {
+ if (
+ $this->getHorizontal() != self::HORIZONTAL_GENERAL &&
+ $this->getHorizontal() != self::HORIZONTAL_LEFT &&
+ $this->getHorizontal() != self::HORIZONTAL_RIGHT &&
+ $this->getHorizontal() != self::HORIZONTAL_DISTRIBUTED
+ ) {
+ $indent = 0; // indent not supported
+ }
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['indent' => $indent]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->indent = $indent;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get read order.
+ *
+ * @return int
+ */
+ public function getReadOrder()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getReadOrder();
+ }
+
+ return $this->readOrder;
+ }
+
+ /**
+ * Set read order.
+ *
+ * @param int $readOrder
+ *
+ * @return $this
+ */
+ public function setReadOrder($readOrder)
+ {
+ if ($readOrder < 0 || $readOrder > 2) {
+ $readOrder = 0;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['readOrder' => $readOrder]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->readOrder = $readOrder;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->horizontal .
+ $this->vertical .
+ $this->textRotation .
+ ($this->wrapText ? 't' : 'f') .
+ ($this->shrinkToFit ? 't' : 'f') .
+ $this->indent .
+ $this->readOrder .
+ __CLASS__
+ );
+ }
+
+ protected function exportArray1(): array
+ {
+ $exportedArray = [];
+ $this->exportArray2($exportedArray, 'horizontal', $this->getHorizontal());
+ $this->exportArray2($exportedArray, 'indent', $this->getIndent());
+ $this->exportArray2($exportedArray, 'readOrder', $this->getReadOrder());
+ $this->exportArray2($exportedArray, 'shrinkToFit', $this->getShrinkToFit());
+ $this->exportArray2($exportedArray, 'textRotation', $this->getTextRotation());
+ $this->exportArray2($exportedArray, 'vertical', $this->getVertical());
+ $this->exportArray2($exportedArray, 'wrapText', $this->getWrapText());
+
+ return $exportedArray;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php
new file mode 100644
index 000000000..568e1e006
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php
@@ -0,0 +1,244 @@
+color = new Color(Color::COLOR_BLACK, $isSupervisor);
+
+ // bind parent if we are a supervisor
+ if ($isSupervisor) {
+ $this->color->bindParent($this, 'color');
+ }
+ if ($isConditional) {
+ $this->borderStyle = self::BORDER_OMIT;
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Border
+ */
+ public function getSharedComponent()
+ {
+ /** @var Style */
+ $parent = $this->parent;
+
+ /** @var Borders $sharedComponent */
+ $sharedComponent = $parent->getSharedComponent();
+ switch ($this->parentPropertyName) {
+ case 'bottom':
+ return $sharedComponent->getBottom();
+ case 'diagonal':
+ return $sharedComponent->getDiagonal();
+ case 'left':
+ return $sharedComponent->getLeft();
+ case 'right':
+ return $sharedComponent->getRight();
+ case 'top':
+ return $sharedComponent->getTop();
+ }
+
+ throw new PhpSpreadsheetException('Cannot get shared component for a pseudo-border.');
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ /** @var Style */
+ $parent = $this->parent;
+
+ return $parent->/** @scrutinizer ignore-call */ getStyleArray([$this->parentPropertyName => $array]);
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->getTop()->applyFromArray(
+ * [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ]
+ * );
+ *
+ *
+ * @param array $styleArray Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $styleArray)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
+ } else {
+ if (isset($styleArray['borderStyle'])) {
+ $this->setBorderStyle($styleArray['borderStyle']);
+ }
+ if (isset($styleArray['color'])) {
+ $this->getColor()->applyFromArray($styleArray['color']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Border style.
+ *
+ * @return string
+ */
+ public function getBorderStyle()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getBorderStyle();
+ }
+
+ return $this->borderStyle;
+ }
+
+ /**
+ * Set Border style.
+ *
+ * @param bool|string $style
+ * When passing a boolean, FALSE equates Border::BORDER_NONE
+ * and TRUE to Border::BORDER_MEDIUM
+ *
+ * @return $this
+ */
+ public function setBorderStyle($style)
+ {
+ if (empty($style)) {
+ $style = self::BORDER_NONE;
+ } elseif (is_bool($style)) {
+ $style = self::BORDER_MEDIUM;
+ }
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['borderStyle' => $style]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->borderStyle = $style;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Border Color.
+ *
+ * @return Color
+ */
+ public function getColor()
+ {
+ return $this->color;
+ }
+
+ /**
+ * Set Border Color.
+ *
+ * @return $this
+ */
+ public function setColor(Color $color)
+ {
+ // make sure parameter is a real color and not a supervisor
+ $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color;
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getColor()->getStyleArray(['argb' => $color->getARGB()]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->color = $color;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->borderStyle .
+ $this->color->getHashCode() .
+ __CLASS__
+ );
+ }
+
+ protected function exportArray1(): array
+ {
+ $exportedArray = [];
+ $this->exportArray2($exportedArray, 'borderStyle', $this->getBorderStyle());
+ $this->exportArray2($exportedArray, 'color', $this->getColor());
+
+ return $exportedArray;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php
new file mode 100644
index 000000000..a1247e859
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php
@@ -0,0 +1,424 @@
+left = new Border($isSupervisor, $isConditional);
+ $this->right = new Border($isSupervisor, $isConditional);
+ $this->top = new Border($isSupervisor, $isConditional);
+ $this->bottom = new Border($isSupervisor, $isConditional);
+ $this->diagonal = new Border($isSupervisor, $isConditional);
+ $this->diagonalDirection = self::DIAGONAL_NONE;
+
+ // Specially for supervisor
+ if ($isSupervisor) {
+ // Initialize pseudo-borders
+ $this->allBorders = new Border(true, $isConditional);
+ $this->outline = new Border(true, $isConditional);
+ $this->inside = new Border(true, $isConditional);
+ $this->vertical = new Border(true, $isConditional);
+ $this->horizontal = new Border(true, $isConditional);
+
+ // bind parent if we are a supervisor
+ $this->left->bindParent($this, 'left');
+ $this->right->bindParent($this, 'right');
+ $this->top->bindParent($this, 'top');
+ $this->bottom->bindParent($this, 'bottom');
+ $this->diagonal->bindParent($this, 'diagonal');
+ $this->allBorders->bindParent($this, 'allBorders');
+ $this->outline->bindParent($this, 'outline');
+ $this->inside->bindParent($this, 'inside');
+ $this->vertical->bindParent($this, 'vertical');
+ $this->horizontal->bindParent($this, 'horizontal');
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Borders
+ */
+ public function getSharedComponent()
+ {
+ /** @var Style */
+ $parent = $this->parent;
+
+ return $parent->getSharedComponent()->getBorders();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['borders' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->applyFromArray(
+ * [
+ * 'bottom' => [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ],
+ * 'top' => [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ]
+ * ]
+ * );
+ *
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->applyFromArray(
+ * [
+ * 'allBorders' => [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ]
+ * ]
+ * );
+ *
+ *
+ * @param array $styleArray Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $styleArray)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
+ } else {
+ if (isset($styleArray['left'])) {
+ $this->getLeft()->applyFromArray($styleArray['left']);
+ }
+ if (isset($styleArray['right'])) {
+ $this->getRight()->applyFromArray($styleArray['right']);
+ }
+ if (isset($styleArray['top'])) {
+ $this->getTop()->applyFromArray($styleArray['top']);
+ }
+ if (isset($styleArray['bottom'])) {
+ $this->getBottom()->applyFromArray($styleArray['bottom']);
+ }
+ if (isset($styleArray['diagonal'])) {
+ $this->getDiagonal()->applyFromArray($styleArray['diagonal']);
+ }
+ if (isset($styleArray['diagonalDirection'])) {
+ $this->setDiagonalDirection($styleArray['diagonalDirection']);
+ }
+ if (isset($styleArray['allBorders'])) {
+ $this->getLeft()->applyFromArray($styleArray['allBorders']);
+ $this->getRight()->applyFromArray($styleArray['allBorders']);
+ $this->getTop()->applyFromArray($styleArray['allBorders']);
+ $this->getBottom()->applyFromArray($styleArray['allBorders']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Left.
+ *
+ * @return Border
+ */
+ public function getLeft()
+ {
+ return $this->left;
+ }
+
+ /**
+ * Get Right.
+ *
+ * @return Border
+ */
+ public function getRight()
+ {
+ return $this->right;
+ }
+
+ /**
+ * Get Top.
+ *
+ * @return Border
+ */
+ public function getTop()
+ {
+ return $this->top;
+ }
+
+ /**
+ * Get Bottom.
+ *
+ * @return Border
+ */
+ public function getBottom()
+ {
+ return $this->bottom;
+ }
+
+ /**
+ * Get Diagonal.
+ *
+ * @return Border
+ */
+ public function getDiagonal()
+ {
+ return $this->diagonal;
+ }
+
+ /**
+ * Get AllBorders (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getAllBorders()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->allBorders;
+ }
+
+ /**
+ * Get Outline (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getOutline()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->outline;
+ }
+
+ /**
+ * Get Inside (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getInside()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->inside;
+ }
+
+ /**
+ * Get Vertical (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getVertical()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->vertical;
+ }
+
+ /**
+ * Get Horizontal (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getHorizontal()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->horizontal;
+ }
+
+ /**
+ * Get DiagonalDirection.
+ *
+ * @return int
+ */
+ public function getDiagonalDirection()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getDiagonalDirection();
+ }
+
+ return $this->diagonalDirection;
+ }
+
+ /**
+ * Set DiagonalDirection.
+ *
+ * @param int $direction see self::DIAGONAL_*
+ *
+ * @return $this
+ */
+ public function setDiagonalDirection($direction)
+ {
+ if ($direction == '') {
+ $direction = self::DIAGONAL_NONE;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['diagonalDirection' => $direction]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->diagonalDirection = $direction;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashcode();
+ }
+
+ return md5(
+ $this->getLeft()->getHashCode() .
+ $this->getRight()->getHashCode() .
+ $this->getTop()->getHashCode() .
+ $this->getBottom()->getHashCode() .
+ $this->getDiagonal()->getHashCode() .
+ $this->getDiagonalDirection() .
+ __CLASS__
+ );
+ }
+
+ protected function exportArray1(): array
+ {
+ $exportedArray = [];
+ $this->exportArray2($exportedArray, 'bottom', $this->getBottom());
+ $this->exportArray2($exportedArray, 'diagonal', $this->getDiagonal());
+ $this->exportArray2($exportedArray, 'diagonalDirection', $this->getDiagonalDirection());
+ $this->exportArray2($exportedArray, 'left', $this->getLeft());
+ $this->exportArray2($exportedArray, 'right', $this->getRight());
+ $this->exportArray2($exportedArray, 'top', $this->getTop());
+
+ return $exportedArray;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php
new file mode 100644
index 000000000..282defc0c
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php
@@ -0,0 +1,427 @@
+ self::COLOR_BLACK,
+ 'White' => self::COLOR_WHITE,
+ 'Red' => self::COLOR_RED,
+ 'Green' => self::COLOR_GREEN,
+ 'Blue' => self::COLOR_BLUE,
+ 'Yellow' => self::COLOR_YELLOW,
+ 'Magenta' => self::COLOR_MAGENTA,
+ 'Cyan' => self::COLOR_CYAN,
+ ];
+
+ const VALIDATE_ARGB_SIZE = 8;
+ const VALIDATE_RGB_SIZE = 6;
+ const VALIDATE_COLOR_6 = '/^[A-F0-9]{6}$/i';
+ const VALIDATE_COLOR_8 = '/^[A-F0-9]{8}$/i';
+
+ private const INDEXED_COLORS = [
+ 1 => 'FF000000', // System Colour #1 - Black
+ 2 => 'FFFFFFFF', // System Colour #2 - White
+ 3 => 'FFFF0000', // System Colour #3 - Red
+ 4 => 'FF00FF00', // System Colour #4 - Green
+ 5 => 'FF0000FF', // System Colour #5 - Blue
+ 6 => 'FFFFFF00', // System Colour #6 - Yellow
+ 7 => 'FFFF00FF', // System Colour #7- Magenta
+ 8 => 'FF00FFFF', // System Colour #8- Cyan
+ 9 => 'FF800000', // Standard Colour #9
+ 10 => 'FF008000', // Standard Colour #10
+ 11 => 'FF000080', // Standard Colour #11
+ 12 => 'FF808000', // Standard Colour #12
+ 13 => 'FF800080', // Standard Colour #13
+ 14 => 'FF008080', // Standard Colour #14
+ 15 => 'FFC0C0C0', // Standard Colour #15
+ 16 => 'FF808080', // Standard Colour #16
+ 17 => 'FF9999FF', // Chart Fill Colour #17
+ 18 => 'FF993366', // Chart Fill Colour #18
+ 19 => 'FFFFFFCC', // Chart Fill Colour #19
+ 20 => 'FFCCFFFF', // Chart Fill Colour #20
+ 21 => 'FF660066', // Chart Fill Colour #21
+ 22 => 'FFFF8080', // Chart Fill Colour #22
+ 23 => 'FF0066CC', // Chart Fill Colour #23
+ 24 => 'FFCCCCFF', // Chart Fill Colour #24
+ 25 => 'FF000080', // Chart Line Colour #25
+ 26 => 'FFFF00FF', // Chart Line Colour #26
+ 27 => 'FFFFFF00', // Chart Line Colour #27
+ 28 => 'FF00FFFF', // Chart Line Colour #28
+ 29 => 'FF800080', // Chart Line Colour #29
+ 30 => 'FF800000', // Chart Line Colour #30
+ 31 => 'FF008080', // Chart Line Colour #31
+ 32 => 'FF0000FF', // Chart Line Colour #32
+ 33 => 'FF00CCFF', // Standard Colour #33
+ 34 => 'FFCCFFFF', // Standard Colour #34
+ 35 => 'FFCCFFCC', // Standard Colour #35
+ 36 => 'FFFFFF99', // Standard Colour #36
+ 37 => 'FF99CCFF', // Standard Colour #37
+ 38 => 'FFFF99CC', // Standard Colour #38
+ 39 => 'FFCC99FF', // Standard Colour #39
+ 40 => 'FFFFCC99', // Standard Colour #40
+ 41 => 'FF3366FF', // Standard Colour #41
+ 42 => 'FF33CCCC', // Standard Colour #42
+ 43 => 'FF99CC00', // Standard Colour #43
+ 44 => 'FFFFCC00', // Standard Colour #44
+ 45 => 'FFFF9900', // Standard Colour #45
+ 46 => 'FFFF6600', // Standard Colour #46
+ 47 => 'FF666699', // Standard Colour #47
+ 48 => 'FF969696', // Standard Colour #48
+ 49 => 'FF003366', // Standard Colour #49
+ 50 => 'FF339966', // Standard Colour #50
+ 51 => 'FF003300', // Standard Colour #51
+ 52 => 'FF333300', // Standard Colour #52
+ 53 => 'FF993300', // Standard Colour #53
+ 54 => 'FF993366', // Standard Colour #54
+ 55 => 'FF333399', // Standard Colour #55
+ 56 => 'FF333333', // Standard Colour #56
+ ];
+
+ /**
+ * ARGB - Alpha RGB.
+ *
+ * @var null|string
+ */
+ protected $argb;
+
+ /** @var bool */
+ private $hasChanged = false;
+
+ /**
+ * Create a new Color.
+ *
+ * @param string $colorValue ARGB value for the colour, or named colour
+ * @param bool $isSupervisor Flag indicating if this is a supervisor or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ * @param bool $isConditional Flag indicating if this is a conditional style or not
+ * Leave this value at default unless you understand exactly what
+ * its ramifications are
+ */
+ public function __construct($colorValue = self::COLOR_BLACK, $isSupervisor = false, $isConditional = false)
+ {
+ // Supervisor?
+ parent::__construct($isSupervisor);
+
+ // Initialise values
+ if (!$isConditional) {
+ $this->argb = $this->validateColor($colorValue) ?: self::COLOR_BLACK;
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Color
+ */
+ public function getSharedComponent()
+ {
+ /** @var Style */
+ $parent = $this->parent;
+ /** @var Border|Fill $sharedComponent */
+ $sharedComponent = $parent->getSharedComponent();
+ if ($sharedComponent instanceof Fill) {
+ if ($this->parentPropertyName === 'endColor') {
+ return $sharedComponent->getEndColor();
+ }
+
+ return $sharedComponent->getStartColor();
+ }
+
+ return $sharedComponent->getColor();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ /** @var Style */
+ $parent = $this->parent;
+
+ return $parent->/** @scrutinizer ignore-call */ getStyleArray([$this->parentPropertyName => $array]);
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getFont()->getColor()->applyFromArray(['rgb' => '808080']);
+ *
+ *
+ * @param array $styleArray Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $styleArray)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
+ } else {
+ if (isset($styleArray['rgb'])) {
+ $this->setRGB($styleArray['rgb']);
+ }
+ if (isset($styleArray['argb'])) {
+ $this->setARGB($styleArray['argb']);
+ }
+ }
+
+ return $this;
+ }
+
+ private function validateColor(?string $colorValue): string
+ {
+ if ($colorValue === null || $colorValue === '') {
+ return self::COLOR_BLACK;
+ }
+ $named = ucfirst(strtolower($colorValue));
+ if (array_key_exists($named, self::NAMED_COLOR_TRANSLATIONS)) {
+ return self::NAMED_COLOR_TRANSLATIONS[$named];
+ }
+ if (preg_match(self::VALIDATE_COLOR_8, $colorValue) === 1) {
+ return $colorValue;
+ }
+ if (preg_match(self::VALIDATE_COLOR_6, $colorValue) === 1) {
+ return 'FF' . $colorValue;
+ }
+
+ return '';
+ }
+
+ /**
+ * Get ARGB.
+ */
+ public function getARGB(): ?string
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getARGB();
+ }
+
+ return $this->argb;
+ }
+
+ /**
+ * Set ARGB.
+ *
+ * @param string $colorValue ARGB value, or a named color
+ *
+ * @return $this
+ */
+ public function setARGB(?string $colorValue = self::COLOR_BLACK)
+ {
+ $this->hasChanged = true;
+ $colorValue = $this->validateColor($colorValue);
+ if ($colorValue === '') {
+ return $this;
+ }
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['argb' => $colorValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->argb = $colorValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get RGB.
+ */
+ public function getRGB(): string
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getRGB();
+ }
+
+ return substr($this->argb ?? '', 2);
+ }
+
+ /**
+ * Set RGB.
+ *
+ * @param string $colorValue RGB value, or a named color
+ *
+ * @return $this
+ */
+ public function setRGB(?string $colorValue = self::COLOR_BLACK)
+ {
+ return $this->setARGB($colorValue);
+ }
+
+ /**
+ * Get a specified colour component of an RGB value.
+ *
+ * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param int $offset Position within the RGB value to extract
+ * @param bool $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ *
+ * @return int|string The extracted colour component
+ */
+ private static function getColourComponent($rgbValue, $offset, $hex = true)
+ {
+ $colour = substr($rgbValue, $offset, 2) ?: '';
+ if (preg_match('/^[0-9a-f]{2}$/i', $colour) !== 1) {
+ $colour = '00';
+ }
+
+ return ($hex) ? $colour : (int) hexdec($colour);
+ }
+
+ /**
+ * Get the red colour component of an RGB value.
+ *
+ * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param bool $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ *
+ * @return int|string The red colour component
+ */
+ public static function getRed($rgbValue, $hex = true)
+ {
+ return self::getColourComponent($rgbValue, strlen($rgbValue) - 6, $hex);
+ }
+
+ /**
+ * Get the green colour component of an RGB value.
+ *
+ * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param bool $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ *
+ * @return int|string The green colour component
+ */
+ public static function getGreen($rgbValue, $hex = true)
+ {
+ return self::getColourComponent($rgbValue, strlen($rgbValue) - 4, $hex);
+ }
+
+ /**
+ * Get the blue colour component of an RGB value.
+ *
+ * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param bool $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ *
+ * @return int|string The blue colour component
+ */
+ public static function getBlue($rgbValue, $hex = true)
+ {
+ return self::getColourComponent($rgbValue, strlen($rgbValue) - 2, $hex);
+ }
+
+ /**
+ * Adjust the brightness of a color.
+ *
+ * @param string $hexColourValue The colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
+ * @param float $adjustPercentage The percentage by which to adjust the colour as a float from -1 to 1
+ *
+ * @return string The adjusted colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
+ */
+ public static function changeBrightness($hexColourValue, $adjustPercentage)
+ {
+ $rgba = (strlen($hexColourValue) === 8);
+ $adjustPercentage = max(-1.0, min(1.0, $adjustPercentage));
+
+ /** @var int $red */
+ $red = self::getRed($hexColourValue, false);
+ /** @var int $green */
+ $green = self::getGreen($hexColourValue, false);
+ /** @var int $blue */
+ $blue = self::getBlue($hexColourValue, false);
+
+ return (($rgba) ? 'FF' : '') . RgbTint::rgbAndTintToRgb($red, $green, $blue, $adjustPercentage);
+ }
+
+ /**
+ * Get indexed color.
+ *
+ * @param int $colorIndex Index entry point into the colour array
+ * @param bool $background Flag to indicate whether default background or foreground colour
+ * should be returned if the indexed colour doesn't exist
+ */
+ public static function indexedColor($colorIndex, $background = false, ?array $palette = null): self
+ {
+ // Clean parameter
+ $colorIndex = (int) $colorIndex;
+
+ if (empty($palette)) {
+ if (isset(self::INDEXED_COLORS[$colorIndex])) {
+ return new self(self::INDEXED_COLORS[$colorIndex]);
+ }
+ } else {
+ if (isset($palette[$colorIndex])) {
+ return new self($palette[$colorIndex]);
+ }
+ }
+
+ return ($background) ? new self(self::COLOR_WHITE) : new self(self::COLOR_BLACK);
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode(): string
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->argb .
+ __CLASS__
+ );
+ }
+
+ protected function exportArray1(): array
+ {
+ $exportedArray = [];
+ $this->exportArray2($exportedArray, 'argb', $this->getARGB());
+
+ return $exportedArray;
+ }
+
+ public function getHasChanged(): bool
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->hasChanged;
+ }
+
+ return $this->hasChanged;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php
new file mode 100644
index 000000000..36069b00c
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php
@@ -0,0 +1,361 @@
+style = new Style(false, true);
+ }
+
+ public function getNoFormatSet(): bool
+ {
+ return $this->noFormatSet;
+ }
+
+ public function setNoFormatSet(bool $noFormatSet): self
+ {
+ $this->noFormatSet = $noFormatSet;
+
+ return $this;
+ }
+
+ /**
+ * Get Condition type.
+ *
+ * @return string
+ */
+ public function getConditionType()
+ {
+ return $this->conditionType;
+ }
+
+ /**
+ * Set Condition type.
+ *
+ * @param string $type Condition type, see self::CONDITION_*
+ *
+ * @return $this
+ */
+ public function setConditionType($type)
+ {
+ $this->conditionType = $type;
+
+ return $this;
+ }
+
+ /**
+ * Get Operator type.
+ *
+ * @return string
+ */
+ public function getOperatorType()
+ {
+ return $this->operatorType;
+ }
+
+ /**
+ * Set Operator type.
+ *
+ * @param string $type Conditional operator type, see self::OPERATOR_*
+ *
+ * @return $this
+ */
+ public function setOperatorType($type)
+ {
+ $this->operatorType = $type;
+
+ return $this;
+ }
+
+ /**
+ * Get text.
+ *
+ * @return string
+ */
+ public function getText()
+ {
+ return $this->text;
+ }
+
+ /**
+ * Set text.
+ *
+ * @param string $text
+ *
+ * @return $this
+ */
+ public function setText($text)
+ {
+ $this->text = $text;
+
+ return $this;
+ }
+
+ /**
+ * Get StopIfTrue.
+ *
+ * @return bool
+ */
+ public function getStopIfTrue()
+ {
+ return $this->stopIfTrue;
+ }
+
+ /**
+ * Set StopIfTrue.
+ *
+ * @param bool $stopIfTrue
+ *
+ * @return $this
+ */
+ public function setStopIfTrue($stopIfTrue)
+ {
+ $this->stopIfTrue = $stopIfTrue;
+
+ return $this;
+ }
+
+ /**
+ * Get Conditions.
+ *
+ * @return (bool|float|int|string)[]
+ */
+ public function getConditions()
+ {
+ return $this->condition;
+ }
+
+ /**
+ * Set Conditions.
+ *
+ * @param (bool|float|int|string)[]|bool|float|int|string $conditions Condition
+ *
+ * @return $this
+ */
+ public function setConditions($conditions)
+ {
+ if (!is_array($conditions)) {
+ $conditions = [$conditions];
+ }
+ $this->condition = $conditions;
+
+ return $this;
+ }
+
+ /**
+ * Add Condition.
+ *
+ * @param bool|float|int|string $condition Condition
+ *
+ * @return $this
+ */
+ public function addCondition($condition)
+ {
+ $this->condition[] = $condition;
+
+ return $this;
+ }
+
+ /**
+ * Get Style.
+ *
+ * @return Style
+ */
+ public function getStyle()
+ {
+ return $this->style;
+ }
+
+ /**
+ * Set Style.
+ *
+ * @return $this
+ */
+ public function setStyle(Style $style)
+ {
+ $this->style = $style;
+
+ return $this;
+ }
+
+ /**
+ * get DataBar.
+ *
+ * @return null|ConditionalDataBar
+ */
+ public function getDataBar()
+ {
+ return $this->dataBar;
+ }
+
+ /**
+ * set DataBar.
+ *
+ * @return $this
+ */
+ public function setDataBar(ConditionalDataBar $dataBar)
+ {
+ $this->dataBar = $dataBar;
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->conditionType .
+ $this->operatorType .
+ implode(';', $this->condition) .
+ $this->style->getHashCode() .
+ __CLASS__
+ );
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone()
+ {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if (is_object($value)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+
+ /**
+ * Verify if param is valid condition type.
+ */
+ public static function isValidConditionType(string $type): bool
+ {
+ return in_array($type, self::CONDITION_TYPES);
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php
new file mode 100644
index 000000000..3e5f76049
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php
@@ -0,0 +1,313 @@
+ '=',
+ Conditional::OPERATOR_GREATERTHAN => '>',
+ Conditional::OPERATOR_GREATERTHANOREQUAL => '>=',
+ Conditional::OPERATOR_LESSTHAN => '<',
+ Conditional::OPERATOR_LESSTHANOREQUAL => '<=',
+ Conditional::OPERATOR_NOTEQUAL => '<>',
+ ];
+
+ public const COMPARISON_RANGE_OPERATORS = [
+ Conditional::OPERATOR_BETWEEN => 'IF(AND(A1>=%s,A1<=%s),TRUE,FALSE)',
+ Conditional::OPERATOR_NOTBETWEEN => 'IF(AND(A1>=%s,A1<=%s),FALSE,TRUE)',
+ ];
+
+ public const COMPARISON_DUPLICATES_OPERATORS = [
+ Conditional::CONDITION_DUPLICATES => "COUNTIF('%s'!%s,%s)>1",
+ Conditional::CONDITION_UNIQUE => "COUNTIF('%s'!%s,%s)=1",
+ ];
+
+ /**
+ * @var Cell
+ */
+ protected $cell;
+
+ /**
+ * @var int
+ */
+ protected $cellRow;
+
+ /**
+ * @var Worksheet
+ */
+ protected $worksheet;
+
+ /**
+ * @var int
+ */
+ protected $cellColumn;
+
+ /**
+ * @var string
+ */
+ protected $conditionalRange;
+
+ /**
+ * @var string
+ */
+ protected $referenceCell;
+
+ /**
+ * @var int
+ */
+ protected $referenceRow;
+
+ /**
+ * @var int
+ */
+ protected $referenceColumn;
+
+ /**
+ * @var Calculation
+ */
+ protected $engine;
+
+ public function __construct(Cell $cell, string $conditionalRange)
+ {
+ $this->cell = $cell;
+ $this->worksheet = $cell->getWorksheet();
+ [$this->cellColumn, $this->cellRow] = Coordinate::indexesFromString($this->cell->getCoordinate());
+ $this->setReferenceCellForExpressions($conditionalRange);
+
+ $this->engine = Calculation::getInstance($this->worksheet->getParent());
+ }
+
+ protected function setReferenceCellForExpressions(string $conditionalRange): void
+ {
+ $conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($conditionalRange)));
+ [$this->referenceCell] = $conditionalRange[0];
+
+ [$this->referenceColumn, $this->referenceRow] = Coordinate::indexesFromString($this->referenceCell);
+
+ // Convert our conditional range to an absolute conditional range, so it can be used "pinned" in formulae
+ $rangeSets = [];
+ foreach ($conditionalRange as $rangeSet) {
+ $absoluteRangeSet = array_map(
+ [Coordinate::class, 'absoluteCoordinate'],
+ $rangeSet
+ );
+ $rangeSets[] = implode(':', $absoluteRangeSet);
+ }
+ $this->conditionalRange = implode(',', $rangeSets);
+ }
+
+ public function evaluateConditional(Conditional $conditional): bool
+ {
+ // Some calculations may modify the stored cell; so reset it before every evaluation.
+ $cellColumn = Coordinate::stringFromColumnIndex($this->cellColumn);
+ $cellAddress = "{$cellColumn}{$this->cellRow}";
+ $this->cell = $this->worksheet->getCell($cellAddress);
+
+ switch ($conditional->getConditionType()) {
+ case Conditional::CONDITION_CELLIS:
+ return $this->processOperatorComparison($conditional);
+ case Conditional::CONDITION_DUPLICATES:
+ case Conditional::CONDITION_UNIQUE:
+ return $this->processDuplicatesComparison($conditional);
+ case Conditional::CONDITION_CONTAINSTEXT:
+ // Expression is NOT(ISERROR(SEARCH("
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getFill()->applyFromArray(
+ * [
+ * 'fillType' => Fill::FILL_GRADIENT_LINEAR,
+ * 'rotation' => 0.0,
+ * 'startColor' => [
+ * 'rgb' => '000000'
+ * ],
+ * 'endColor' => [
+ * 'argb' => 'FFFFFFFF'
+ * ]
+ * ]
+ * );
+ *
+ *
+ * @param array $styleArray Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $styleArray)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
+ } else {
+ if (isset($styleArray['fillType'])) {
+ $this->setFillType($styleArray['fillType']);
+ }
+ if (isset($styleArray['rotation'])) {
+ $this->setRotation($styleArray['rotation']);
+ }
+ if (isset($styleArray['startColor'])) {
+ $this->getStartColor()->applyFromArray($styleArray['startColor']);
+ }
+ if (isset($styleArray['endColor'])) {
+ $this->getEndColor()->applyFromArray($styleArray['endColor']);
+ }
+ if (isset($styleArray['color'])) {
+ $this->getStartColor()->applyFromArray($styleArray['color']);
+ $this->getEndColor()->applyFromArray($styleArray['color']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Fill Type.
+ *
+ * @return null|string
+ */
+ public function getFillType()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getFillType();
+ }
+
+ return $this->fillType;
+ }
+
+ /**
+ * Set Fill Type.
+ *
+ * @param string $fillType Fill type, see self::FILL_*
+ *
+ * @return $this
+ */
+ public function setFillType($fillType)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['fillType' => $fillType]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->fillType = $fillType;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Rotation.
+ *
+ * @return float
+ */
+ public function getRotation()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getRotation();
+ }
+
+ return $this->rotation;
+ }
+
+ /**
+ * Set Rotation.
+ *
+ * @param float $angleInDegrees
+ *
+ * @return $this
+ */
+ public function setRotation($angleInDegrees)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['rotation' => $angleInDegrees]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->rotation = $angleInDegrees;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Start Color.
+ *
+ * @return Color
+ */
+ public function getStartColor()
+ {
+ return $this->startColor;
+ }
+
+ /**
+ * Set Start Color.
+ *
+ * @return $this
+ */
+ public function setStartColor(Color $color)
+ {
+ $this->colorChanged = true;
+ // make sure parameter is a real color and not a supervisor
+ $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color;
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStartColor()->getStyleArray(['argb' => $color->getARGB()]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->startColor = $color;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get End Color.
+ *
+ * @return Color
+ */
+ public function getEndColor()
+ {
+ return $this->endColor;
+ }
+
+ /**
+ * Set End Color.
+ *
+ * @return $this
+ */
+ public function setEndColor(Color $color)
+ {
+ $this->colorChanged = true;
+ // make sure parameter is a real color and not a supervisor
+ $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color;
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getEndColor()->getStyleArray(['argb' => $color->getARGB()]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->endColor = $color;
+ }
+
+ return $this;
+ }
+
+ public function getColorsChanged(): bool
+ {
+ if ($this->isSupervisor) {
+ $changed = $this->getSharedComponent()->colorChanged;
+ } else {
+ $changed = $this->colorChanged;
+ }
+
+ return $changed || $this->startColor->getHasChanged() || $this->endColor->getHasChanged();
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+ // Note that we don't care about colours for fill type NONE, but could have duplicate NONEs with
+ // different hashes if we don't explicitly prevent this
+ return md5(
+ $this->getFillType() .
+ $this->getRotation() .
+ ($this->getFillType() !== self::FILL_NONE ? $this->getStartColor()->getHashCode() : '') .
+ ($this->getFillType() !== self::FILL_NONE ? $this->getEndColor()->getHashCode() : '') .
+ ((string) $this->getColorsChanged()) .
+ __CLASS__
+ );
+ }
+
+ protected function exportArray1(): array
+ {
+ $exportedArray = [];
+ $this->exportArray2($exportedArray, 'fillType', $this->getFillType());
+ $this->exportArray2($exportedArray, 'rotation', $this->getRotation());
+ if ($this->getColorsChanged()) {
+ $this->exportArray2($exportedArray, 'endColor', $this->getEndColor());
+ $this->exportArray2($exportedArray, 'startColor', $this->getStartColor());
+ }
+
+ return $exportedArray;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php
new file mode 100644
index 000000000..a8eeaa986
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php
@@ -0,0 +1,854 @@
+name = null;
+ $this->size = null;
+ $this->bold = null;
+ $this->italic = null;
+ $this->superscript = null;
+ $this->subscript = null;
+ $this->underline = null;
+ $this->strikethrough = null;
+ $this->color = new Color(Color::COLOR_BLACK, $isSupervisor, $isConditional);
+ } else {
+ $this->color = new Color(Color::COLOR_BLACK, $isSupervisor);
+ }
+ // bind parent if we are a supervisor
+ if ($isSupervisor) {
+ $this->color->bindParent($this, 'color');
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Font
+ */
+ public function getSharedComponent()
+ {
+ /** @var Style */
+ $parent = $this->parent;
+
+ return $parent->getSharedComponent()->getFont();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['font' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getFont()->applyFromArray(
+ * [
+ * 'name' => 'Arial',
+ * 'bold' => TRUE,
+ * 'italic' => FALSE,
+ * 'underline' => \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE,
+ * 'strikethrough' => FALSE,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ]
+ * );
+ *
+ *
+ * @param array $styleArray Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $styleArray)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
+ } else {
+ if (isset($styleArray['name'])) {
+ $this->setName($styleArray['name']);
+ }
+ if (isset($styleArray['latin'])) {
+ $this->setLatin($styleArray['latin']);
+ }
+ if (isset($styleArray['eastAsian'])) {
+ $this->setEastAsian($styleArray['eastAsian']);
+ }
+ if (isset($styleArray['complexScript'])) {
+ $this->setComplexScript($styleArray['complexScript']);
+ }
+ if (isset($styleArray['bold'])) {
+ $this->setBold($styleArray['bold']);
+ }
+ if (isset($styleArray['italic'])) {
+ $this->setItalic($styleArray['italic']);
+ }
+ if (isset($styleArray['superscript'])) {
+ $this->setSuperscript($styleArray['superscript']);
+ }
+ if (isset($styleArray['subscript'])) {
+ $this->setSubscript($styleArray['subscript']);
+ }
+ if (isset($styleArray['underline'])) {
+ $this->setUnderline($styleArray['underline']);
+ }
+ if (isset($styleArray['strikethrough'])) {
+ $this->setStrikethrough($styleArray['strikethrough']);
+ }
+ if (isset($styleArray['color'])) {
+ $this->getColor()->applyFromArray($styleArray['color']);
+ }
+ if (isset($styleArray['size'])) {
+ $this->setSize($styleArray['size']);
+ }
+ if (isset($styleArray['chartColor'])) {
+ $this->chartColor = $styleArray['chartColor'];
+ }
+ if (isset($styleArray['scheme'])) {
+ $this->setScheme($styleArray['scheme']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Name.
+ *
+ * @return null|string
+ */
+ public function getName()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getName();
+ }
+
+ return $this->name;
+ }
+
+ public function getLatin(): string
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getLatin();
+ }
+
+ return $this->latin;
+ }
+
+ public function getEastAsian(): string
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getEastAsian();
+ }
+
+ return $this->eastAsian;
+ }
+
+ public function getComplexScript(): string
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getComplexScript();
+ }
+
+ return $this->complexScript;
+ }
+
+ /**
+ * Set Name and turn off Scheme.
+ *
+ * @param string $fontname
+ */
+ public function setName($fontname): self
+ {
+ if ($fontname == '') {
+ $fontname = 'Calibri';
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['name' => $fontname]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->name = $fontname;
+ }
+
+ return $this->setScheme('');
+ }
+
+ public function setLatin(string $fontname): self
+ {
+ if ($fontname == '') {
+ $fontname = 'Calibri';
+ }
+ if (!$this->isSupervisor) {
+ $this->latin = $fontname;
+ } else {
+ // should never be true
+ // @codeCoverageIgnoreStart
+ $styleArray = $this->getStyleArray(['latin' => $fontname]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ // @codeCoverageIgnoreEnd
+ }
+
+ return $this;
+ }
+
+ public function setEastAsian(string $fontname): self
+ {
+ if ($fontname == '') {
+ $fontname = 'Calibri';
+ }
+ if (!$this->isSupervisor) {
+ $this->eastAsian = $fontname;
+ } else {
+ // should never be true
+ // @codeCoverageIgnoreStart
+ $styleArray = $this->getStyleArray(['eastAsian' => $fontname]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ // @codeCoverageIgnoreEnd
+ }
+
+ return $this;
+ }
+
+ public function setComplexScript(string $fontname): self
+ {
+ if ($fontname == '') {
+ $fontname = 'Calibri';
+ }
+ if (!$this->isSupervisor) {
+ $this->complexScript = $fontname;
+ } else {
+ // should never be true
+ // @codeCoverageIgnoreStart
+ $styleArray = $this->getStyleArray(['complexScript' => $fontname]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ // @codeCoverageIgnoreEnd
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Size.
+ *
+ * @return null|float
+ */
+ public function getSize()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getSize();
+ }
+
+ return $this->size;
+ }
+
+ /**
+ * Set Size.
+ *
+ * @param mixed $sizeInPoints A float representing the value of a positive measurement in points (1/72 of an inch)
+ *
+ * @return $this
+ */
+ public function setSize($sizeInPoints, bool $nullOk = false)
+ {
+ if (is_string($sizeInPoints) || is_int($sizeInPoints)) {
+ $sizeInPoints = (float) $sizeInPoints; // $pValue = 0 if given string is not numeric
+ }
+
+ // Size must be a positive floating point number
+ // ECMA-376-1:2016, part 1, chapter 18.4.11 sz (Font Size), p. 1536
+ if (!is_float($sizeInPoints) || !($sizeInPoints > 0)) {
+ if (!$nullOk || $sizeInPoints !== null) {
+ $sizeInPoints = 10.0;
+ }
+ }
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['size' => $sizeInPoints]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->size = $sizeInPoints;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Bold.
+ *
+ * @return null|bool
+ */
+ public function getBold()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getBold();
+ }
+
+ return $this->bold;
+ }
+
+ /**
+ * Set Bold.
+ *
+ * @param bool $bold
+ *
+ * @return $this
+ */
+ public function setBold($bold)
+ {
+ if ($bold == '') {
+ $bold = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['bold' => $bold]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->bold = $bold;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Italic.
+ *
+ * @return null|bool
+ */
+ public function getItalic()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getItalic();
+ }
+
+ return $this->italic;
+ }
+
+ /**
+ * Set Italic.
+ *
+ * @param bool $italic
+ *
+ * @return $this
+ */
+ public function setItalic($italic)
+ {
+ if ($italic == '') {
+ $italic = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['italic' => $italic]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->italic = $italic;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Superscript.
+ *
+ * @return null|bool
+ */
+ public function getSuperscript()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getSuperscript();
+ }
+
+ return $this->superscript;
+ }
+
+ /**
+ * Set Superscript.
+ *
+ * @return $this
+ */
+ public function setSuperscript(bool $superscript)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['superscript' => $superscript]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->superscript = $superscript;
+ if ($this->superscript) {
+ $this->subscript = false;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Subscript.
+ *
+ * @return null|bool
+ */
+ public function getSubscript()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getSubscript();
+ }
+
+ return $this->subscript;
+ }
+
+ /**
+ * Set Subscript.
+ *
+ * @return $this
+ */
+ public function setSubscript(bool $subscript)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['subscript' => $subscript]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->subscript = $subscript;
+ if ($this->subscript) {
+ $this->superscript = false;
+ }
+ }
+
+ return $this;
+ }
+
+ public function getBaseLine(): int
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getBaseLine();
+ }
+
+ return $this->baseLine;
+ }
+
+ public function setBaseLine(int $baseLine): self
+ {
+ if (!$this->isSupervisor) {
+ $this->baseLine = $baseLine;
+ } else {
+ // should never be true
+ // @codeCoverageIgnoreStart
+ $styleArray = $this->getStyleArray(['baseLine' => $baseLine]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ // @codeCoverageIgnoreEnd
+ }
+
+ return $this;
+ }
+
+ public function getStrikeType(): string
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getStrikeType();
+ }
+
+ return $this->strikeType;
+ }
+
+ public function setStrikeType(string $strikeType): self
+ {
+ if (!$this->isSupervisor) {
+ $this->strikeType = $strikeType;
+ } else {
+ // should never be true
+ // @codeCoverageIgnoreStart
+ $styleArray = $this->getStyleArray(['strikeType' => $strikeType]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ // @codeCoverageIgnoreEnd
+ }
+
+ return $this;
+ }
+
+ public function getUnderlineColor(): ?ChartColor
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getUnderlineColor();
+ }
+
+ return $this->underlineColor;
+ }
+
+ public function setUnderlineColor(array $colorArray): self
+ {
+ if (!$this->isSupervisor) {
+ $this->underlineColor = new ChartColor($colorArray);
+ } else {
+ // should never be true
+ // @codeCoverageIgnoreStart
+ $styleArray = $this->getStyleArray(['underlineColor' => $colorArray]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ // @codeCoverageIgnoreEnd
+ }
+
+ return $this;
+ }
+
+ public function getChartColor(): ?ChartColor
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getChartColor();
+ }
+
+ return $this->chartColor;
+ }
+
+ public function setChartColor(array $colorArray): self
+ {
+ if (!$this->isSupervisor) {
+ $this->chartColor = new ChartColor($colorArray);
+ } else {
+ // should never be true
+ // @codeCoverageIgnoreStart
+ $styleArray = $this->getStyleArray(['chartColor' => $colorArray]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ // @codeCoverageIgnoreEnd
+ }
+
+ return $this;
+ }
+
+ public function setChartColorFromObject(?ChartColor $chartColor): self
+ {
+ $this->chartColor = $chartColor;
+
+ return $this;
+ }
+
+ /**
+ * Get Underline.
+ *
+ * @return null|string
+ */
+ public function getUnderline()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getUnderline();
+ }
+
+ return $this->underline;
+ }
+
+ /**
+ * Set Underline.
+ *
+ * @param bool|string $underlineStyle \PhpOffice\PhpSpreadsheet\Style\Font underline type
+ * If a boolean is passed, then TRUE equates to UNDERLINE_SINGLE,
+ * false equates to UNDERLINE_NONE
+ *
+ * @return $this
+ */
+ public function setUnderline($underlineStyle)
+ {
+ if (is_bool($underlineStyle)) {
+ $underlineStyle = ($underlineStyle) ? self::UNDERLINE_SINGLE : self::UNDERLINE_NONE;
+ } elseif ($underlineStyle == '') {
+ $underlineStyle = self::UNDERLINE_NONE;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['underline' => $underlineStyle]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->underline = $underlineStyle;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Strikethrough.
+ *
+ * @return null|bool
+ */
+ public function getStrikethrough()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getStrikethrough();
+ }
+
+ return $this->strikethrough;
+ }
+
+ /**
+ * Set Strikethrough.
+ *
+ * @param bool $strikethru
+ *
+ * @return $this
+ */
+ public function setStrikethrough($strikethru)
+ {
+ if ($strikethru == '') {
+ $strikethru = false;
+ }
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['strikethrough' => $strikethru]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->strikethrough = $strikethru;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Color.
+ *
+ * @return Color
+ */
+ public function getColor()
+ {
+ return $this->color;
+ }
+
+ /**
+ * Set Color.
+ *
+ * @return $this
+ */
+ public function setColor(Color $color)
+ {
+ // make sure parameter is a real color and not a supervisor
+ $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color;
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getColor()->getStyleArray(['argb' => $color->getARGB()]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->color = $color;
+ }
+
+ return $this;
+ }
+
+ private function hashChartColor(?ChartColor $underlineColor): string
+ {
+ if ($underlineColor === null) {
+ return '';
+ }
+
+ return
+ $underlineColor->getValue()
+ . $underlineColor->getType()
+ . (string) $underlineColor->getAlpha();
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->name .
+ $this->size .
+ ($this->bold ? 't' : 'f') .
+ ($this->italic ? 't' : 'f') .
+ ($this->superscript ? 't' : 'f') .
+ ($this->subscript ? 't' : 'f') .
+ $this->underline .
+ ($this->strikethrough ? 't' : 'f') .
+ $this->color->getHashCode() .
+ $this->scheme .
+ implode(
+ '*',
+ [
+ $this->latin,
+ $this->eastAsian,
+ $this->complexScript,
+ $this->strikeType,
+ $this->hashChartColor($this->chartColor),
+ $this->hashChartColor($this->underlineColor),
+ (string) $this->baseLine,
+ ]
+ ) .
+ __CLASS__
+ );
+ }
+
+ protected function exportArray1(): array
+ {
+ $exportedArray = [];
+ $this->exportArray2($exportedArray, 'baseLine', $this->getBaseLine());
+ $this->exportArray2($exportedArray, 'bold', $this->getBold());
+ $this->exportArray2($exportedArray, 'chartColor', $this->getChartColor());
+ $this->exportArray2($exportedArray, 'color', $this->getColor());
+ $this->exportArray2($exportedArray, 'complexScript', $this->getComplexScript());
+ $this->exportArray2($exportedArray, 'eastAsian', $this->getEastAsian());
+ $this->exportArray2($exportedArray, 'italic', $this->getItalic());
+ $this->exportArray2($exportedArray, 'latin', $this->getLatin());
+ $this->exportArray2($exportedArray, 'name', $this->getName());
+ $this->exportArray2($exportedArray, 'scheme', $this->getScheme());
+ $this->exportArray2($exportedArray, 'size', $this->getSize());
+ $this->exportArray2($exportedArray, 'strikethrough', $this->getStrikethrough());
+ $this->exportArray2($exportedArray, 'strikeType', $this->getStrikeType());
+ $this->exportArray2($exportedArray, 'subscript', $this->getSubscript());
+ $this->exportArray2($exportedArray, 'superscript', $this->getSuperscript());
+ $this->exportArray2($exportedArray, 'underline', $this->getUnderline());
+ $this->exportArray2($exportedArray, 'underlineColor', $this->getUnderlineColor());
+
+ return $exportedArray;
+ }
+
+ public function getScheme(): string
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getScheme();
+ }
+
+ return $this->scheme;
+ }
+
+ public function setScheme(string $scheme): self
+ {
+ if ($scheme === '' || $scheme === 'major' || $scheme === 'minor') {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['scheme' => $scheme]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->scheme = $scheme;
+ }
+ }
+
+ return $this;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php
new file mode 100644
index 000000000..b605cff6e
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php
@@ -0,0 +1,457 @@
+formatCode = null;
+ $this->builtInFormatCode = false;
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return NumberFormat
+ */
+ public function getSharedComponent()
+ {
+ /** @var Style */
+ $parent = $this->parent;
+
+ return $parent->getSharedComponent()->getNumberFormat();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['numberFormat' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getNumberFormat()->applyFromArray(
+ * [
+ * 'formatCode' => NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE
+ * ]
+ * );
+ *
+ *
+ * @param array $styleArray Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $styleArray)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
+ } else {
+ if (isset($styleArray['formatCode'])) {
+ $this->setFormatCode($styleArray['formatCode']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Format Code.
+ *
+ * @return null|string
+ */
+ public function getFormatCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getFormatCode();
+ }
+ if (is_int($this->builtInFormatCode)) {
+ return self::builtInFormatCode($this->builtInFormatCode);
+ }
+
+ return $this->formatCode;
+ }
+
+ /**
+ * Set Format Code.
+ *
+ * @param string $formatCode see self::FORMAT_*
+ *
+ * @return $this
+ */
+ public function setFormatCode(string $formatCode)
+ {
+ if ($formatCode == '') {
+ $formatCode = self::FORMAT_GENERAL;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['formatCode' => $formatCode]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->formatCode = $formatCode;
+ $this->builtInFormatCode = self::builtInFormatCodeIndex($formatCode);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Built-In Format Code.
+ *
+ * @return false|int
+ */
+ public function getBuiltInFormatCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getBuiltInFormatCode();
+ }
+
+ // Scrutinizer says this could return true. It is wrong.
+ return $this->builtInFormatCode;
+ }
+
+ /**
+ * Set Built-In Format Code.
+ *
+ * @param int $formatCodeIndex Id of the built-in format code to use
+ *
+ * @return $this
+ */
+ public function setBuiltInFormatCode(int $formatCodeIndex)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['formatCode' => self::builtInFormatCode($formatCodeIndex)]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->builtInFormatCode = $formatCodeIndex;
+ $this->formatCode = self::builtInFormatCode($formatCodeIndex);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Fill built-in format codes.
+ */
+ private static function fillBuiltInFormatCodes(): void
+ {
+ // [MS-OI29500: Microsoft Office Implementation Information for ISO/IEC-29500 Standard Compliance]
+ // 18.8.30. numFmt (Number Format)
+ //
+ // The ECMA standard defines built-in format IDs
+ // 14: "mm-dd-yy"
+ // 22: "m/d/yy h:mm"
+ // 37: "#,##0 ;(#,##0)"
+ // 38: "#,##0 ;[Red](#,##0)"
+ // 39: "#,##0.00;(#,##0.00)"
+ // 40: "#,##0.00;[Red](#,##0.00)"
+ // 47: "mmss.0"
+ // KOR fmt 55: "yyyy-mm-dd"
+ // Excel defines built-in format IDs
+ // 14: "m/d/yyyy"
+ // 22: "m/d/yyyy h:mm"
+ // 37: "#,##0_);(#,##0)"
+ // 38: "#,##0_);[Red](#,##0)"
+ // 39: "#,##0.00_);(#,##0.00)"
+ // 40: "#,##0.00_);[Red](#,##0.00)"
+ // 47: "mm:ss.0"
+ // KOR fmt 55: "yyyy/mm/dd"
+
+ // Built-in format codes
+ if (empty(self::$builtInFormats)) {
+ self::$builtInFormats = [];
+
+ // General
+ self::$builtInFormats[0] = self::FORMAT_GENERAL;
+ self::$builtInFormats[1] = '0';
+ self::$builtInFormats[2] = '0.00';
+ self::$builtInFormats[3] = '#,##0';
+ self::$builtInFormats[4] = '#,##0.00';
+
+ self::$builtInFormats[9] = '0%';
+ self::$builtInFormats[10] = '0.00%';
+ self::$builtInFormats[11] = '0.00E+00';
+ self::$builtInFormats[12] = '# ?/?';
+ self::$builtInFormats[13] = '# ??/??';
+ self::$builtInFormats[14] = 'm/d/yyyy'; // Despite ECMA 'mm-dd-yy';
+ self::$builtInFormats[15] = 'd-mmm-yy';
+ self::$builtInFormats[16] = 'd-mmm';
+ self::$builtInFormats[17] = 'mmm-yy';
+ self::$builtInFormats[18] = 'h:mm AM/PM';
+ self::$builtInFormats[19] = 'h:mm:ss AM/PM';
+ self::$builtInFormats[20] = 'h:mm';
+ self::$builtInFormats[21] = 'h:mm:ss';
+ self::$builtInFormats[22] = 'm/d/yyyy h:mm'; // Despite ECMA 'm/d/yy h:mm';
+
+ self::$builtInFormats[37] = '#,##0_);(#,##0)'; // Despite ECMA '#,##0 ;(#,##0)';
+ self::$builtInFormats[38] = '#,##0_);[Red](#,##0)'; // Despite ECMA '#,##0 ;[Red](#,##0)';
+ self::$builtInFormats[39] = '#,##0.00_);(#,##0.00)'; // Despite ECMA '#,##0.00;(#,##0.00)';
+ self::$builtInFormats[40] = '#,##0.00_);[Red](#,##0.00)'; // Despite ECMA '#,##0.00;[Red](#,##0.00)';
+
+ self::$builtInFormats[44] = '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)';
+ self::$builtInFormats[45] = 'mm:ss';
+ self::$builtInFormats[46] = '[h]:mm:ss';
+ self::$builtInFormats[47] = 'mm:ss.0'; // Despite ECMA 'mmss.0';
+ self::$builtInFormats[48] = '##0.0E+0';
+ self::$builtInFormats[49] = '@';
+
+ // CHT
+ self::$builtInFormats[27] = '[$-404]e/m/d';
+ self::$builtInFormats[30] = 'm/d/yy';
+ self::$builtInFormats[36] = '[$-404]e/m/d';
+ self::$builtInFormats[50] = '[$-404]e/m/d';
+ self::$builtInFormats[57] = '[$-404]e/m/d';
+
+ // THA
+ self::$builtInFormats[59] = 't0';
+ self::$builtInFormats[60] = 't0.00';
+ self::$builtInFormats[61] = 't#,##0';
+ self::$builtInFormats[62] = 't#,##0.00';
+ self::$builtInFormats[67] = 't0%';
+ self::$builtInFormats[68] = 't0.00%';
+ self::$builtInFormats[69] = 't# ?/?';
+ self::$builtInFormats[70] = 't# ??/??';
+
+ // JPN
+ self::$builtInFormats[28] = '[$-411]ggge"ๅนด"m"ๆ"d"ๆฅ"';
+ self::$builtInFormats[29] = '[$-411]ggge"ๅนด"m"ๆ"d"ๆฅ"';
+ self::$builtInFormats[31] = 'yyyy"ๅนด"m"ๆ"d"ๆฅ"';
+ self::$builtInFormats[32] = 'h"ๆ"mm"ๅ"';
+ self::$builtInFormats[33] = 'h"ๆ"mm"ๅ"ss"็ง"';
+ self::$builtInFormats[34] = 'yyyy"ๅนด"m"ๆ"';
+ self::$builtInFormats[35] = 'm"ๆ"d"ๆฅ"';
+ self::$builtInFormats[51] = '[$-411]ggge"ๅนด"m"ๆ"d"ๆฅ"';
+ self::$builtInFormats[52] = 'yyyy"ๅนด"m"ๆ"';
+ self::$builtInFormats[53] = 'm"ๆ"d"ๆฅ"';
+ self::$builtInFormats[54] = '[$-411]ggge"ๅนด"m"ๆ"d"ๆฅ"';
+ self::$builtInFormats[55] = 'yyyy"ๅนด"m"ๆ"';
+ self::$builtInFormats[56] = 'm"ๆ"d"ๆฅ"';
+ self::$builtInFormats[58] = '[$-411]ggge"ๅนด"m"ๆ"d"ๆฅ"';
+
+ // Flip array (for faster lookups)
+ self::$flippedBuiltInFormats = array_flip(self::$builtInFormats);
+ }
+ }
+
+ /**
+ * Get built-in format code.
+ *
+ * @param int $index
+ *
+ * @return string
+ */
+ public static function builtInFormatCode($index)
+ {
+ // Clean parameter
+ $index = (int) $index;
+
+ // Ensure built-in format codes are available
+ self::fillBuiltInFormatCodes();
+
+ // Lookup format code
+ if (isset(self::$builtInFormats[$index])) {
+ return self::$builtInFormats[$index];
+ }
+
+ return '';
+ }
+
+ /**
+ * Get built-in format code index.
+ *
+ * @param string $formatCodeIndex
+ *
+ * @return false|int
+ */
+ public static function builtInFormatCodeIndex($formatCodeIndex)
+ {
+ // Ensure built-in format codes are available
+ self::fillBuiltInFormatCodes();
+
+ // Lookup format code
+ if (array_key_exists($formatCodeIndex, self::$flippedBuiltInFormats)) {
+ return self::$flippedBuiltInFormats[$formatCodeIndex];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->formatCode .
+ $this->builtInFormatCode .
+ __CLASS__
+ );
+ }
+
+ /**
+ * Convert a value in a pre-defined format to a PHP string.
+ *
+ * @param mixed $value Value to format
+ * @param string $format Format code: see = self::FORMAT_* for predefined values;
+ * or can be any valid MS Excel custom format string
+ * @param array $callBack Callback function for additional formatting of string
+ *
+ * @return string Formatted string
+ */
+ public static function toFormattedString($value, $format, $callBack = null)
+ {
+ return NumberFormat\Formatter::toFormattedString($value, $format, $callBack);
+ }
+
+ protected function exportArray1(): array
+ {
+ $exportedArray = [];
+ $this->exportArray2($exportedArray, 'formatCode', $this->getFormatCode());
+
+ return $exportedArray;
+ }
+}
diff --git a/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php
new file mode 100644
index 000000000..7988143c5
--- /dev/null
+++ b/libraries/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php
@@ -0,0 +1,12 @@
+ '',
+ // 12-hour suffix
+ 'am/pm' => 'A',
+ // 4-digit year
+ 'e' => 'Y',
+ 'yyyy' => 'Y',
+ // 2-digit year
+ 'yy' => 'y',
+ // first letter of month - no php equivalent
+ 'mmmmm' => 'M',
+ // full month name
+ 'mmmm' => 'F',
+ // short month name
+ 'mmm' => 'M',
+ // mm is minutes if time, but can also be month w/leading zero
+ // so we try to identify times be the inclusion of a : separator in the mask
+ // It isn't perfect, but the best way I know how
+ ':mm' => ':i',
+ 'mm:' => 'i:',
+ // full day of week name
+ 'dddd' => 'l',
+ // short day of week name
+ 'ddd' => 'D',
+ // days leading zero
+ 'dd' => 'd',
+ // days no leading zero
+ 'd' => 'j',
+ // fractional seconds - no php equivalent
+ '.s' => '',
+ ];
+
+ /**
+ * Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock).
+ */
+ private const DATE_FORMAT_REPLACEMENTS24 = [
+ 'hh' => 'H',
+ 'h' => 'G',
+ // month leading zero
+ 'mm' => 'm',
+ // month no leading zero
+ 'm' => 'n',
+ // seconds
+ 'ss' => 's',
+ ];
+
+ /**
+ * Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock).
+ */
+ private const DATE_FORMAT_REPLACEMENTS12 = [
+ 'hh' => 'h',
+ 'h' => 'g',
+ // month leading zero
+ 'mm' => 'm',
+ // month no leading zero
+ 'm' => 'n',
+ // seconds
+ 'ss' => 's',
+ ];
+
+ private const HOURS_IN_DAY = 24;
+ private const MINUTES_IN_DAY = 60 * self::HOURS_IN_DAY;
+ private const SECONDS_IN_DAY = 60 * self::MINUTES_IN_DAY;
+ private const INTERVAL_PRECISION = 10;
+ private const INTERVAL_LEADING_ZERO = [
+ '[hh]',
+ '[mm]',
+ '[ss]',
+ ];
+ private const INTERVAL_ROUND_PRECISION = [
+ // hours and minutes truncate
+ '[h]' => self::INTERVAL_PRECISION,
+ '[hh]' => self::INTERVAL_PRECISION,
+ '[m]' => self::INTERVAL_PRECISION,
+ '[mm]' => self::INTERVAL_PRECISION,
+ // seconds round
+ '[s]' => 0,
+ '[ss]' => 0,
+ ];
+ private const INTERVAL_MULTIPLIER = [
+ '[h]' => self::HOURS_IN_DAY,
+ '[hh]' => self::HOURS_IN_DAY,
+ '[m]' => self::MINUTES_IN_DAY,
+ '[mm]' => self::MINUTES_IN_DAY,
+ '[s]' => self::SECONDS_IN_DAY,
+ '[ss]' => self::SECONDS_IN_DAY,
+ ];
+
+ /** @param mixed $value */
+ private static function tryInterval(bool &$seekingBracket, string &$block, $value, string $format): void
+ {
+ if ($seekingBracket) {
+ if (false !== strpos($block, $format)) {
+ $hours = (string) (int) round(
+ self::INTERVAL_MULTIPLIER[$format] * $value,
+ self::INTERVAL_ROUND_PRECISION[$format]
+ );
+ if (strlen($hours) === 1 && in_array($format, self::INTERVAL_LEADING_ZERO, true)) {
+ $hours = "0$hours";
+ }
+ $block = str_replace($format, $hours, $block);
+ $seekingBracket = false;
+ }
+ }
+ }
+
+ /** @param mixed $value */
+ public static function format($value, string $format): string
+ {
+ // strip off first part containing e.g. [$-F800] or [$USD-409]
+ // general syntax: [$