<?php

namespace PhpOffice\PhpSpreadsheet\Writer\Pdf;

use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
use PhpOffice\PhpSpreadsheet\Writer\Pdf;

class Mpdf extends Pdf
{
    /**
     * Gets the implementation of external PDF library that should be used.
     *
     * @param array $config Configuration array
     *
     * @return \Mpdf\Mpdf implementation
     */
    protected function createExternalWriterInstance($config)
    {
        return new \Mpdf\Mpdf($config);
    }

    /**
     * Save Spreadsheet to file.
     *
     * @param string $pFilename Name of the file to save as
     *
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
     * @throws PhpSpreadsheetException
     */
    public function save($pFilename)
    {
        $fileHandle = parent::prepareForSave($pFilename);

        //  Default PDF paper size
        $paperSize = 'LETTER'; //    Letter    (8.5 in. by 11 in.)

        //  Check for paper size and page orientation
        if (null === $this->getSheetIndex()) {
            $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation()
                == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
            $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize();
        } else {
            $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation()
                == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
            $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize();
        }
        $this->setOrientation($orientation);

        //  Override Page Orientation
        if (null !== $this->getOrientation()) {
            $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_DEFAULT)
                ? PageSetup::ORIENTATION_PORTRAIT
                : $this->getOrientation();
        }
        $orientation = strtoupper($orientation);

        //  Override Paper Size
        if (null !== $this->getPaperSize()) {
            $printPaperSize = $this->getPaperSize();
        }

        if (isset(self::$paperSizes[$printPaperSize])) {
            $paperSize = self::$paperSizes[$printPaperSize];
        }

        //  Create PDF
        $config = ['tempDir' => $this->tempDir];
        $pdf = $this->createExternalWriterInstance($config);
        $ortmp = $orientation;
        $pdf->_setPageSize(strtoupper($paperSize), $ortmp);
        $pdf->DefOrientation = $orientation;
        $pdf->AddPage($orientation);

        //  Document info
        $pdf->SetTitle($this->spreadsheet->getProperties()->getTitle());
        $pdf->SetAuthor($this->spreadsheet->getProperties()->getCreator());
        $pdf->SetSubject($this->spreadsheet->getProperties()->getSubject());
        $pdf->SetKeywords($this->spreadsheet->getProperties()->getKeywords());
        $pdf->SetCreator($this->spreadsheet->getProperties()->getCreator());

        $pdf->WriteHTML(
            $this->generateHTMLHeader(false) .
            $this->generateSheetData() .
            $this->generateHTMLFooter()
        );

        //  Write to file
        fwrite($fileHandle, $pdf->Output('', 'S'));

        parent::restoreStateAfterSave($fileHandle);
    }
}


<?php

namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;

class SpgrContainer
{
    /**
     * Parent Shape Group Container.
     *
     * @var \PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer
     */
    private $parent;

    /**
     * Shape Container collection.
     *
     * @var array
     */
    private $children = [];

    /**
     * Set parent Shape Group Container.
     *
     * @param \PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer $parent
     */
    public function setParent($parent)
    {
        $this->parent = $parent;
    }

    /**
     * Get the parent Shape Group Container if any.
     *
     * @return null|\PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer
     */
    public function getParent()
    {
        return $this->parent;
    }

    /**
     * Add a child. This will be either spgrContainer or spContainer.
     *
     * @param mixed $child
     */
    public function addChild($child)
    {
        $this->children[] = $child;
        $child->setParent($this);
    }

    /**
     * Get collection of Shape Containers.
     */
    public function getChildren()
    {
        return $this->children;
    }

    /**
     * Recursively get all spContainers within this spgrContainer.
     *
     * @return SpgrContainer\SpContainer[]
     */
    public function getAllSpContainers()
    {
        $allSpContainers = [];

        foreach ($this->children as $child) {
            if ($child instanceof self) {
                $allSpContainers = array_merge($allSpContainers, $child->getAllSpContainers());
            } else {
                $allSpContainers[] = $child;
            }
        }

        return $allSpContainers;
    }
}


<?php
/**
 * Abstract Request
 */
namespace Omniship\Message;

use Omniship\Common\AbstractGateway;
use Omniship\Exceptions\InvalidRequestException;
use Omniship\Exceptions\RuntimeException;
use Omniship\Helper\Helper;
use Omniship\Http\Client;
use Omniship\Interfaces\GatewayInterface;
use Omniship\Interfaces\RequestInterface;
use Omniship\Interfaces\ResponseInterface;
use Omniship\Traits\Exceptions;
use Omniship\Traits\Parameters;
use Omniship\Traits\ParametersData;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request as HttpRequest;

/**
 * Abstract Request
 *
 * This abstract class implements RequestInterface and defines a basic
 * set of functions that all Omniship Requests are intended to include.
 *
 * Requests of this class are usually created using the createRequest
 * function of the gateway and then actioned using methods within this
 * class or a class that extends this class.
 *
 * Example -- creating a request:
 *
 *
 *   class MyRequest extends \Omniship\Message\AbstractRequest {};
 *
 *   class MyGateway extends \Omniship\Common\AbstractGateway {
 *     function myRequest($parameters) {
 *       $this->createRequest('MyRequest', $parameters);
 *     }
 *   }
 *
 *   // Create the gateway object
 *   $gw = Omniship::create('MyGateway');
 *
 *   // Create the request object
 *   $myRequest = $gw->myRequest($someParameters);
 *
 *
 * Example -- validating and sending a request:
 *
 *
 *   try {
 *     $myRequest->validate();
 *     $myResponse = $myRequest->send();
 *   } catch (InvalidRequestException $e) {
 *     print "Something went wrong: " . $e->getMessage() . "\n";
 *   }
 *   // now do something with the $myResponse object, test for success, etc.
 *
 *
 * @see RequestInterface
 * @see AbstractResponse
 */
abstract class AbstractRequest implements RequestInterface
{

    use Exceptions, ParametersData, Parameters {
        Parameters::__construct AS parametersConstruct;
    }
    /**
     * The request client.
     *
     * @var Client
     */
    protected $httpClient;
    /**
     * The HTTP request object.
     *
     * @var \Symfony\Component\HttpFoundation\Request
     */
    protected $httpRequest;
    /**
     * An associated ResponseInterface.
     *
     * @var ResponseInterface
     */
    protected $response;
    /**
     * An associated AbstractGateway.
     *
     * @var AbstractGateway
     */
    protected $gateway;
    /**
     * Create a new Request
     *
     * @param Client $httpClient  A HTTP client to make API calls with
     * @param HttpRequest     $httpRequest A Symfony HTTP request object
     */
    public function __construct(Client $httpClient, HttpRequest $httpRequest)
    {
        $this->httpClient = $httpClient;
        $this->httpRequest = $httpRequest;
        $this->parametersConstruct();
    }
    /**
     * Initialize the object with parameters.
     *
     * If any unknown parameters passed, they will be ignored.
     *
     * @param array $parameters An associative array of parameters
     *
     * @return $this
     * @throws RuntimeException
     */
    public function initialize(array $parameters = array())
    {
        if (null !== $this->response) {
            throw new RuntimeException('Request cannot be modified after it has been sent!');
        }
        $this->parameters = new ParameterBag;
        Helper::initialize($this, $parameters);
        return $this;
    }

    /**
     * @param GatewayInterface $gateway
     * @return $this
     */
    public function setGateway(GatewayInterface $gateway)
    {
        $this->gateway = $gateway;
        return $this;
    }

    /**
     * @return AbstractGateway $gateway
     */
    public function getGateway()
    {
        return $this->gateway;
    }

    /**
     * Set a single parameter
     *
     * @param string $key The parameter key
     * @param mixed $value The value to set
     * @return $this Provides a fluent interface
     * @throws RuntimeException if a request parameter is modified after the request has been sent.
     */
    public function setParameter($key, $value)
    {
        if (null !== $this->response) {
            throw new RuntimeException('Request cannot be modified after it has been sent!');
        }
        $this->parameters->set($key, $value);
        return $this;
    }
    /**
     * Validate the request.
     *
     * This method is called internally by gateways to avoid wasting time with an API call
     * when the request is clearly invalid.
     *
     * @param string ... a variable length list of required parameters
     * @throws InvalidRequestException
     */
    public function validate()
    {
        foreach (func_get_args() as $key) {
            $value = $this->parameters->get($key);
            if (! isset($value)) {
                throw new InvalidRequestException("The $key parameter is required");
            }
        }
    }
    /**
     * Send the request
     *
     * @return ResponseInterface
     */
    public function send()
    {
        $data = $this->getData();
        return $this->sendData($data);
    }
    /**
     * Get the associated Response.
     *
     * @return ResponseInterface
     */
    public function getResponse()
    {
        if (null === $this->response) {
            throw new RuntimeException('You must call send() before accessing the Response!');
        }
        return $this->response;
    }
    /**
     * {@inheritdoc}
     */
    public function getRequestFormatted()
    {
        return null;
    }
}


<?php

namespace joshmoody\Mock\Models;

use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Database\Eloquent\Builder as QueryBuilder;
use Prelude\Database\DsnParser;

class Database
{
	public static $driver = [];

	/**
	 * Initialize the connection to the database.
	 *
	 * @param null $config
	 */
	public static function init($config = null)
	{
		$defaults = [
			'driver' => 'sqlite',
			'host'		=> null,
			'database'	=> dirname(dirname(__DIR__)) . '/data/database.sqlite',
			'username'	=> null,
			'password'	=> null,
			'charset'	=> 'utf8',
			'collation'	=> 'utf8_unicode_ci',
			'prefix'	=> null
		];

		$capsule = new Capsule;

		if (is_string($config)) {
			$config = self::parseDsn($config);
		}

		if (is_array($config)) {

			// missing 'driver' key, so it must be an array of arrays
			if (!array_key_exists('driver', $config)) {

				// if we have an array of connections, iterate through them.  connections should be stored in the form of name => conn_info
				foreach ($config as $connection_name => $connection_info) {

					// if it's a dsn string, then parse it
					if (is_string($connection_info)) {
						$connection_info = self::parseDsn($connection_info);
					}

					// now merge it into our options
					$options[$connection_name] = array_merge($defaults, $connection_info);
				}
			} else {
				$options['mock-data'] = array_merge($defaults, $config);
			}
		} else {
			$options['mock-data'] = $defaults;
		}

		// add each connection, then set as global and boot
		foreach ($options as $name => $info) {
			$capsule->addConnection($info, $name);
			self::$driver[$name] = $info['driver'];
		}

		$capsule->setAsGlobal();
		$capsule->bootEloquent();

		// determine if we should log queries or not
		foreach ($options as $name => $info) {

			// make sure we use FALSE to disable queries, otherwise it'll just default to logging queries
			if (isset($info['log_queries']) && $info['log_queries'] === false) {
				Capsule::connection($name)->disableQueryLog();
			} else {
				Capsule::connection($name)->enableQueryLog();
			}
		}
	}

	/**
	 * Return the name of the random function based on the SQL dialect being used.
	 *
	 * @param string $connection_name
	 * @return string
	 */
	public static function random($connection_name = 'mock-data')
	{
		if (self::$driver[$connection_name] == 'sqlite') {
			return 'random()';
		} else {
			return 'rand()';
		}
	}

	/**
	 * Take a string DSN and parse it into an array of its pieces
	 *
	 * @param null $string
	 * @return array|null
	 */
	public static function parseDsn($string = null)
	{
		$opts = null;

		if (!empty($string)) {
			$dsn = (object) DsnParser::parseUrl($string)->toArray();

			$opts = [
				'driver'	=> $dsn->driver,
				'host'		=> $dsn->host,
				'database'	=> $dsn->dbname,
				'username'	=> $dsn->user,
				'password'	=> isset($dsn->pass) ? $dsn->pass : null
			];
		}

		return $opts;
	}

	/**
	 * Get a preview of what query will be run from a query builder.
	 *
	 * This DOES NOT run the query so it can be used for debugging potentially memory-intensive queries.
	 *
	 * @param QueryBuilder $query
	 * @return string
	 */
	public static function getQueryPreview(QueryBuilder $query = null)
	{
		if (empty($query)) {
			return "";
		}

		$sql        = str_replace('?', "'%s'", $query->toSql());
		$bindings   = $query->getBindings();

		return vsprintf($sql, $bindings);
	}

	/**
	 * Get the last query that was run with data that was used bound to it.
	 *
	 * @param string $connection
	 * @return string
	 */
	public static function getLastQuery($connection = "")
	{
		$last_query = "";
		$pretty_queries = self::getPrettyQueryLog($connection);

		if (!empty($pretty_queries)) {
			$last_query = $pretty_queries[ count($pretty_queries) - 1 ];
		}

		return $last_query;
	}

	/**
	 * Get a list of all queries formatted with their bindings in place
	 *
	 * @param string $connection
	 * @return array
	 */
	public static function getPrettyQueryLog($connection = "")
	{
		$return_queries = [];

		$queries = Capsule::connection($connection)->getQueryLog();

		foreach ($queries as $query) {
			$query_pattern = str_replace('?', "'%s'", $query['query']);
			$return_queries[] = vsprintf($query_pattern, $query['bindings']);
		}

		return $return_queries;
	}
}


<?php

namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;

use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\RichText\Run;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;

class StringTable extends WriterPart
{
    /**
     * Create worksheet stringtable.
     *
     * @param Worksheet $pSheet Worksheet
     * @param string[] $pExistingTable Existing table to eventually merge with
     *
     * @return string[] String table for worksheet
     */
    public function createStringTable(Worksheet $pSheet, $pExistingTable = null)
    {
        // Create string lookup table
        $aStringTable = [];
        $cellCollection = null;
        $aFlippedStringTable = null; // For faster lookup

        // Is an existing table given?
        if (($pExistingTable !== null) && is_array($pExistingTable)) {
            $aStringTable = $pExistingTable;
        }

        // Fill index array
        $aFlippedStringTable = $this->flipStringTable($aStringTable);

        // Loop through cells
        foreach ($pSheet->getCoordinates() as $coordinate) {
            $cell = $pSheet->getCell($coordinate);
            $cellValue = $cell->getValue();
            if (!is_object($cellValue) &&
                ($cellValue !== null) &&
                $cellValue !== '' &&
                !isset($aFlippedStringTable[$cellValue]) &&
                ($cell->getDataType() == DataType::TYPE_STRING || $cell->getDataType() == DataType::TYPE_STRING2 || $cell->getDataType() == DataType::TYPE_NULL)) {
                $aStringTable[] = $cellValue;
                $aFlippedStringTable[$cellValue] = true;
            } elseif ($cellValue instanceof RichText &&
                ($cellValue !== null) &&
                !isset($aFlippedStringTable[$cellValue->getHashCode()])) {
                $aStringTable[] = $cellValue;
                $aFlippedStringTable[$cellValue->getHashCode()] = true;
            }
        }

        return $aStringTable;
    }

    /**
     * Write string table to XML format.
     *
     * @param string[] $pStringTable
     *
     * @throws WriterException
     *
     * @return string XML Output
     */
    public function writeStringTable(array $pStringTable)
    {
        // Create XML writer
        $objWriter = null;
        if ($this->getParentWriter()->getUseDiskCaching()) {
            $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
        } else {
            $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
        }

        // XML header
        $objWriter->startDocument('1.0', 'UTF-8', 'yes');

        // String table
        $objWriter->startElement('sst');
        $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
        $objWriter->writeAttribute('uniqueCount', count($pStringTable));

        // Loop through string table
        foreach ($pStringTable as $textElement) {
            $objWriter->startElement('si');

            if (!$textElement instanceof RichText) {
                $textToWrite = StringHelper::controlCharacterPHP2OOXML($textElement);
                $objWriter->startElement('t');
                if ($textToWrite !== trim($textToWrite)) {
                    $objWriter->writeAttribute('xml:space', 'preserve');
                }
                $objWriter->writeRawData($textToWrite);
                $objWriter->endElement();
            } elseif ($textElement instanceof RichText) {
                $this->writeRichText($objWriter, $textElement);
            }

            $objWriter->endElement();
        }

        $objWriter->endElement();

        return $objWriter->getData();
    }

    /**
     * Write Rich Text.
     *
     * @param XMLWriter $objWriter XML Writer
     * @param RichText $pRichText Rich text
     * @param string $prefix Optional Namespace prefix
     */
    public function writeRichText(XMLWriter $objWriter, RichText $pRichText, $prefix = null)
    {
        if ($prefix !== null) {
            $prefix .= ':';
        }

        // Loop through rich text elements
        $elements = $pRichText->getRichTextElements();
        foreach ($elements as $element) {
            // r
            $objWriter->startElement($prefix . 'r');

            // rPr
            if ($element instanceof Run) {
                // rPr
                $objWriter->startElement($prefix . 'rPr');

                // rFont
                $objWriter->startElement($prefix . 'rFont');
                $objWriter->writeAttribute('val', $element->getFont()->getName());
                $objWriter->endElement();

                // Bold
                $objWriter->startElement($prefix . 'b');
                $objWriter->writeAttribute('val', ($element->getFont()->getBold() ? 'true' : 'false'));
                $objWriter->endElement();

                // Italic
                $objWriter->startElement($prefix . 'i');
                $objWriter->writeAttribute('val', ($element->getFont()->getItalic() ? 'true' : 'false'));
                $objWriter->endElement();

                // Superscript / subscript
                if ($element->getFont()->getSuperscript() || $element->getFont()->getSubscript()) {
                    $objWriter->startElement($prefix . 'vertAlign');
                    if ($element->getFont()->getSuperscript()) {
                        $objWriter->writeAttribute('val', 'superscript');
                    } elseif ($element->getFont()->getSubscript()) {
                        $objWriter->writeAttribute('val', 'subscript');
                    }
                    $objWriter->endElement();
                }

                // Strikethrough
                $objWriter->startElement($prefix . 'strike');
                $objWriter->writeAttribute('val', ($element->getFont()->getStrikethrough() ? 'true' : 'false'));
                $objWriter->endElement();

                // Color
                $objWriter->startElement($prefix . 'color');
                $objWriter->writeAttribute('rgb', $element->getFont()->getColor()->getARGB());
                $objWriter->endElement();

                // Size
                $objWriter->startElement($prefix . 'sz');
                $objWriter->writeAttribute('val', $element->getFont()->getSize());
                $objWriter->endElement();

                // Underline
                $objWriter->startElement($prefix . 'u');
                $objWriter->writeAttribute('val', $element->getFont()->getUnderline());
                $objWriter->endElement();

                $objWriter->endElement();
            }

            // t
            $objWriter->startElement($prefix . 't');
            $objWriter->writeAttribute('xml:space', 'preserve');
            $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($element->getText()));
            $objWriter->endElement();

            $objWriter->endElement();
        }
    }

    /**
     * Write Rich Text.
     *
     * @param XMLWriter $objWriter XML Writer
     * @param RichText|string $pRichText text string or Rich text
     * @param string $prefix Optional Namespace prefix
     */
    public function writeRichTextForCharts(XMLWriter $objWriter, $pRichText = null, $prefix = null)
    {
        if (!$pRichText instanceof RichText) {
            $textRun = $pRichText;
            $pRichText = new RichText();
            $pRichText->createTextRun($textRun);
        }

        if ($prefix !== null) {
            $prefix .= ':';
        }

        // Loop through rich text elements
        $elements = $pRichText->getRichTextElements();
        foreach ($elements as $element) {
            // r
            $objWriter->startElement($prefix . 'r');

            // rPr
            $objWriter->startElement($prefix . 'rPr');

            // Bold
            $objWriter->writeAttribute('b', ($element->getFont()->getBold() ? 1 : 0));
            // Italic
            $objWriter->writeAttribute('i', ($element->getFont()->getItalic() ? 1 : 0));
            // Underline
            $underlineType = $element->getFont()->getUnderline();
            switch ($underlineType) {
                case 'single':
                    $underlineType = 'sng';

                    break;
                case 'double':
                    $underlineType = 'dbl';

                    break;
            }
            $objWriter->writeAttribute('u', $underlineType);
            // Strikethrough
            $objWriter->writeAttribute('strike', ($element->getFont()->getStrikethrough() ? 'sngStrike' : 'noStrike'));

            // rFont
            $objWriter->startElement($prefix . 'latin');
            $objWriter->writeAttribute('typeface', $element->getFont()->getName());
            $objWriter->endElement();

            $objWriter->endElement();

            // t
            $objWriter->startElement($prefix . 't');
            $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($element->getText()));
            $objWriter->endElement();

            $objWriter->endElement();
        }
    }

    /**
     * Flip string table (for index searching).
     *
     * @param array $stringTable Stringtable
     *
     * @return array
     */
    public function flipStringTable(array $stringTable)
    {
        // Return value
        $returnValue = [];

        // Loop through stringtable and add flipped items to $returnValue
        foreach ($stringTable as $key => $value) {
            if (!$value instanceof RichText) {
                $returnValue[$value] = $key;
            } elseif ($value instanceof RichText) {
                $returnValue[$value->getHashCode()] = $key;
            }
        }

        return $returnValue;
    }
}


<?php

namespace PhpOffice\PhpSpreadsheet\Writer\Ods;

use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Font;
use PhpOffice\PhpSpreadsheet\Worksheet\Row;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Exception;
use PhpOffice\PhpSpreadsheet\Writer\Ods;
use PhpOffice\PhpSpreadsheet\Writer\Ods\Cell\Comment;

/**
 * @category   PhpSpreadsheet
 *
 * @method Ods getParentWriter
 *
 * @copyright  Copyright (c) 2006 - 2015 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet)
 * @author     Alexander Pervakov <frost-nzcr4@jagmort.com>
 */
class Content extends WriterPart
{
    const NUMBER_COLS_REPEATED_MAX = 1024;
    const NUMBER_ROWS_REPEATED_MAX = 1048576;
    const CELL_STYLE_PREFIX = 'ce';

    /**
     * Write content.xml to XML format.
     *
     * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
     *
     * @return string XML Output
     */
    public function write()
    {
        $objWriter = null;
        if ($this->getParentWriter()->getUseDiskCaching()) {
            $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
        } else {
            $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
        }

        // XML header
        $objWriter->startDocument('1.0', 'UTF-8');

        // Content
        $objWriter->startElement('office:document-content');
        $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
        $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0');
        $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0');
        $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0');
        $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0');
        $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0');
        $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
        $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
        $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
        $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0');
        $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0');
        $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0');
        $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0');
        $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0');
        $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML');
        $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0');
        $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0');
        $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
        $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer');
        $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc');
        $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events');
        $objWriter->writeAttribute('xmlns:xforms', 'http://www.w3.org/2002/xforms');
        $objWriter->writeAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema');
        $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
        $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report');
        $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2');
        $objWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml');
        $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#');
        $objWriter->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table');
        $objWriter->writeAttribute('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0');
        $objWriter->writeAttribute('xmlns:formx', 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0');
        $objWriter->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/');
        $objWriter->writeAttribute('office:version', '1.2');

        $objWriter->writeElement('office:scripts');
        $objWriter->writeElement('office:font-face-decls');

        // Styles XF
        $objWriter->startElement('office:automatic-styles');
        $this->writeXfStyles($objWriter, $this->getParentWriter()->getSpreadsheet());
        $objWriter->endElement();

        $objWriter->startElement('office:body');
        $objWriter->startElement('office:spreadsheet');
        $objWriter->writeElement('table:calculation-settings');

        $this->writeSheets($objWriter);

        $objWriter->writeElement('table:named-expressions');
        $objWriter->endElement();
        $objWriter->endElement();
        $objWriter->endElement();

        return $objWriter->getData();
    }

    /**
     * Write sheets.
     *
     * @param XMLWriter $objWriter
     */
    private function writeSheets(XMLWriter $objWriter)
    {
        $spreadsheet = $this->getParentWriter()->getSpreadsheet(); // @var $spreadsheet Spreadsheet

        $sheetCount = $spreadsheet->getSheetCount();
        for ($i = 0; $i < $sheetCount; ++$i) {
            $objWriter->startElement('table:table');
            $objWriter->writeAttribute('table:name', $spreadsheet->getSheet($i)->getTitle());
            $objWriter->writeElement('office:forms');
            $objWriter->startElement('table:table-column');
            $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
            $objWriter->endElement();
            $this->writeRows($objWriter, $spreadsheet->getSheet($i));
            $objWriter->endElement();
        }
    }

    /**
     * Write rows of the specified sheet.
     *
     * @param XMLWriter $objWriter
     * @param Worksheet $sheet
     */
    private function writeRows(XMLWriter $objWriter, Worksheet $sheet)
    {
        $numberRowsRepeated = self::NUMBER_ROWS_REPEATED_MAX;
        $span_row = 0;
        $rows = $sheet->getRowIterator();
        while ($rows->valid()) {
            --$numberRowsRepeated;
            $row = $rows->current();
            if ($row->getCellIterator()->valid()) {
                if ($span_row) {
                    $objWriter->startElement('table:table-row');
                    if ($span_row > 1) {
                        $objWriter->writeAttribute('table:number-rows-repeated', $span_row);
                    }
                    $objWriter->startElement('table:table-cell');
                    $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
                    $objWriter->endElement();
                    $objWriter->endElement();
                    $span_row = 0;
                }
                $objWriter->startElement('table:table-row');
                $this->writeCells($objWriter, $row);
                $objWriter->endElement();
            } else {
                ++$span_row;
            }
            $rows->next();
        }
    }

    /**
     * Write cells of the specified row.
     *
     * @param XMLWriter $objWriter
     * @param Row $row
     *
     * @throws Exception
     */
    private function writeCells(XMLWriter $objWriter, Row $row)
    {
        $numberColsRepeated = self::NUMBER_COLS_REPEATED_MAX;
        $prevColumn = -1;
        $cells = $row->getCellIterator();
        while ($cells->valid()) {
            /** @var \PhpOffice\PhpSpreadsheet\Cell\Cell $cell */
            $cell = $cells->current();
            $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1;

            $this->writeCellSpan($objWriter, $column, $prevColumn);
            $objWriter->startElement('table:table-cell');
            $this->writeCellMerge($objWriter, $cell);

            // Style XF
            $style = $cell->getXfIndex();
            if ($style !== null) {
                $objWriter->writeAttribute('table:style-name', self::CELL_STYLE_PREFIX . $style);
            }

            switch ($cell->getDataType()) {
                case DataType::TYPE_BOOL:
                    $objWriter->writeAttribute('office:value-type', 'boolean');
                    $objWriter->writeAttribute('office:value', $cell->getValue());
                    $objWriter->writeElement('text:p', $cell->getValue());

                    break;
                case DataType::TYPE_ERROR:
                    throw new Exception('Writing of error not implemented yet.');

                    break;
                case DataType::TYPE_FORMULA:
                    $formulaValue = $cell->getValue();
                    if ($this->getParentWriter()->getPreCalculateFormulas()) {
                        try {
                            $formulaValue = $cell->getCalculatedValue();
                        } catch (Exception $e) {
                            // don't do anything
                        }
                    }
                    $objWriter->writeAttribute('table:formula', 'of:' . $cell->getValue());
                    if (is_numeric($formulaValue)) {
                        $objWriter->writeAttribute('office:value-type', 'float');
                    } else {
                        $objWriter->writeAttribute('office:value-type', 'string');
                    }
                    $objWriter->writeAttribute('office:value', $formulaValue);
                    $objWriter->writeElement('text:p', $formulaValue);

                    break;
                case DataType::TYPE_INLINE:
                    throw new Exception('Writing of inline not implemented yet.');

                    break;
                case DataType::TYPE_NUMERIC:
                    $objWriter->writeAttribute('office:value-type', 'float');
                    $objWriter->writeAttribute('office:value', $cell->getValue());
                    $objWriter->writeElement('text:p', $cell->getValue());

                    break;
                case DataType::TYPE_STRING:
                    $objWriter->writeAttribute('office:value-type', 'string');
                    $objWriter->writeElement('text:p', $cell->getValue());

                    break;
            }
            Comment::write($objWriter, $cell);
            $objWriter->endElement();
            $prevColumn = $column;
            $cells->next();
        }
        $numberColsRepeated = $numberColsRepeated - $prevColumn - 1;
        if ($numberColsRepeated > 0) {
            if ($numberColsRepeated > 1) {
                $objWriter->startElement('table:table-cell');
                $objWriter->writeAttribute('table:number-columns-repeated', $numberColsRepeated);
                $objWriter->endElement();
            } else {
                $objWriter->writeElement('table:table-cell');
            }
        }
    }

    /**
     * Write span.
     *
     * @param XMLWriter $objWriter
     * @param int $curColumn
     * @param int $prevColumn
     */
    private function writeCellSpan(XMLWriter $objWriter, $curColumn, $prevColumn)
    {
        $diff = $curColumn - $prevColumn - 1;
        if (1 === $diff) {
            $objWriter->writeElement('table:table-cell');
        } elseif ($diff > 1) {
            $objWriter->startElement('table:table-cell');
            $objWriter->writeAttribute('table:number-columns-repeated', $diff);
            $objWriter->endElement();
        }
    }

    /**
     * Write XF cell styles.
     *
     * @param XMLWriter $writer
     * @param Spreadsheet $spreadsheet
     */
    private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet)
    {
        foreach ($spreadsheet->getCellXfCollection() as $style) {
            $writer->startElement('style:style');
            $writer->writeAttribute('style:name', self::CELL_STYLE_PREFIX . $style->getIndex());
            $writer->writeAttribute('style:family', 'table-cell');
            $writer->writeAttribute('style:parent-style-name', 'Default');

            // style:text-properties

            // Font
            $writer->startElement('style:text-properties');

            $font = $style->getFont();

            if ($font->getBold()) {
                $writer->writeAttribute('fo:font-weight', 'bold');
                $writer->writeAttribute('style:font-weight-complex', 'bold');
                $writer->writeAttribute('style:font-weight-asian', 'bold');
            }

            if ($font->getItalic()) {
                $writer->writeAttribute('fo:font-style', 'italic');
            }

            if ($color = $font->getColor()) {
                $writer->writeAttribute('fo:color', sprintf('#%s', $color->getRGB()));
            }

            if ($family = $font->getName()) {
                $writer->writeAttribute('fo:font-family', $family);
            }

            if ($size = $font->getSize()) {
                $writer->writeAttribute('fo:font-size', sprintf('%.1fpt', $size));
            }

            if ($font->getUnderline() && $font->getUnderline() != Font::UNDERLINE_NONE) {
                $writer->writeAttribute('style:text-underline-style', 'solid');
                $writer->writeAttribute('style:text-underline-width', 'auto');
                $writer->writeAttribute('style:text-underline-color', 'font-color');

                switch ($font->getUnderline()) {
                    case Font::UNDERLINE_DOUBLE:
                        $writer->writeAttribute('style:text-underline-type', 'double');

                        break;
                    case Font::UNDERLINE_SINGLE:
                        $writer->writeAttribute('style:text-underline-type', 'single');

                        break;
                }
            }

            $writer->endElement(); // Close style:text-properties

            // style:table-cell-properties

            $writer->startElement('style:table-cell-properties');
            $writer->writeAttribute('style:rotation-align', 'none');

            // Fill
            if ($fill = $style->getFill()) {
                switch ($fill->getFillType()) {
                    case Fill::FILL_SOLID:
                        $writer->writeAttribute('fo:background-color', sprintf(
                            '#%s',
                            strtolower($fill->getStartColor()->getRGB())
                        ));

                        break;
                    case Fill::FILL_GRADIENT_LINEAR:
                    case Fill::FILL_GRADIENT_PATH:
                        /// TODO :: To be implemented
                        break;
                    case Fill::FILL_NONE:
                    default:
                }
            }

            $writer->endElement(); // Close style:table-cell-properties

            // End

            $writer->endElement(); // Close style:style
        }
    }

    /**
     * Write attributes for merged cell.
     *
     * @param XMLWriter $objWriter
     * @param Cell $cell
     *
     * @throws \PhpOffice\PhpSpreadsheet\Exception
     */
    private function writeCellMerge(XMLWriter $objWriter, Cell $cell)
    {
        if (!$cell->isMergeRangeValueCell()) {
            return;
        }

        $mergeRange = Coordinate::splitRange($cell->getMergeRange());
        list($startCell, $endCell) = $mergeRange[0];
        $start = Coordinate::coordinateFromString($startCell);
        $end = Coordinate::coordinateFromString($endCell);
        $columnSpan = Coordinate::columnIndexFromString($end[0]) - Coordinate::columnIndexFromString($start[0]) + 1;
        $rowSpan = $end[1] - $start[1] + 1;

        $objWriter->writeAttribute('table:number-columns-spanned', $columnSpan);
        $objWriter->writeAttribute('table:number-rows-spanned', $rowSpan);
    }
}


<?php

namespace PhpOffice\PhpSpreadsheet\Style;

use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;

class Alignment extends Supervisor
{
    // Horizontal alignment styles
    const HORIZONTAL_GENERAL = 'general';
    const HORIZONTAL_LEFT = 'left';
    const HORIZONTAL_RIGHT = 'right';
    const HORIZONTAL_CENTER = 'center';
    const HORIZONTAL_CENTER_CONTINUOUS = 'centerContinuous';
    const HORIZONTAL_JUSTIFY = 'justify';
    const HORIZONTAL_FILL = 'fill';
    const HORIZONTAL_DISTRIBUTED = 'distributed'; // Excel2007 only

    // Vertical alignment styles
    const VERTICAL_BOTTOM = 'bottom';
    const VERTICAL_TOP = 'top';
    const VERTICAL_CENTER = 'center';
    const VERTICAL_JUSTIFY = 'justify';
    const VERTICAL_DISTRIBUTED = 'distributed'; // Excel2007 only

    // Read order
    const READORDER_CONTEXT = 0;
    const READORDER_LTR = 1;
    const READORDER_RTL = 2;

    /**
     * Horizontal alignment.
     *
     * @var string
     */
    protected $horizontal = self::HORIZONTAL_GENERAL;

    /**
     * Vertical alignment.
     *
     * @var string
     */
    protected $vertical = self::VERTICAL_BOTTOM;

    /**
     * Text rotation.
     *
     * @var int
     */
    protected $textRotation = 0;

    /**
     * Wrap text.
     *
     * @var bool
     */
    protected $wrapText = false;

    /**
     * Shrink to fit.
     *
     * @var bool
     */
    protected $shrinkToFit = false;

    /**
     * Indent - only possible with horizontal alignment left and right.
     *
     * @var int
     */
    protected $indent = 0;

    /**
     * Read order.
     *
     * @var int
     */
    protected $readOrder = 0;

    /**
     * Create a new Alignment.
     *
     * @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($isSupervisor = false, $isConditional = false)
    {
        // Supervisor?
        parent::__construct($isSupervisor);

        if ($isConditional) {
            $this->horizontal = null;
            $this->vertical = null;
            $this->textRotation = null;
        }
    }

    /**
     * Get the shared style component for the currently active cell in currently active sheet.
     * Only used for style supervisor.
     *
     * @return Alignment
     */
    public function getSharedComponent()
    {
        return $this->parent->getSharedComponent()->getAlignment();
    }

    /**
     * Build style array from subcomponents.
     *
     * @param array $array
     *
     * @return array
     */
    public function getStyleArray($array)
    {
        return ['alignment' => $array];
    }

    /**
     * Apply styles from array.
     *
     * <code>
     * $spreadsheet->getActiveSheet()->getStyle('B2')->getAlignment()->applyFromArray(
     *        [
     *            'horizontal'   => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
     *            'vertical'     => \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER,
     *            'textRotation' => 0,
     *            'wrapText'     => TRUE
     *        ]
     * );
     * </code>
     *
     * @param array $pStyles Array containing style information
     *
     * @throws PhpSpreadsheetException
     *
     * @return Alignment
     */
    public function applyFromArray(array $pStyles)
    {
        if ($this->isSupervisor) {
            $this->getActiveSheet()->getStyle($this->getSelectedCells())
                ->applyFromArray($this->getStyleArray($pStyles));
        } else {
            if (isset($pStyles['horizontal'])) {
                $this->setHorizontal($pStyles['horizontal']);
            }
            if (isset($pStyles['vertical'])) {
                $this->setVertical($pStyles['vertical']);
            }
            if (isset($pStyles['textRotation'])) {
                $this->setTextRotation($pStyles['textRotation']);
            }
            if (isset($pStyles['wrapText'])) {
                $this->setWrapText($pStyles['wrapText']);
            }
            if (isset($pStyles['shrinkToFit'])) {
                $this->setShrinkToFit($pStyles['shrinkToFit']);
            }
            if (isset($pStyles['indent'])) {
                $this->setIndent($pStyles['indent']);
            }
            if (isset($pStyles['readOrder'])) {
                $this->setReadOrder($pStyles['readOrder']);
            }
        }

        return $this;
    }

    /**
     * Get Horizontal.
     *
     * @return string
     */
    public function getHorizontal()
    {
        if ($this->isSupervisor) {
            return $this->getSharedComponent()->getHorizontal();
        }

        return $this->horizontal;
    }

    /**
     * Set Horizontal.
     *
     * @param string $pValue see self::HORIZONTAL_*
     *
     * @return Alignment
     */
    public function setHorizontal($pValue)
    {
        if ($pValue == '') {
            $pValue = self::HORIZONTAL_GENERAL;
        }

        if ($this->isSupervisor) {
            $styleArray = $this->getStyleArray(['horizontal' => $pValue]);
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
        } else {
            $this->horizontal = $pValue;
        }

        return $this;
    }

    /**
     * Get Vertical.
     *
     * @return string
     */
    public function getVertical()
    {
        if ($this->isSupervisor) {
            return $this->getSharedComponent()->getVertical();
        }

        return $this->vertical;
    }

    /**
     * Set Vertical.
     *
     * @param string $pValue see self::VERTICAL_*
     *
     * @return Alignment
     */
    public function setVertical($pValue)
    {
        if ($pValue == '') {
            $pValue = self::VERTICAL_BOTTOM;
        }

        if ($this->isSupervisor) {
            $styleArray = $this->getStyleArray(['vertical' => $pValue]);
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
        } else {
            $this->vertical = $pValue;
        }

        return $this;
    }

    /**
     * Get TextRotation.
     *
     * @return int
     */
    public function getTextRotation()
    {
        if ($this->isSupervisor) {
            return $this->getSharedComponent()->getTextRotation();
        }

        return $this->textRotation;
    }

    /**
     * Set TextRotation.
     *
     * @param int $pValue
     *
     * @throws PhpSpreadsheetException
     *
     * @return Alignment
     */
    public function setTextRotation($pValue)
    {
        // Excel2007 value 255 => PhpSpreadsheet value -165
        if ($pValue == 255) {
            $pValue = -165;
        }

        // Set rotation
        if (($pValue >= -90 && $pValue <= 90) || $pValue == -165) {
            if ($this->isSupervisor) {
                $styleArray = $this->getStyleArray(['textRotation' => $pValue]);
                $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
            } else {
                $this->textRotation = $pValue;
            }
        } 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 $pValue
     *
     * @return Alignment
     */
    public function setWrapText($pValue)
    {
        if ($pValue == '') {
            $pValue = false;
        }
        if ($this->isSupervisor) {
            $styleArray = $this->getStyleArray(['wrapText' => $pValue]);
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
        } else {
            $this->wrapText = $pValue;
        }

        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 $pValue
     *
     * @return Alignment
     */
    public function setShrinkToFit($pValue)
    {
        if ($pValue == '') {
            $pValue = false;
        }
        if ($this->isSupervisor) {
            $styleArray = $this->getStyleArray(['shrinkToFit' => $pValue]);
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
        } else {
            $this->shrinkToFit = $pValue;
        }

        return $this;
    }

    /**
     * Get indent.
     *
     * @return int
     */
    public function getIndent()
    {
        if ($this->isSupervisor) {
            return $this->getSharedComponent()->getIndent();
        }

        return $this->indent;
    }

    /**
     * Set indent.
     *
     * @param int $pValue
     *
     * @return Alignment
     */
    public function setIndent($pValue)
    {
        if ($pValue > 0) {
            if ($this->getHorizontal() != self::HORIZONTAL_GENERAL &&
                $this->getHorizontal() != self::HORIZONTAL_LEFT &&
                $this->getHorizontal() != self::HORIZONTAL_RIGHT) {
                $pValue = 0; // indent not supported
            }
        }
        if ($this->isSupervisor) {
            $styleArray = $this->getStyleArray(['indent' => $pValue]);
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
        } else {
            $this->indent = $pValue;
        }

        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 $pValue
     *
     * @return Alignment
     */
    public function setReadOrder($pValue)
    {
        if ($pValue < 0 || $pValue > 2) {
            $pValue = 0;
        }
        if ($this->isSupervisor) {
            $styleArray = $this->getStyleArray(['readOrder' => $pValue]);
            $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
        } else {
            $this->readOrder = $pValue;
        }

        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__
        );
    }
}


<?php

namespace XQueue\Maileon\API;

use XQueue\Maileon\API\MaileonAPIResult;

/**
 * Abstract base class for all the service accessing individual resources. This class handles
 * the basic authentication and provides convenience methods to access the four HTTP methods
 * used in RESTful web services.
 *
 * @author Felix Heinrichs | Trusted Mails GmbH | felix.heinrichs@trusted-mails.com
 * @author Marcus Ständer | Trusted Mails GmbH | marcus.staender@trusted-mails.com
 */
abstract class AbstractMaileonService
{

    /**
     * Mime type for the maileon xml data format.
     *
     * @var string $MAILEON_XML_MIME_TYPE
     */
    public static $MAILEON_XML_MIME_TYPE = 'application/vnd.maileon.api+xml';

    /**
     * Configuration object storing BASE_URI and API_KEY to access the maileon REST API.
     *
     * @var array $configuration
     */
    protected $configuration;

    /**
     * Base64 encoded version of the API key.
     *
     * @var string $encodedApiKey
     */
    protected $encodedApiKey;

    /**
     * If true, print debug output (e.g. sent header will be printed)
     *
     * @var boolean $debug
     */
    protected $debug = false;

    /**
     * If true, throw an exception if a transmission or server error occures
     *
     * @var boolean $throwException
     */
    protected $throwException = true;

    /**
     * If a proxy is used, provide the proxy IP here
     *
     * @var string $proxy_host
     */
    protected $proxy_host;

    /**
     * If a proxy is used, use this port. Default is 80.
     *
     * @var int $proxy_port
     */
    protected $proxy_port = 80;

    /**
     * If set, this sets the timeout for a CURL request
     *
     * @var int $timeout
     */
    protected $timeout;

    private $verboseOut = null;

    /**
     * Creates a new instance of the service.
     *
     * @param string[] $config
     *  the API call configuration array
     */
    public function __construct(array $config)
    {
        // check for valid configuration object
        if (!array_key_exists('BASE_URI', $config) || !array_key_exists('API_KEY', $config)) {
            apiError((get_class($this) . ': invalid config object (BASE_URI or API_KEY not set)'));
        }
        if (array_key_exists('THROW_EXCEPTION', $config)) {
            $this->throwException = $config['THROW_EXCEPTION'];
        }
        if (array_key_exists('DEBUG', $config)) {
            $this->debug = $config['DEBUG'];
        }

        // Proxy config
        if (array_key_exists('PROXY_HOST', $config)) {
            $this->proxy_host = $config['PROXY_HOST'];
        }
        if (array_key_exists('PROXY_PORT', $config)) {
            $this->proxy_port = $config['PROXY_PORT'];
        }

        // Timeout in seconds
        if (array_key_exists('TIMEOUT', $config)) {
            $this->timeout = $config['TIMEOUT'];
        }
        $this->configuration = $config;
        $this->encodedApiKey = base64_encode($config['API_KEY']);
    }

    /**
     * (De)activates printing debug output.
     * CAUTION: enabling this in production may compromise sensitive information.
     *
     * @param bool $isDebug
     *  true to enable debugging, false to disable it
     */
    public function setDebug($isDebug)
    {
        $this->debug = $isDebug;
    }

    /**
     * @param unknown $isDebug
     * @return \em boolean
     *  true if debug output is enabled, false otherwise
     */
    public function isDebug()
    {
        return $this->debug;
    }

    /**
     * Performs a GET operation on a resource
     *
     * @param string $resourcePath
     *  the path of the resource to GET
     * @param string[] $queryParameters
     *  any additional query parameters
     * @param string $mimeType
     *  the acceptable response MIME type
     * @param mixed $deserializationType
     *  The name of the class this result should be deserialized as. Use
     *      array( 'array', 'typename' ) to deserialize arrays of a type.
     * @return \em MaileonAPIResult
     *    the result object of the API call
     * @throws MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    public function get($resourcePath, $queryParameters = array(),
                        $mimeType = 'application/vnd.maileon.api+xml',
                        $deserializationType = null)
    {
        $curlSession = $this->prepareSession($resourcePath, $queryParameters, $mimeType);
        return $this->performRequest($curlSession, $deserializationType);
    }

    /**
     * Performs a PUT operation (i.e. an update) on a resource.
     *
     * @param string $resourcePath
     *  the path of the resource to PUT
     * @param string $payload
     *  the payload data to PUT, i.e. the data to update the current state of the resource with
     * @param array $queryParameters
     *  any additional query parameters
     * @param string $mimeType
     *  the acceptable response MIME type
     * @param mixed $deserializationType
     *  The name of the class this result should be deserialized as. Use
     *      array( 'array', 'typename' ) to deserialize arrays of a type.
     * @return \em MaileonAPIResult
     *    the result object of the API call
     * @throws MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    public function put($resourcePath, $payload = "", $queryParameters = array(),
                        $mimeType = 'application/vnd.maileon.api+xml',
                        $deserializationType = null)
    {
        $curlSession = $this->prepareSession($resourcePath, $queryParameters, $mimeType);
        /*
         *  PUT does not work as expected when passing post data, see
         *	http://developers.sugarcrm.com/wordpress/2011/11/22/howto-do-put-requests-with-php-curl-without-writing-to-a-file/
         *	Because of this, we use a custom request here.
         */
        curl_setopt($curlSession, CURLOPT_CUSTOMREQUEST, "PUT");
        curl_setopt($curlSession, CURLOPT_POSTFIELDS, $payload);
        return $this->performRequest($curlSession, $deserializationType);
    }

    /**
     * Performs a POST operation (i.e. creates a new instance) on a resource.
     *
     * @param string $resourcePath
     *  the path of the resource to POST. This is typically the parent (or owner) resource
     *  of the resource instance to create.
     * @param string $payload
     *  the data to POST, i.e. the contents of the new resource instance
     * @param array $queryParameters
     *  any additional query parameters
     * @param string $mimeType
     *  the acceptable response MIME type
     * @param mixed $deserializationType
     *  The name of the class this result should be deserialized as. Use
     *      array( 'array', 'typename' ) to deserialize arrays of a type.
     * @return \em MaileonAPIResult
     *    the result object of the API call
     * @throws MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    public function post($resourcePath, $payload = "", $queryParameters = array(),
                        $mimeType = 'application/vnd.maileon.api+xml',
                        $deserializationType = null,
                        $contentType = null,
                        $contentLength = null)
    {
        $curlSession = $this->prepareSession($resourcePath, $queryParameters, $mimeType, $contentType, $contentLength);
        curl_setopt($curlSession, CURLOPT_POST, true);
        curl_setopt($curlSession, CURLOPT_POSTFIELDS, $payload);
        return $this->performRequest($curlSession, $deserializationType);
    }

    /**
     * Performs a DELETE operation on a resource.
     *
     * @param string $resourcePath
     *  the resource to DELETE
     * @param array $queryParameters
     *  any additional query parameters
     * @param string $mimeType
     *  the acceptable response MIME type
     * @param mixed $deserializationType
     *  The name of the class this result should be deserialized as. Use
     *      array( 'array', 'typename' ) to deserialize arrays of a type.
     * @return \em MaileonAPIResult
     *    the result object of the API call
     * @throws MaileonAPIException
     *  if there was a connection problem or a server error occurred
     */
    public function delete($resourcePath, $queryParameters = array(),
                        $mimeType = 'application/vnd.maileon.api+xml',
                        $deserializationType = null)
    {
        $curlSession = $this->prepareSession($resourcePath, $queryParameters, $mimeType);
        curl_setopt($curlSession, CURLOPT_CUSTOMREQUEST, "DELETE");
        return $this->performRequest($curlSession, $deserializationType);
    }

    private function prepareSession($resourcePath, $queryParameters, $mimeType, $contentType = null, $contentLength = null)
    {
        $requestUrl = $this->constructRequestUrl($resourcePath, $queryParameters);
        $headers = $this->constructHeaders($mimeType, $contentType, $contentLength);
        $curlSession = curl_init($requestUrl);
        $options = array(
            CURLOPT_HEADER => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_FAILONERROR => false,
            CURLOPT_VERBOSE => $this->debug
        );

        if ($this->debug) {
            $this->verboseOut = fopen("php://temp", "rw+");
            $options[CURLOPT_STDERR] = $this->verboseOut;
        }

        if ($this->timeout) {
            $options[CURLOPT_CONNECTTIMEOUT] = $this->timeout;
            $options[CURLOPT_TIMEOUT] = $this->timeout;
        }

        if ($this->proxy_host) {
            $options[CURLOPT_PROXY] = $this->proxy_host;
            $options[CURLOPT_PROXYPORT] = $this->proxy_port;
        }

        curl_setopt_array($curlSession, $options);
        return $curlSession;
    }

    private function constructRequestUrl($resourcePath, $queryParameters)
    {
        $requestUrl = $this->configuration['BASE_URI'] . "/" . $resourcePath;

        if (isset($queryParameters) && !empty($queryParameters)) {
            $requestUrl = $requestUrl . '?';

            foreach ($queryParameters as $key => $value) {
                if (is_array($value)) {
                    foreach ($value as $innerKey => $innerValue) {
                        if ($innerValue === true) {
                            $requestUrl .= $innerValue . '=true&';
                        } else if ($value === false) {
                            $requestUrl .= $innerValue . '=false&';
                        } else {
                            $requestUrl .= $key . '=' . $innerValue . '&';
                        }
                    }
                } else {
                    if ($value === true) {
                        $requestUrl .= $key . '=true&';
                    } else if ($value === false) {
                        $requestUrl .= $key . '=false&';
                    } else {
                        $requestUrl .= $key . '=' . $value . '&';
                    }
                }
            }

            $requestUrl = rtrim($requestUrl, '&');
        }

        return $requestUrl;
    }

    private function constructHeaders($mimeType, $contentType = null, $contentLength = null)
    {
        $headers = array(
            "Content-type: " . ($contentType === null ? $mimeType : $contentType),
            "Accept: " . $mimeType,
            "Authorization: Basic " . $this->encodedApiKey,
            "Expect:"
        );

        if($contentLength !== null) { $headers []= "Content-Length: " . $contentLength; }

        return $headers;
    }

    /**
     * Perform the currently initialized request
     *
     * @param resource $curlSession
     *  the curl session
     * @param mixed $deserializationType
     *  The name of the class this result should be deserialized as. Use
     *      array( 'array', 'typename' ) to deserialize arrays of a type.
     * @return \MaileonAPIResult
     * @throws MaileonAPIException
     */
    private function performRequest($curlSession, $deserializationType = null)
    {
        $response = curl_exec($curlSession);
        // coerce all false values to null
        $response = $response ? $response : null;
        try {
            $result = new MaileonAPIResult($response, $curlSession, $this->throwException, $deserializationType);
            $this->printDebugInformation($curlSession, $result);
            curl_close($curlSession);
            return $result;
        } catch (MaileonAPIException $e) {
            if ($this->debug) {
                $this->printDebugInformation($curlSession, null, $this->throwException ? null : $e);
            }
            curl_close($curlSession);
            if ($this->throwException) {
                throw $e;
            }
            return null;
        }
    }

    protected function appendArrayFields($params, $name, $fieldValues)
    {
        if (isset ($fieldValues) && is_array($fieldValues) && count($fieldValues) > 0) {
            $params ["$name"] = array();
            foreach ($fieldValues as $value) {
                $params ["$name"] [] = urlencode($value); //urlencode(utf8_encode($value));
            }
        }
        return $params;
    }

    private function printDebugInformation($curlSession, $result = null, $exception = null)
    {
        if ($this->debug) {
            rewind($this->verboseOut);
            $sessionLog = stream_get_contents($this->verboseOut);
            $sessionLog = preg_replace("/^Authorization: .*$/m", "Authorization: ***redacted***", $sessionLog);
            if (defined('RUNNING_IN_PHPUNIT') && RUNNING_IN_PHPUNIT) {
                echo "\n";
                echo "cURL session log:\n";
                echo $sessionLog . "\n";
                if ($result != null) {
                    echo "Result:\n";
                    echo $result . "\n";
                }
                if ($exception != null) {
                    echo "Caught exception:\n";
                    echo $exception . "\n";
                }
            } else {
                echo "
cURL session log
\n";
                echo "
\n";
                echo htmlentities($sessionLog);
                echo "
\n";
                if ($result != null) {
                    echo "
Result
\n";
                    echo "
\n";
                    echo htmlentities($result);
                    echo "
\n";
                }
                if ($exception != null) {
                    echo "
Exception
\n";
                    echo "
\n";
                    echo htmlentities($exception);
                    echo "
\n";
                }
            }
            $this->verboseOut = null;
        }
    }
}


<?php

namespace PhpOffice\PhpSpreadsheet\Calculation;

class Database
{
    /**
     * fieldExtract.
     *
     * Extracts the column ID to use for the data field.
     *
     * @param mixed[] $database The range of cells that makes up the list or database.
     *                                        A database is a list of related data in which rows of related
     *                                        information are records, and columns of data are fields. The
     *                                        first row of the list contains labels for each column.
     * @param mixed $field Indicates which column is used in the function. Enter the
     *                                        column label enclosed between double quotation marks, such as
     *                                        "Age" or "Yield," or a number (without quotation marks) that
     *                                        represents the position of the column within the list: 1 for
     *                                        the first column, 2 for the second column, and so on.
     *
     * @return null|string
     */
    private static function fieldExtract($database, $field)
    {
        $field = strtoupper(Functions::flattenSingleValue($field));
        $fieldNames = array_map('strtoupper', array_shift($database));

        if (is_numeric($field)) {
            $keys = array_keys($fieldNames);

            return $keys[$field - 1];
        }
        $key = array_search($field, $fieldNames);

        return ($key) ? $key : null;
    }

    /**
     * filter.
     *
     * Parses the selection criteria, extracts the database rows that match those criteria, and
     * returns that subset of rows.
     *
     * @param mixed[] $database The range of cells that makes up the list or database.
     *                                        A database is a list of related data in which rows of related
     *                                        information are records, and columns of data are fields. The
     *                                        first row of the list contains labels for each column.
     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
     *                                        You can use any range for the criteria argument, as long as it
     *                                        includes at least one column label and at least one cell below
     *                                        the column label in which you specify a condition for the
     *                                        column.
     *
     * @return array of mixed
     */
    private static function filter($database, $criteria)
    {
        $fieldNames = array_shift($database);
        $criteriaNames = array_shift($criteria);

        //    Convert the criteria into a set of AND/OR conditions with [:placeholders]
        $testConditions = $testValues = [];
        $testConditionsCount = 0;
        foreach ($criteriaNames as $key => $criteriaName) {
            $testCondition = [];
            $testConditionCount = 0;
            foreach ($criteria as $row => $criterion) {
                if ($criterion[$key] > '') {
                    $testCondition[] = '[:' . $criteriaName . ']' . Functions::ifCondition($criterion[$key]);
                    ++$testConditionCount;
                }
            }
            if ($testConditionCount > 1) {
                $testConditions[] = 'OR(' . implode(',', $testCondition) . ')';
                ++$testConditionsCount;
            } elseif ($testConditionCount == 1) {
                $testConditions[] = $testCondition[0];
                ++$testConditionsCount;
            }
        }

        if ($testConditionsCount > 1) {
            $testConditionSet = 'AND(' . implode(',', $testConditions) . ')';
        } elseif ($testConditionsCount == 1) {
            $testConditionSet = $testConditions[0];
        }

        //    Loop through each row of the database
        foreach ($database as $dataRow => $dataValues) {
            //    Substitute actual values from the database row for our [:placeholders]
            $testConditionList = $testConditionSet;
            foreach ($criteriaNames as $key => $criteriaName) {
                $k = array_search($criteriaName, $fieldNames);
                if (isset($dataValues[$k])) {
                    $dataValue = $dataValues[$k];
                    $dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue;
                    $testConditionList = str_replace('[:' . $criteriaName . ']', $dataValue, $testConditionList);
                }
            }
            //    evaluate the criteria against the row data
            $result = Calculation::getInstance()->_calculateFormulaValue('=' . $testConditionList);
            //    If the row failed to meet the criteria, remove it from the database
            if (!$result) {
                unset($database[$dataRow]);
            }
        }

        return $database;
    }

    private static function getFilteredColumn($database, $field, $criteria)
    {
        //    reduce the database to a set of rows that match all the criteria
        $database = self::filter($database, $criteria);
        //    extract an array of values for the requested column
        $colData = [];
        foreach ($database as $row) {
            $colData[] = $row[$field];
        }

        return $colData;
    }

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

        // Return
        return Statistical::AVERAGE(
            self::getFilteredColumn($database, $field, $criteria)
        );
    }

    /**
     * DCOUNT.
     *
     * Counts the cells that contain numbers in a column of a list or database that match conditions
     * that you specify.
     *
     * Excel Function:
     *        DCOUNT(database,[field],criteria)
     *
     * Excel Function:
     *        DAVERAGE(database,field,criteria)
     *
     * @category Database Functions
     *
     * @param mixed[] $database The range of cells that makes up the list or database.
     *                                        A database is a list of related data in which rows of related
     *                                        information are records, and columns of data are fields. The
     *                                        first row of the list contains labels for each column.
     * @param int|string $field Indicates which column is used in the function. Enter the
     *                                        column label enclosed between double quotation marks, such as
     *                                        "Age" or "Yield," or a number (without quotation marks) that
     *                                        represents the position of the column within the list: 1 for
     *                                        the first column, 2 for the second column, and so on.
     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
     *                                        You can use any range for the criteria argument, as long as it
     *                                        includes at least one column label and at least one cell below
     *                                        the column label in which you specify a condition for the
     *                                        column.
     *
     * @return int
     *
     * @TODO    The field argument is optional. If field is omitted, DCOUNT counts all records in the
     *            database that match the criteria.
     */
    public static function DCOUNT($database, $field, $criteria)
    {
        $field = self::fieldExtract($database, $field);
        if ($field === null) {
            return null;
        }

        // Return
        return Statistical::COUNT(
            self::getFilteredColumn($database, $field, $criteria)
        );
    }

    /**
     * DCOUNTA.
     *
     * Counts the nonblank cells in a column of a list or database that match conditions that you specify.
     *
     * Excel Function:
     *        DCOUNTA(database,[field],criteria)
     *
     * @category Database Functions
     *
     * @param mixed[] $database The range of cells that makes up the list or database.
     *                                        A database is a list of related data in which rows of related
     *                                        information are records, and columns of data are fields. The
     *                                        first row of the list contains labels for each column.
     * @param int|string $field Indicates which column is used in the function. Enter the
     *                                        column label enclosed between double quotation marks, such as
     *                                        "Age" or "Yield," or a number (without quotation marks) that
     *                                        represents the position of the column within the list: 1 for
     *                                        the first column, 2 for the second column, and so on.
     * @param mixed[] $criteria The range of cells that contains the conditions you specify.
     *                                        You can use any range for the criteria argument, as long as it
     *                                        includes at least one column label and at least one cell below
     *                                        the column label in which you specify a condition for the
     *                                        column.
     *
     * @return int
     *
     * @TODO    The field argument is optional. If field is omitted, DCOUNTA counts all records in the
     *            database that match the criteria.
     */
    public static function DCOUNTA($database, $field, $criteria)
    {
        $field = self::fieldExtract($database, $field);
        if ($field === null) {
            return null;
        }

        //    reduce the database to a set of rows that match all the criteria
        $database = self::filter($database, $criteria);
        //    extract an array of values for the requested column
        $colData = [];
        foreach ($database as $row) {
            $colData[] = $row[$field];
        }

        // Return
        return Statistical::COUNTA(
            self::getFilteredColumn($database, $field, $criteria)
        );
    }

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

        // Return
        $colData = self::getFilteredColumn($database, $field, $criteria);
        if (count($colData) > 1) {
            return Functions::NAN();
        }

        return $colData[0];
    }

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

        // Return
        return Statistical::MAX(
            self::getFilteredColumn($database, $field, $criteria)
        );
    }

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

        // Return
        return Statistical::MIN(
            self::getFilteredColumn($database, $field, $criteria)
        );
    }

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

        // Return
        return MathTrig::PRODUCT(
            self::getFilteredColumn($database, $field, $criteria)
        );
    }

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

        // Return
        return Statistical::STDEV(
            self::getFilteredColumn($database, $field, $criteria)
        );
    }

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

        // Return
        return Statistical::STDEVP(
            self::getFilteredColumn($database, $field, $criteria)
        );
    }

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

        // Return
        return MathTrig::SUM(
            self::getFilteredColumn($database, $field, $criteria)
        );
    }

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

        // Return
        return Statistical::VARFunc(
            self::getFilteredColumn($database, $field, $criteria)
        );
    }

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

        // Return
        return Statistical::VARP(
            self::getFilteredColumn($database, $field, $criteria)
        );
    }
}