<?php

namespace PhpOffice\PhpSpreadsheet\Reader\Xls;

use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip;

class Escher
{
    const DGGCONTAINER = 0xF000;
    const BSTORECONTAINER = 0xF001;
    const DGCONTAINER = 0xF002;
    const SPGRCONTAINER = 0xF003;
    const SPCONTAINER = 0xF004;
    const DGG = 0xF006;
    const BSE = 0xF007;
    const DG = 0xF008;
    const SPGR = 0xF009;
    const SP = 0xF00A;
    const OPT = 0xF00B;
    const CLIENTTEXTBOX = 0xF00D;
    const CLIENTANCHOR = 0xF010;
    const CLIENTDATA = 0xF011;
    const BLIPJPEG = 0xF01D;
    const BLIPPNG = 0xF01E;
    const SPLITMENUCOLORS = 0xF11E;
    const TERTIARYOPT = 0xF122;

    /**
     * Escher stream data (binary).
     *
     * @var string
     */
    private $data;

    /**
     * Size in bytes of the Escher stream data.
     *
     * @var int
     */
    private $dataSize;

    /**
     * Current position of stream pointer in Escher stream data.
     *
     * @var int
     */
    private $pos;

    /**
     * The object to be returned by the reader. Modified during load.
     *
     * @var BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer
     */
    private $object;

    /**
     * Create a new Escher instance.
     *
     * @param mixed $object
     */
    public function __construct($object)
    {
        $this->object = $object;
    }

    /**
     * Load Escher stream data. May be a partial Escher stream.
     *
     * @param string $data
     *
     * @return BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer
     */
    public function load($data)
    {
        $this->data = $data;

        // total byte size of Excel data (workbook global substream + sheet substreams)
        $this->dataSize = strlen($this->data);

        $this->pos = 0;

        // Parse Escher stream
        while ($this->pos < $this->dataSize) {
            // offset: 2; size: 2: Record Type
            $fbt = Xls::getUInt2d($this->data, $this->pos + 2);

            switch ($fbt) {
                case self::DGGCONTAINER:
                    $this->readDggContainer();

                    break;
                case self::DGG:
                    $this->readDgg();

                    break;
                case self::BSTORECONTAINER:
                    $this->readBstoreContainer();

                    break;
                case self::BSE:
                    $this->readBSE();

                    break;
                case self::BLIPJPEG:
                    $this->readBlipJPEG();

                    break;
                case self::BLIPPNG:
                    $this->readBlipPNG();

                    break;
                case self::OPT:
                    $this->readOPT();

                    break;
                case self::TERTIARYOPT:
                    $this->readTertiaryOPT();

                    break;
                case self::SPLITMENUCOLORS:
                    $this->readSplitMenuColors();

                    break;
                case self::DGCONTAINER:
                    $this->readDgContainer();

                    break;
                case self::DG:
                    $this->readDg();

                    break;
                case self::SPGRCONTAINER:
                    $this->readSpgrContainer();

                    break;
                case self::SPCONTAINER:
                    $this->readSpContainer();

                    break;
                case self::SPGR:
                    $this->readSpgr();

                    break;
                case self::SP:
                    $this->readSp();

                    break;
                case self::CLIENTTEXTBOX:
                    $this->readClientTextbox();

                    break;
                case self::CLIENTANCHOR:
                    $this->readClientAnchor();

                    break;
                case self::CLIENTDATA:
                    $this->readClientData();

                    break;
                default:
                    $this->readDefault();

                    break;
            }
        }

        return $this->object;
    }

    /**
     * Read a generic record.
     */
    private function readDefault()
    {
        // offset 0; size: 2; recVer and recInstance
        $verInstance = Xls::getUInt2d($this->data, $this->pos);

        // offset: 2; size: 2: Record Type
        $fbt = Xls::getUInt2d($this->data, $this->pos + 2);

        // bit: 0-3; mask: 0x000F; recVer
        $recVer = (0x000F & $verInstance) >> 0;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;
    }

    /**
     * Read DggContainer record (Drawing Group Container).
     */
    private function readDggContainer()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;

        // record is a container, read contents
        $dggContainer = new DggContainer();
        $this->object->setDggContainer($dggContainer);
        $reader = new self($dggContainer);
        $reader->load($recordData);
    }

    /**
     * Read Dgg record (Drawing Group).
     */
    private function readDgg()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;
    }

    /**
     * Read BstoreContainer record (Blip Store Container).
     */
    private function readBstoreContainer()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;

        // record is a container, read contents
        $bstoreContainer = new BstoreContainer();
        $this->object->setBstoreContainer($bstoreContainer);
        $reader = new self($bstoreContainer);
        $reader->load($recordData);
    }

    /**
     * Read BSE record.
     */
    private function readBSE()
    {
        // offset: 0; size: 2; recVer and recInstance

        // bit: 4-15; mask: 0xFFF0; recInstance
        $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;

        // add BSE to BstoreContainer
        $BSE = new BSE();
        $this->object->addBSE($BSE);

        $BSE->setBLIPType($recInstance);

        // offset: 0; size: 1; btWin32 (MSOBLIPTYPE)
        $btWin32 = ord($recordData[0]);

        // offset: 1; size: 1; btWin32 (MSOBLIPTYPE)
        $btMacOS = ord($recordData[1]);

        // offset: 2; size: 16; MD4 digest
        $rgbUid = substr($recordData, 2, 16);

        // offset: 18; size: 2; tag
        $tag = Xls::getUInt2d($recordData, 18);

        // offset: 20; size: 4; size of BLIP in bytes
        $size = Xls::getInt4d($recordData, 20);

        // offset: 24; size: 4; number of references to this BLIP
        $cRef = Xls::getInt4d($recordData, 24);

        // offset: 28; size: 4; MSOFO file offset
        $foDelay = Xls::getInt4d($recordData, 28);

        // offset: 32; size: 1; unused1
        $unused1 = ord($recordData[32]);

        // offset: 33; size: 1; size of nameData in bytes (including null terminator)
        $cbName = ord($recordData[33]);

        // offset: 34; size: 1; unused2
        $unused2 = ord($recordData[34]);

        // offset: 35; size: 1; unused3
        $unused3 = ord($recordData[35]);

        // offset: 36; size: $cbName; nameData
        $nameData = substr($recordData, 36, $cbName);

        // offset: 36 + $cbName, size: var; the BLIP data
        $blipData = substr($recordData, 36 + $cbName);

        // record is a container, read contents
        $reader = new self($BSE);
        $reader->load($blipData);
    }

    /**
     * Read BlipJPEG record. Holds raw JPEG image data.
     */
    private function readBlipJPEG()
    {
        // offset: 0; size: 2; recVer and recInstance

        // bit: 4-15; mask: 0xFFF0; recInstance
        $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;

        $pos = 0;

        // offset: 0; size: 16; rgbUid1 (MD4 digest of)
        $rgbUid1 = substr($recordData, 0, 16);
        $pos += 16;

        // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3
        if (in_array($recInstance, [0x046B, 0x06E3])) {
            $rgbUid2 = substr($recordData, 16, 16);
            $pos += 16;
        }

        // offset: var; size: 1; tag
        $tag = ord($recordData[$pos]);
        $pos += 1;

        // offset: var; size: var; the raw image data
        $data = substr($recordData, $pos);

        $blip = new Blip();
        $blip->setData($data);

        $this->object->setBlip($blip);
    }

    /**
     * Read BlipPNG record. Holds raw PNG image data.
     */
    private function readBlipPNG()
    {
        // offset: 0; size: 2; recVer and recInstance

        // bit: 4-15; mask: 0xFFF0; recInstance
        $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;

        $pos = 0;

        // offset: 0; size: 16; rgbUid1 (MD4 digest of)
        $rgbUid1 = substr($recordData, 0, 16);
        $pos += 16;

        // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3
        if ($recInstance == 0x06E1) {
            $rgbUid2 = substr($recordData, 16, 16);
            $pos += 16;
        }

        // offset: var; size: 1; tag
        $tag = ord($recordData[$pos]);
        $pos += 1;

        // offset: var; size: var; the raw image data
        $data = substr($recordData, $pos);

        $blip = new Blip();
        $blip->setData($data);

        $this->object->setBlip($blip);
    }

    /**
     * Read OPT record. This record may occur within DggContainer record or SpContainer.
     */
    private function readOPT()
    {
        // offset: 0; size: 2; recVer and recInstance

        // bit: 4-15; mask: 0xFFF0; recInstance
        $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;

        $this->readOfficeArtRGFOPTE($recordData, $recInstance);
    }

    /**
     * Read TertiaryOPT record.
     */
    private function readTertiaryOPT()
    {
        // offset: 0; size: 2; recVer and recInstance

        // bit: 4-15; mask: 0xFFF0; recInstance
        $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;
    }

    /**
     * Read SplitMenuColors record.
     */
    private function readSplitMenuColors()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;
    }

    /**
     * Read DgContainer record (Drawing Container).
     */
    private function readDgContainer()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;

        // record is a container, read contents
        $dgContainer = new DgContainer();
        $this->object->setDgContainer($dgContainer);
        $reader = new self($dgContainer);
        $escher = $reader->load($recordData);
    }

    /**
     * Read Dg record (Drawing).
     */
    private function readDg()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;
    }

    /**
     * Read SpgrContainer record (Shape Group Container).
     */
    private function readSpgrContainer()
    {
        // context is either context DgContainer or SpgrContainer

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;

        // record is a container, read contents
        $spgrContainer = new SpgrContainer();

        if ($this->object instanceof DgContainer) {
            // DgContainer
            $this->object->setSpgrContainer($spgrContainer);
        } else {
            // SpgrContainer
            $this->object->addChild($spgrContainer);
        }

        $reader = new self($spgrContainer);
        $escher = $reader->load($recordData);
    }

    /**
     * Read SpContainer record (Shape Container).
     */
    private function readSpContainer()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // add spContainer to spgrContainer
        $spContainer = new SpContainer();
        $this->object->addChild($spContainer);

        // move stream pointer to next record
        $this->pos += 8 + $length;

        // record is a container, read contents
        $reader = new self($spContainer);
        $escher = $reader->load($recordData);
    }

    /**
     * Read Spgr record (Shape Group).
     */
    private function readSpgr()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;
    }

    /**
     * Read Sp record (Shape).
     */
    private function readSp()
    {
        // offset: 0; size: 2; recVer and recInstance

        // bit: 4-15; mask: 0xFFF0; recInstance
        $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;
    }

    /**
     * Read ClientTextbox record.
     */
    private function readClientTextbox()
    {
        // offset: 0; size: 2; recVer and recInstance

        // bit: 4-15; mask: 0xFFF0; recInstance
        $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;
    }

    /**
     * Read ClientAnchor record. This record holds information about where the shape is anchored in worksheet.
     */
    private function readClientAnchor()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;

        // offset: 2; size: 2; upper-left corner column index (0-based)
        $c1 = Xls::getUInt2d($recordData, 2);

        // offset: 4; size: 2; upper-left corner horizontal offset in 1/1024 of column width
        $startOffsetX = Xls::getUInt2d($recordData, 4);

        // offset: 6; size: 2; upper-left corner row index (0-based)
        $r1 = Xls::getUInt2d($recordData, 6);

        // offset: 8; size: 2; upper-left corner vertical offset in 1/256 of row height
        $startOffsetY = Xls::getUInt2d($recordData, 8);

        // offset: 10; size: 2; bottom-right corner column index (0-based)
        $c2 = Xls::getUInt2d($recordData, 10);

        // offset: 12; size: 2; bottom-right corner horizontal offset in 1/1024 of column width
        $endOffsetX = Xls::getUInt2d($recordData, 12);

        // offset: 14; size: 2; bottom-right corner row index (0-based)
        $r2 = Xls::getUInt2d($recordData, 14);

        // offset: 16; size: 2; bottom-right corner vertical offset in 1/256 of row height
        $endOffsetY = Xls::getUInt2d($recordData, 16);

        // set the start coordinates
        $this->object->setStartCoordinates(Coordinate::stringFromColumnIndex($c1 + 1) . ($r1 + 1));

        // set the start offsetX
        $this->object->setStartOffsetX($startOffsetX);

        // set the start offsetY
        $this->object->setStartOffsetY($startOffsetY);

        // set the end coordinates
        $this->object->setEndCoordinates(Coordinate::stringFromColumnIndex($c2 + 1) . ($r2 + 1));

        // set the end offsetX
        $this->object->setEndOffsetX($endOffsetX);

        // set the end offsetY
        $this->object->setEndOffsetY($endOffsetY);
    }

    /**
     * Read ClientData record.
     */
    private function readClientData()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        // move stream pointer to next record
        $this->pos += 8 + $length;
    }

    /**
     * Read OfficeArtRGFOPTE table of property-value pairs.
     *
     * @param string $data Binary data
     * @param int $n Number of properties
     */
    private function readOfficeArtRGFOPTE($data, $n)
    {
        $splicedComplexData = substr($data, 6 * $n);

        // loop through property-value pairs
        for ($i = 0; $i < $n; ++$i) {
            // read 6 bytes at a time
            $fopte = substr($data, 6 * $i, 6);

            // offset: 0; size: 2; opid
            $opid = Xls::getUInt2d($fopte, 0);

            // bit: 0-13; mask: 0x3FFF; opid.opid
            $opidOpid = (0x3FFF & $opid) >> 0;

            // bit: 14; mask 0x4000; 1 = value in op field is BLIP identifier
            $opidFBid = (0x4000 & $opid) >> 14;

            // bit: 15; mask 0x8000; 1 = this is a complex property, op field specifies size of complex data
            $opidFComplex = (0x8000 & $opid) >> 15;

            // offset: 2; size: 4; the value for this property
            $op = Xls::getInt4d($fopte, 2);

            if ($opidFComplex) {
                $complexData = substr($splicedComplexData, 0, $op);
                $splicedComplexData = substr($splicedComplexData, $op);

                // we store string value with complex data
                $value = $complexData;
            } else {
                // we store integer value
                $value = $op;
            }

            $this->object->setOPT($opidOpid, $value);
        }
    }
}


<?php

namespace Snaptier\API\Api;

use Snaptier\API\Exception\InvalidArgumentException;
use Snaptier\API\HttpClient\Message\ResponseMediator;
use Http\Client\Common\HttpMethodsClient;

/**
 * The abstract snaptier api class.
 *
 * @author Joseph Bielawski <stloyd@gmail.com>
 * @author Graham Campbell <graham@alt-three.com>
 * @author Miguel Piedrafita <soy@miguelpiedrafita.com>
 */
abstract class AbstractApi
{
    /**
     * The http methods client.
     *
     * @var \Http\Client\Common\HttpMethodsClient
     */
    private $client;

    /**
     * Create a new api instance.
     *
     * @param \Http\Client\Common\HttpMethodsClient $client
     *
     * @return void
     */
    public function __construct(HttpMethodsClient $client)
    {
        $this->client = $client;
    }

    /**
     * Get the http methods client.
     *
     * @return \Http\Client\Common\HttpMethodsClient
     */
    protected function getHttpClient()
    {
        return $this->client;
    }

    /**
     * Send a GET request with query params.
     *
     * @param string $path
     * @param array  $params
     * @param array  $headers
     *
     * @throws \Http\Client\Exception
     *
     * @return array
     */
    protected function get(string $path, array $params = [], array $headers = [])
    {
        $response = $this->pureGet($path, $params, $headers);

        return ResponseMediator::getContent($response);
    }

    /**
     * Send a GET request with query params.
     *
     * @param string $path
     * @param array  $params
     * @param array  $headers
     *
     * @throws \Http\Client\Exception
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    protected function pureGet(string $path, array $params = [], array $headers = [])
    {
        if ($params) {
            $path .= '?'.http_build_query($params);
        }

        return $this->client->get($path, $headers);
    }

    /**
     * Send a POST request with JSON-encoded params.
     *
     * @param string $path
     * @param array  $params
     * @param array  $headers
     *
     * @throws \Http\Client\Exception
     *
     * @return array
     */
    protected function post(string $path, array $params = [], array $headers = [])
    {
        $body = self::createJsonBody($params);

        if ($body) {
            $headers = self::addJsonContentType($headers);
        }

        return $this->postRaw($path, $body, $headers);
    }

    /**
     * Send a POST request with raw data.
     *
     * @param string                                        $path
     * @param string|\Psr\Http\Message\StreamInterface|null $body
     * @param array                                         $headers
     *
     * @throws \Http\Client\Exception
     *
     * @return array
     */
    protected function postRaw(string $path, $body = null, array $headers = [])
    {
        $response = $this->client->post($path, $headers, $body);

        return ResponseMediator::getContent($response);
    }

    /**
     * Send a PUT request with JSON-encoded params.
     *
     * @param string $path
     * @param array  $params
     * @param array  $headers
     *
     * @throws \Http\Client\Exception
     *
     * @return array
     */
    protected function put(string $path, array $params = [], array $headers = [])
    {
        $body = self::createJsonBody($params);

        if ($body) {
            $headers = self::addJsonContentType($headers);
        }

        return $this->putRaw($path, $body, $headers);
    }

    /**
     * Send a PUT request with raw data.
     *
     * @param string                                        $path
     * @param string|\Psr\Http\Message\StreamInterface|null $body
     * @param array                                         $headers
     *
     * @throws \Http\Client\Exception
     *
     * @return array
     */
    protected function putRaw(string $path, $body = null, array $headers = [])
    {
        $response = $this->client->put($path, $headers, $body);

        return ResponseMediator::getContent($response);
    }

    /**
     * Send a DELETE request with JSON-encoded params.
     *
     * @param string $path
     * @param array  $params
     * @param array  $headers
     *
     * @throws \Http\Client\Exception
     *
     * @return array
     */
    protected function delete(string $path, array $params = [], array $headers = [])
    {
        $body = self::createJsonBody($params);

        if ($body) {
            $headers = self::addJsonContentType($headers);
        }

        return $this->deleteRaw($path, $body, $headers);
    }

    /**
     * Send a DELETE request with raw data.
     *
     * @param string                                        $path
     * @param string|\Psr\Http\Message\StreamInterface|null $body
     * @param array                                         $headers
     *
     * @throws \Http\Client\Exception
     *
     * @return array
     */
    protected function deleteRaw(string $path, $body = null, array $headers = [])
    {
        $response = $this->client->delete($path, $headers, $body);

        return ResponseMediator::getContent($response);
    }

    /**
     * Build a URL path from the given parts.
     *
     * @param string[] $parts
     *
     * @throws \Bitbucket\Exception\InvalidArgumentException
     *
     * @return string
     */
    protected static function buildPath(string ...$parts)
    {
        $parts = array_map(function (string $part) {
            if (!$part) {
                throw new InvalidArgumentException('Missing required parameter.');
            }

            return self::urlEncode($part);
        }, $parts);

        return implode('/', $parts);
    }

    /**
     * Create a JSON encoded version of an array of params.
     *
     * @param array $params
     *
     * @return string|null
     */
    private static function createJsonBody(array $params)
    {
        if ($params) {
            return json_encode($params);
        }
    }

    /**
     * Add the JSON content type to the headers if one is not already present.
     *
     * @param array $headers
     *
     * @return array
     */
    private static function addJsonContentType(array $headers)
    {
        return array_merge(['Content-Type' => 'application/json'], $headers);
    }

    /**
     * Encode the given string for a URL.
     *
     * @param string $str
     *
     * @return string
     */
    private static function urlEncode(string $str)
    {
        $str = rawurlencode($str);

        return str_replace('.', '%2E', $str);
    }
}


<?php

namespace XQueue\Maileon\API\Blacklists;

use XQueue\Maileon\API\AbstractMaileonService;
use XQueue\Maileon\API\Blacklists\AddEntriesAction;

/**
 * Facade that wraps the REST service for blacklists.
 *
 */
class BlacklistsService extends AbstractMaileonService
{

    /**
     * Retrieves the names and IDs of the custom blacklists in your account.
     *
     * @return \em com_maileon_api_MaileonAPIResult
     *    the result object of the API call, with a com_maileon_api_blacklists_Blacklist[] available at com_maileon_api_MaileonAPIResult::getResult()
     * @throws com_maileon_api_MaileonAPIException
     *      if there was a connection problem or a server error occurred
     */
    public function getBlacklists()
    {
        return $this->get("blacklists");
    }

    /**
     * Retrieves a full blacklist (including entries) by id.
     *
     * @param integer $id
     *        the id of the blacklist to retrieve
     * @return \em com_maileon_api_MaileonAPIResult
     *        the result object of the API call, with the requested com_maileon_api_blacklists_Blacklist available at com_maileon_api_MaileonAPIResult::getResult()
     * @throws com_maileon_api_MaileonAPIException
     *      if there was a connection problem or a server error occurred
     */
    public function getBlacklist($id)
    {
        return $this->get("blacklists/" . $id);
    }

    /**
     * Adds a number of expressions to a blacklist.
     *
     * @param integer $id
     *  the id of the blacklist to add the entries to
     * @param string[] $entries
     *  the blacklist entries to add to the blacklist
     * @param string $importName
     *  a unique name for the import that will be displayed in Maileon's web user interface. If this is null, a unique name will be generated.
     * @return \em com_maileon_api_MaileonAPIResult
     *        the result object of the API call.
     * @throws com_maileon_api_MaileonAPIException
     *      if there was a connection problem or a server error occurred
     */
    public function addEntriesToBlacklist($id, $entries, $importName = null)
    {
        if ($importName == null) {
            $importName = "phpclient_import_" . uniqid();
        }
        $action = new AddEntriesAction();
        $action->importName = $importName;
        $action->entries = $entries;
        return $this->post("blacklists/" . $id . "/actions", $action->toXMLString());
    }

}


<?php

namespace PhpOffice\PhpSpreadsheet;

use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;

class NamedRange
{
    /**
     * Range name.
     *
     * @var string
     */
    private $name;

    /**
     * Worksheet on which the named range can be resolved.
     *
     * @var Worksheet
     */
    private $worksheet;

    /**
     * Range of the referenced cells.
     *
     * @var string
     */
    private $range;

    /**
     * Is the named range local? (i.e. can only be used on $this->worksheet).
     *
     * @var bool
     */
    private $localOnly;

    /**
     * Scope.
     *
     * @var Worksheet
     */
    private $scope;

    /**
     * Create a new NamedRange.
     *
     * @param string $pName
     * @param Worksheet $pWorksheet
     * @param string $pRange
     * @param bool $pLocalOnly
     * @param null|Worksheet $pScope Scope. Only applies when $pLocalOnly = true. Null for global scope.
     *
     * @throws Exception
     */
    public function __construct($pName, Worksheet $pWorksheet, $pRange = 'A1', $pLocalOnly = false, $pScope = null)
    {
        // Validate data
        if (($pName === null) || ($pWorksheet === null) || ($pRange === null)) {
            throw new Exception('Parameters can not be null.');
        }

        // Set local members
        $this->name = $pName;
        $this->worksheet = $pWorksheet;
        $this->range = $pRange;
        $this->localOnly = $pLocalOnly;
        $this->scope = ($pLocalOnly == true) ? (($pScope == null) ? $pWorksheet : $pScope) : null;
    }

    /**
     * Get name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set name.
     *
     * @param string $value
     *
     * @return NamedRange
     */
    public function setName($value)
    {
        if ($value !== null) {
            // Old title
            $oldTitle = $this->name;

            // Re-attach
            if ($this->worksheet !== null) {
                $this->worksheet->getParent()->removeNamedRange($this->name, $this->worksheet);
            }
            $this->name = $value;

            if ($this->worksheet !== null) {
                $this->worksheet->getParent()->addNamedRange($this);
            }

            // New title
            $newTitle = $this->name;
            ReferenceHelper::getInstance()->updateNamedFormulas($this->worksheet->getParent(), $oldTitle, $newTitle);
        }

        return $this;
    }

    /**
     * Get worksheet.
     *
     * @return Worksheet
     */
    public function getWorksheet()
    {
        return $this->worksheet;
    }

    /**
     * Set worksheet.
     *
     * @param Worksheet $value
     *
     * @return NamedRange
     */
    public function setWorksheet(Worksheet $value = null)
    {
        if ($value !== null) {
            $this->worksheet = $value;
        }

        return $this;
    }

    /**
     * Get range.
     *
     * @return string
     */
    public function getRange()
    {
        return $this->range;
    }

    /**
     * Set range.
     *
     * @param string $value
     *
     * @return NamedRange
     */
    public function setRange($value)
    {
        if ($value !== null) {
            $this->range = $value;
        }

        return $this;
    }

    /**
     * Get localOnly.
     *
     * @return bool
     */
    public function getLocalOnly()
    {
        return $this->localOnly;
    }

    /**
     * Set localOnly.
     *
     * @param bool $value
     *
     * @return NamedRange
     */
    public function setLocalOnly($value)
    {
        $this->localOnly = $value;
        $this->scope = $value ? $this->worksheet : null;

        return $this;
    }

    /**
     * Get scope.
     *
     * @return null|Worksheet
     */
    public function getScope()
    {
        return $this->scope;
    }

    /**
     * Set scope.
     *
     * @param null|Worksheet $value
     *
     * @return NamedRange
     */
    public function setScope(Worksheet $value = null)
    {
        $this->scope = $value;
        $this->localOnly = ($value == null) ? false : true;

        return $this;
    }

    /**
     * Resolve a named range to a regular cell range.
     *
     * @param string $pNamedRange Named range
     * @param null|Worksheet $pSheet Scope. Use null for global scope
     *
     * @return NamedRange
     */
    public static function resolveRange($pNamedRange, Worksheet $pSheet)
    {
        return $pSheet->getParent()->getNamedRange($pNamedRange, $pSheet);
    }

    /**
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
     */
    public function __clone()
    {
        $vars = get_object_vars($this);
        foreach ($vars as $key => $value) {
            if (is_object($value)) {
                $this->$key = clone $value;
            } else {
                $this->$key = $value;
            }
        }
    }
}


<?php

namespace PhpOffice\PhpSpreadsheet\Shared;

class XMLWriter extends \XMLWriter
{
    public static $debugEnabled = false;

    /** Temporary storage method */
    const STORAGE_MEMORY = 1;
    const STORAGE_DISK = 2;

    /**
     * Temporary filename.
     *
     * @var string
     */
    private $tempFileName = '';

    /**
     * Create a new XMLWriter instance.
     *
     * @param int $pTemporaryStorage Temporary storage location
     * @param string $pTemporaryStorageFolder Temporary storage folder
     */
    public function __construct($pTemporaryStorage = self::STORAGE_MEMORY, $pTemporaryStorageFolder = null)
    {
        // Open temporary storage
        if ($pTemporaryStorage == self::STORAGE_MEMORY) {
            $this->openMemory();
        } else {
            // Create temporary filename
            if ($pTemporaryStorageFolder === null) {
                $pTemporaryStorageFolder = File::sysGetTempDir();
            }
            $this->tempFileName = @tempnam($pTemporaryStorageFolder, 'xml');

            // Open storage
            if ($this->openUri($this->tempFileName) === false) {
                // Fallback to memory...
                $this->openMemory();
            }
        }

        // Set default values
        if (self::$debugEnabled) {
            $this->setIndent(true);
        }
    }

    /**
     * Destructor.
     */
    public function __destruct()
    {
        // Unlink temporary files
        if ($this->tempFileName != '') {
            @unlink($this->tempFileName);
        }
    }

    /**
     * Get written data.
     *
     * @return string
     */
    public function getData()
    {
        if ($this->tempFileName == '') {
            return $this->outputMemory(true);
        }
        $this->flush();

        return file_get_contents($this->tempFileName);
    }

    /**
     * Wrapper method for writeRaw.
     *
     * @param string|string[] $text
     *
     * @return bool
     */
    public function writeRawData($text)
    {
        if (is_array($text)) {
            $text = implode("\n", $text);
        }

        return $this->writeRaw(htmlspecialchars($text));
    }
}


<?php

namespace PhpOffice\PhpSpreadsheet\Worksheet;

use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;

class SheetView
{
    // Sheet View types
    const SHEETVIEW_NORMAL = 'normal';
    const SHEETVIEW_PAGE_LAYOUT = 'pageLayout';
    const SHEETVIEW_PAGE_BREAK_PREVIEW = 'pageBreakPreview';

    private static $sheetViewTypes = [
        self::SHEETVIEW_NORMAL,
        self::SHEETVIEW_PAGE_LAYOUT,
        self::SHEETVIEW_PAGE_BREAK_PREVIEW,
    ];

    /**
     * ZoomScale.
     *
     * Valid values range from 10 to 400.
     *
     * @var int
     */
    private $zoomScale = 100;

    /**
     * ZoomScaleNormal.
     *
     * Valid values range from 10 to 400.
     *
     * @var int
     */
    private $zoomScaleNormal = 100;

    /**
     * View.
     *
     * Valid values range from 10 to 400.
     *
     * @var string
     */
    private $sheetviewType = self::SHEETVIEW_NORMAL;

    /**
     * Create a new SheetView.
     */
    public function __construct()
    {
    }

    /**
     * Get ZoomScale.
     *
     * @return int
     */
    public function getZoomScale()
    {
        return $this->zoomScale;
    }

    /**
     * Set ZoomScale.
     * Valid values range from 10 to 400.
     *
     * @param int $pValue
     *
     * @throws PhpSpreadsheetException
     *
     * @return SheetView
     */
    public function setZoomScale($pValue)
    {
        // Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface,
        // but it is apparently still able to handle any scale >= 1
        if (($pValue >= 1) || $pValue === null) {
            $this->zoomScale = $pValue;
        } else {
            throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.');
        }

        return $this;
    }

    /**
     * Get ZoomScaleNormal.
     *
     * @return int
     */
    public function getZoomScaleNormal()
    {
        return $this->zoomScaleNormal;
    }

    /**
     * Set ZoomScale.
     * Valid values range from 10 to 400.
     *
     * @param int $pValue
     *
     * @throws PhpSpreadsheetException
     *
     * @return SheetView
     */
    public function setZoomScaleNormal($pValue)
    {
        if (($pValue >= 1) || $pValue === null) {
            $this->zoomScaleNormal = $pValue;
        } else {
            throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.');
        }

        return $this;
    }

    /**
     * Get View.
     *
     * @return string
     */
    public function getView()
    {
        return $this->sheetviewType;
    }

    /**
     * Set View.
     *
     * Valid values are
     *        'normal'            self::SHEETVIEW_NORMAL
     *        'pageLayout'        self::SHEETVIEW_PAGE_LAYOUT
     *        'pageBreakPreview'  self::SHEETVIEW_PAGE_BREAK_PREVIEW
     *
     * @param string $pValue
     *
     * @throws PhpSpreadsheetException
     *
     * @return SheetView
     */
    public function setView($pValue)
    {
        // MS Excel 2007 allows setting the view to 'normal', 'pageLayout' or 'pageBreakPreview' via the user interface
        if ($pValue === null) {
            $pValue = self::SHEETVIEW_NORMAL;
        }
        if (in_array($pValue, self::$sheetViewTypes)) {
            $this->sheetviewType = $pValue;
        } else {
            throw new PhpSpreadsheetException('Invalid sheetview layout type.');
        }

        return $this;
    }

    /**
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
     */
    public function __clone()
    {
        $vars = get_object_vars($this);
        foreach ($vars as $key => $value) {
            if (is_object($value)) {
                $this->$key = clone $value;
            } else {
                $this->$key = $value;
            }
        }
    }
}


<?php

namespace Cityware\MemoryShared\Storage;

use Zend\Session\Container as SessionContainer;

/**
 * Storage adapter using the php session shared memory store
 */
class Session implements StorageInterface, Feature\CapacityStorageInterface {

    /**
     * Session object
     * @var \ArrayObject|SessionContainer
     */
    protected $session;

    /**
     * Session namespace
     * @var string
     */
    protected $namespace;

    /**
     * Construct storage
     */
    public function __construct($namespace = 'cityware_memory_shared') {
        $this->namespace = $namespace;
    }

    /**
     * Memory alloc
     */
    protected function alloc() {
        if (null !== $this->session) {
            return;
        }
        if (php_sapi_name() === 'cli') {
            $this->session = new \ArrayObject();
            return;
        }
        $this->session = new SessionContainer($this->namespace);
    }

    /**
     * Test if has datas with $uid key
     * @param mixed $uid
     * @return boolean
     */
    public function has($uid) {
        if (null === $this->session) {
            return false;
        }
        return $this->session->offsetExists($uid);
    }

    /**
     * Read datas with $uid key
     * @param mixed $uid
     * @return mixed
     */
    public function read($uid) {
        $this->alloc();
        return $this->session->offsetGet($uid);
    }

    /**
     * Write datas on $uid key
     * @param mixed $uid
     * @param mixed $mixed
     */
    public function write($uid, $mixed) {
        $this->alloc();
        $this->session->offsetSet($uid, $mixed);
        return true;
    }

    /**
     * Clear datas with $uid key
     * @param mixed $uid
     * @return void
     */
    public function clear($uid = null) {
        $this->alloc();
        if ($uid) {
            return $this->session->offsetUnset($uid);
        }
        return $this->session->exchangeArray(array());
    }

    /**
     * Close storage
     * @param int
     */
    public function close() {
        return;
    }

    /**
     * Get max bloc allow
     */
    public function canAllowBlocsMemory($numBloc) {
        return true; // no limitation
    }

}


<?php

namespace PhpOffice\PhpSpreadsheet\Shared\Trend;

use PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix;

class PolynomialBestFit extends BestFit
{
    /**
     * Algorithm type to use for best-fit
     * (Name of this Trend class).
     *
     * @var string
     */
    protected $bestFitType = 'polynomial';

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

    /**
     * Return the order of this polynomial.
     *
     * @return int
     */
    public function getOrder()
    {
        return $this->order;
    }

    /**
     * Return the Y-Value for a specified value of X.
     *
     * @param float $xValue X-Value
     *
     * @return float Y-Value
     */
    public function getValueOfYForX($xValue)
    {
        $retVal = $this->getIntersect();
        $slope = $this->getSlope();
        foreach ($slope as $key => $value) {
            if ($value != 0.0) {
                $retVal += $value * pow($xValue, $key + 1);
            }
        }

        return $retVal;
    }

    /**
     * Return the X-Value for a specified value of Y.
     *
     * @param float $yValue Y-Value
     *
     * @return float X-Value
     */
    public function getValueOfXForY($yValue)
    {
        return ($yValue - $this->getIntersect()) / $this->getSlope();
    }

    /**
     * Return the Equation of the best-fit line.
     *
     * @param int $dp Number of places of decimal precision to display
     *
     * @return string
     */
    public function getEquation($dp = 0)
    {
        $slope = $this->getSlope($dp);
        $intersect = $this->getIntersect($dp);

        $equation = 'Y = ' . $intersect;
        foreach ($slope as $key => $value) {
            if ($value != 0.0) {
                $equation .= ' + ' . $value . ' * X';
                if ($key > 0) {
                    $equation .= '^' . ($key + 1);
                }
            }
        }

        return $equation;
    }

    /**
     * Return the Slope of the line.
     *
     * @param int $dp Number of places of decimal precision to display
     *
     * @return string
     */
    public function getSlope($dp = 0)
    {
        if ($dp != 0) {
            $coefficients = [];
            foreach ($this->slope as $coefficient) {
                $coefficients[] = round($coefficient, $dp);
            }

            return $coefficients;
        }

        return $this->slope;
    }

    public function getCoefficients($dp = 0)
    {
        return array_merge([$this->getIntersect($dp)], $this->getSlope($dp));
    }

    /**
     * Execute the regression and calculate the goodness of fit for a set of X and Y data values.
     *
     * @param int $order Order of Polynomial for this regression
     * @param float[] $yValues The set of Y-values for this regression
     * @param float[] $xValues The set of X-values for this regression
     */
    private function polynomialRegression($order, $yValues, $xValues)
    {
        // calculate sums
        $x_sum = array_sum($xValues);
        $y_sum = array_sum($yValues);
        $xx_sum = $xy_sum = 0;
        for ($i = 0; $i < $this->valueCount; ++$i) {
            $xy_sum += $xValues[$i] * $yValues[$i];
            $xx_sum += $xValues[$i] * $xValues[$i];
            $yy_sum += $yValues[$i] * $yValues[$i];
        }
        /*
         *    This routine uses logic from the PHP port of polyfit version 0.1
         *    written by Michael Bommarito and Paul Meagher
         *
         *    The function fits a polynomial function of order $order through
         *    a series of x-y data points using least squares.
         *
         */
        for ($i = 0; $i < $this->valueCount; ++$i) {
            for ($j = 0; $j <= $order; ++$j) {
                $A[$i][$j] = pow($xValues[$i], $j);
            }
        }
        for ($i = 0; $i < $this->valueCount; ++$i) {
            $B[$i] = [$yValues[$i]];
        }
        $matrixA = new Matrix($A);
        $matrixB = new Matrix($B);
        $C = $matrixA->solve($matrixB);

        $coefficients = [];
        for ($i = 0; $i < $C->getRowDimension(); ++$i) {
            $r = $C->get($i, 0);
            if (abs($r) <= pow(10, -9)) {
                $r = 0;
            }
            $coefficients[] = $r;
        }

        $this->intersect = array_shift($coefficients);
        $this->slope = $coefficients;

        $this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum);
        foreach ($this->xValues as $xKey => $xValue) {
            $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
        }
    }

    /**
     * Define the regression and calculate the goodness of fit for a set of X and Y data values.
     *
     * @param int $order Order of Polynomial for this regression
     * @param float[] $yValues The set of Y-values for this regression
     * @param float[] $xValues The set of X-values for this regression
     * @param bool $const
     */
    public function __construct($order, $yValues, $xValues = [], $const = true)
    {
        if (parent::__construct($yValues, $xValues) !== false) {
            if ($order < $this->valueCount) {
                $this->bestFitType .= '_' . $order;
                $this->order = $order;
                $this->polynomialRegression($order, $yValues, $xValues);
                if (($this->getGoodnessOfFit() < 0.0) || ($this->getGoodnessOfFit() > 1.0)) {
                    $this->error = true;
                }
            } else {
                $this->error = true;
            }
        }
    }
}


<?php declare(strict_types=1);

namespace AlecRabbit\ConsoleColour\Core;

use AlecRabbit\ConsoleColour\ConsoleColor;
use AlecRabbit\ConsoleColour\Exception\InvalidStyleException;

abstract class AbstractThemes
{
    /** @var array */
    protected $definedThemes;

    /** @var bool */
    protected $doColorize = false;

    /** @var ConsoleColor */
    protected $color;

    /**
     * Themed constructor.
     * @param null|bool $colorize
     * @throws InvalidStyleException
     */
    public function __construct(?bool $colorize = null)
    {
        $this->color = new ConsoleColor();
        $this->doColorize = $this->refineColorize($colorize);
        $this->setThemes();
    }

    /**
     * @param null|bool $colorize
     * @return bool
     */
    protected function refineColorize(?bool $colorize): bool
    {
        if ($supported = $this->color->isSupported()) {
            return $colorize ?? $supported;
        }
        // @codeCoverageIgnoreStart
        return false;
        // @codeCoverageIgnoreEnd
    }

    /**
     * @param bool $override
     * @throws InvalidStyleException
     */
    protected function setThemes(bool $override = false): void
    {
        foreach ($this->getThemes() as $name => $styles) {
            $this->color->addTheme($name, $styles, $override);
        }
    }

    /**
     * @return array
     *
     * @psalm-suppress RedundantConditionGivenDocblockType
     */
    public function getThemes(): array
    {
        if (null !== $this->definedThemes) {
            return $this->definedThemes;
        }
        return
            $this->definedThemes = $this->prepareThemes();
    }

    /**
     * @return array
     */
    abstract protected function prepareThemes(): array;

    /**
     * @param string $name
     * @param array $arguments
     * @return string
     * @throws \Throwable
     */
    public function __call(string $name, array $arguments): string
    {
        $this->assertMethodName($name);
        $this->assertArgs($name, $arguments);

        return
            $this->apply($this->definedThemes[$name], $arguments[0]);
    }

    /**
     * @param string $name
     */
    protected function assertMethodName(string $name): void
    {
        if (!\array_key_exists($name, $this->definedThemes)) {
            throw new \BadMethodCallException('Unknown method call [' . static::class . '::' . $name . '].');
        }
    }

    /**
     * @param string $name
     * @param array $arguments
     */
    protected function assertArgs(string $name, array $arguments): void
    {
        if (1 !== \count($arguments)) {
            throw new \ArgumentCountError(
                'Method [' . static::class . '::' . $name . '] accepts only one argument.'
            );
        }
    }

    /**
     * @param array|string $style
     * @param string $text
     * @return string
     * @throws \Throwable
     */
    protected function apply($style, $text): string
    {
        return
            $this->doColorize ? $this->color->apply($style, $text) : $text;
    }
}


<?php

namespace Cityware\MemoryShared\Storage;

class Bloc implements StorageInterface, Feature\CapacityStorageInterface {

    /**
     * identifier
     * @var resource
     */
    protected $identifier;

    /**
     *
     * @var mixed
     */
    protected $memory;

    /**
     * Bloc size
     * @var int
     */
    protected $segmentSize = 256;

    /**
     * Construct segment memory
     * @param type $identifier
     */
    public function __construct($identifier = 'Z') {
        if (is_array($identifier)) {
            if (!isset($identifier['identifier'])) {
                throw new Exception\RuntimeException(
                'Segment storage options must be an identifier '
                . 'name or array with a "identifier" key'
                );
            }
            $identifier = $identifier['identifier'];
        }
        $this->identifier = $identifier;
    }

    public function realloc($segmentSize) {
        $this->close();
        $this->setSegmentSize($segmentSize);
    }

    /**
     * Memory alloc
     */
    protected function alloc() {
        if (null !== $this->memory) {
            return;
        }
        $this->memory = shm_attach(ftok(__FILE__, $this->identifier), $this->segmentSize, 0644);
    }

    /**
     * Test if has datas with $uid key
     * @param mixed $uid
     * @return boolean
     */
    public function has($uid) {
        if (null === $this->memory) {
            return false;
        }
        return shm_has_var($this->memory, $uid);
    }

    /**
     * Read datas with $uid key
     * @param mixed $uid
     * @return mixed
     */
    public function read($uid) {
        if (!is_int($uid) && !is_numeric($uid)) {
            throw new Exception\RuntimeException('Segment type key must integer or numeric.');
        }
        $this->alloc();
        $strShm = shm_get_var($this->memory, $uid);
        $str = trim($strShm);
        if (!$str) {
            return false;
        }
        return $str;
    }

    /**
     * Write datas on $uid key
     * @param mixed $uid
     * @param mixed $mixed
     */
    public function write($uid, $mixed) {
        if (!is_int($uid) && !is_numeric($uid)) {
            throw new Exception\RuntimeException('Segment type key must integer or numeric.');
        }
        $this->alloc();
        return shm_put_var($this->memory, $uid, $mixed);
    }

    /**
     * Clear datas with $uid key
     * @param mixed $uid
     * @return void
     */
    public function clear($uid = null) {
        if (null === $uid) {
            $this->alloc();
            return shm_remove($this->memory);
        }
        if (!is_int($uid) && !is_numeric($uid)) {
            throw new Exception\RuntimeException('Segment type key must integer or numeric.');
        }
        $this->alloc();
        if (!$this->has($uid)) {
            return false;
        }
        return shm_remove_var($this->memory, $uid);
    }

    /**
     * Close segment
     * @param int
     */
    public function close() {
        if (null === $this->memory) {
            return;
        }
        shm_detach($this->memory);
        $this->memory = null;
    }

    /**
     * Get segment memory
     * @return type
     */
    public function getSegment() {
        return $this->memory;
    }

    /**
     * Get segment size
     * @return int
     */
    public function getSegmentSize() {
        return $this->segmentSize;
    }

    /**
     * Set segment size
     * @param int
     */
    public function setSegmentSize($size) {
        if (null !== $this->memory) {
            throw new Exception\RuntimeException(
            'You can not change the segment size because memory is already allocated.'
            . ' Use realloc() function to create new memory segment.'
            );
        }
        $this->segmentSize = (integer) $size;
        return $this;
    }

    /**
     * Get max bloc allow
     */
    public function canAllowBlocsMemory($numBloc) {
        return $this->segmentSize >= $numBloc;
    }

}


<?php

namespace PhpOffice\PhpSpreadsheet\Worksheet;

use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\Shared\Date;

class AutoFilter
{
    /**
     * Autofilter Worksheet.
     *
     * @var Worksheet
     */
    private $workSheet;

    /**
     * Autofilter Range.
     *
     * @var string
     */
    private $range = '';

    /**
     * Autofilter Column Ruleset.
     *
     * @var AutoFilter\Column[]
     */
    private $columns = [];

    /**
     * Create a new AutoFilter.
     *
     * @param string $pRange Cell range (i.e. A1:E10)
     * @param Worksheet $pSheet
     */
    public function __construct($pRange = '', Worksheet $pSheet = null)
    {
        $this->range = $pRange;
        $this->workSheet = $pSheet;
    }

    /**
     * Get AutoFilter Parent Worksheet.
     *
     * @return Worksheet
     */
    public function getParent()
    {
        return $this->workSheet;
    }

    /**
     * Set AutoFilter Parent Worksheet.
     *
     * @param Worksheet $pSheet
     *
     * @return AutoFilter
     */
    public function setParent(Worksheet $pSheet = null)
    {
        $this->workSheet = $pSheet;

        return $this;
    }

    /**
     * Get AutoFilter Range.
     *
     * @return string
     */
    public function getRange()
    {
        return $this->range;
    }

    /**
     * Set AutoFilter Range.
     *
     * @param string $pRange Cell range (i.e. A1:E10)
     *
     * @throws PhpSpreadsheetException
     *
     * @return AutoFilter
     */
    public function setRange($pRange)
    {
        // Uppercase coordinate
        $cellAddress = explode('!', strtoupper($pRange));
        if (count($cellAddress) > 1) {
            list($worksheet, $pRange) = $cellAddress;
        }

        if (strpos($pRange, ':') !== false) {
            $this->range = $pRange;
        } elseif (empty($pRange)) {
            $this->range = '';
        } else {
            throw new PhpSpreadsheetException('Autofilter must be set on a range of cells.');
        }

        if (empty($pRange)) {
            //    Discard all column rules
            $this->columns = [];
        } else {
            //    Discard any column rules that are no longer valid within this range
            list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($this->range);
            foreach ($this->columns as $key => $value) {
                $colIndex = Coordinate::columnIndexFromString($key);
                if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) {
                    unset($this->columns[$key]);
                }
            }
        }

        return $this;
    }

    /**
     * Get all AutoFilter Columns.
     *
     * @return AutoFilter\Column[]
     */
    public function getColumns()
    {
        return $this->columns;
    }

    /**
     * Validate that the specified column is in the AutoFilter range.
     *
     * @param string $column Column name (e.g. A)
     *
     * @throws PhpSpreadsheetException
     *
     * @return int The column offset within the autofilter range
     */
    public function testColumnInRange($column)
    {
        if (empty($this->range)) {
            throw new PhpSpreadsheetException('No autofilter range is defined.');
        }

        $columnIndex = Coordinate::columnIndexFromString($column);
        list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($this->range);
        if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) {
            throw new PhpSpreadsheetException('Column is outside of current autofilter range.');
        }

        return $columnIndex - $rangeStart[0];
    }

    /**
     * Get a specified AutoFilter Column Offset within the defined AutoFilter range.
     *
     * @param string $pColumn Column name (e.g. A)
     *
     * @throws PhpSpreadsheetException
     *
     * @return int The offset of the specified column within the autofilter range
     */
    public function getColumnOffset($pColumn)
    {
        return $this->testColumnInRange($pColumn);
    }

    /**
     * Get a specified AutoFilter Column.
     *
     * @param string $pColumn Column name (e.g. A)
     *
     * @throws PhpSpreadsheetException
     *
     * @return AutoFilter\Column
     */
    public function getColumn($pColumn)
    {
        $this->testColumnInRange($pColumn);

        if (!isset($this->columns[$pColumn])) {
            $this->columns[$pColumn] = new AutoFilter\Column($pColumn, $this);
        }

        return $this->columns[$pColumn];
    }

    /**
     * Get a specified AutoFilter Column by it's offset.
     *
     * @param int $pColumnOffset Column offset within range (starting from 0)
     *
     * @throws PhpSpreadsheetException
     *
     * @return AutoFilter\Column
     */
    public function getColumnByOffset($pColumnOffset)
    {
        list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($this->range);
        $pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $pColumnOffset);

        return $this->getColumn($pColumn);
    }

    /**
     * Set AutoFilter.
     *
     * @param AutoFilter\Column|string $pColumn
     *            A simple string containing a Column ID like 'A' is permitted
     *
     * @throws PhpSpreadsheetException
     *
     * @return AutoFilter
     */
    public function setColumn($pColumn)
    {
        if ((is_string($pColumn)) && (!empty($pColumn))) {
            $column = $pColumn;
        } elseif (is_object($pColumn) && ($pColumn instanceof AutoFilter\Column)) {
            $column = $pColumn->getColumnIndex();
        } else {
            throw new PhpSpreadsheetException('Column is not within the autofilter range.');
        }
        $this->testColumnInRange($column);

        if (is_string($pColumn)) {
            $this->columns[$pColumn] = new AutoFilter\Column($pColumn, $this);
        } elseif (is_object($pColumn) && ($pColumn instanceof AutoFilter\Column)) {
            $pColumn->setParent($this);
            $this->columns[$column] = $pColumn;
        }
        ksort($this->columns);

        return $this;
    }

    /**
     * Clear a specified AutoFilter Column.
     *
     * @param string $pColumn Column name (e.g. A)
     *
     * @throws PhpSpreadsheetException
     *
     * @return AutoFilter
     */
    public function clearColumn($pColumn)
    {
        $this->testColumnInRange($pColumn);

        if (isset($this->columns[$pColumn])) {
            unset($this->columns[$pColumn]);
        }

        return $this;
    }

    /**
     * Shift an AutoFilter Column Rule to a different column.
     *
     * Note: This method bypasses validation of the destination column to ensure it is within this AutoFilter range.
     *        Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value.
     *        Use with caution.
     *
     * @param string $fromColumn Column name (e.g. A)
     * @param string $toColumn Column name (e.g. B)
     *
     * @return AutoFilter
     */
    public function shiftColumn($fromColumn, $toColumn)
    {
        $fromColumn = strtoupper($fromColumn);
        $toColumn = strtoupper($toColumn);

        if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) {
            $this->columns[$fromColumn]->setParent();
            $this->columns[$fromColumn]->setColumnIndex($toColumn);
            $this->columns[$toColumn] = $this->columns[$fromColumn];
            $this->columns[$toColumn]->setParent($this);
            unset($this->columns[$fromColumn]);

            ksort($this->columns);
        }

        return $this;
    }

    /**
     * Test if cell value is in the defined set of values.
     *
     * @param mixed $cellValue
     * @param mixed[] $dataSet
     *
     * @return bool
     */
    private static function filterTestInSimpleDataSet($cellValue, $dataSet)
    {
        $dataSetValues = $dataSet['filterValues'];
        $blanks = $dataSet['blanks'];
        if (($cellValue == '') || ($cellValue === null)) {
            return $blanks;
        }

        return in_array($cellValue, $dataSetValues);
    }

    /**
     * Test if cell value is in the defined set of Excel date values.
     *
     * @param mixed $cellValue
     * @param mixed[] $dataSet
     *
     * @return bool
     */
    private static function filterTestInDateGroupSet($cellValue, $dataSet)
    {
        $dateSet = $dataSet['filterValues'];
        $blanks = $dataSet['blanks'];
        if (($cellValue == '') || ($cellValue === null)) {
            return $blanks;
        }

        if (is_numeric($cellValue)) {
            $dateValue = Date::excelToTimestamp($cellValue);
            if ($cellValue < 1) {
                //    Just the time part
                $dtVal = date('His', $dateValue);
                $dateSet = $dateSet['time'];
            } elseif ($cellValue == floor($cellValue)) {
                //    Just the date part
                $dtVal = date('Ymd', $dateValue);
                $dateSet = $dateSet['date'];
            } else {
                //    date and time parts
                $dtVal = date('YmdHis', $dateValue);
                $dateSet = $dateSet['dateTime'];
            }
            foreach ($dateSet as $dateValue) {
                //    Use of substr to extract value at the appropriate group level
                if (substr($dtVal, 0, strlen($dateValue)) == $dateValue) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Test if cell value is within a set of values defined by a ruleset.
     *
     * @param mixed $cellValue
     * @param mixed[] $ruleSet
     *
     * @return bool
     */
    private static function filterTestInCustomDataSet($cellValue, $ruleSet)
    {
        $dataSet = $ruleSet['filterRules'];
        $join = $ruleSet['join'];
        $customRuleForBlanks = isset($ruleSet['customRuleForBlanks']) ? $ruleSet['customRuleForBlanks'] : false;

        if (!$customRuleForBlanks) {
            //    Blank cells are always ignored, so return a FALSE
            if (($cellValue == '') || ($cellValue === null)) {
                return false;
            }
        }
        $returnVal = ($join == AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND);
        foreach ($dataSet as $rule) {
            if (is_numeric($rule['value'])) {
                //    Numeric values are tested using the appropriate operator
                switch ($rule['operator']) {
                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL:
                        $retVal = ($cellValue == $rule['value']);

                        break;
                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL:
                        $retVal = ($cellValue != $rule['value']);

                        break;
                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN:
                        $retVal = ($cellValue > $rule['value']);

                        break;
                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL:
                        $retVal = ($cellValue >= $rule['value']);

                        break;
                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN:
                        $retVal = ($cellValue < $rule['value']);

                        break;
                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL:
                        $retVal = ($cellValue <= $rule['value']);

                        break;
                }
            } elseif ($rule['value'] == '') {
                switch ($rule['operator']) {
                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL:
                        $retVal = (($cellValue == '') || ($cellValue === null));

                        break;
                    case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL:
                        $retVal = (($cellValue != '') && ($cellValue !== null));

                        break;
                    default:
                        $retVal = true;

                        break;
                }
            } else {
                //    String values are always tested for equality, factoring in for wildcards (hence a regexp test)
                $retVal = preg_match('/^' . $rule['value'] . '$/i', $cellValue);
            }
            //    If there are multiple conditions, then we need to test both using the appropriate join operator
            switch ($join) {
                case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR:
                    $returnVal = $returnVal || $retVal;
                    //    Break as soon as we have a TRUE match for OR joins,
                    //        to avoid unnecessary additional code execution
                    if ($returnVal) {
                        return $returnVal;
                    }

                    break;
                case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND:
                    $returnVal = $returnVal && $retVal;

                    break;
            }
        }

        return $returnVal;
    }

    /**
     * Test if cell date value is matches a set of values defined by a set of months.
     *
     * @param mixed $cellValue
     * @param mixed[] $monthSet
     *
     * @return bool
     */
    private static function filterTestInPeriodDateSet($cellValue, $monthSet)
    {
        //    Blank cells are always ignored, so return a FALSE
        if (($cellValue == '') || ($cellValue === null)) {
            return false;
        }

        if (is_numeric($cellValue)) {
            $dateValue = date('m', Date::excelToTimestamp($cellValue));
            if (in_array($dateValue, $monthSet)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Search/Replace arrays to convert Excel wildcard syntax to a regexp syntax for preg_matching.
     *
     * @var array
     */
    private static $fromReplace = ['\*', '\?', '~~', '~.*', '~.?'];

    private static $toReplace = ['.*', '.', '~', '\*', '\?'];

    /**
     * Convert a dynamic rule daterange to a custom filter range expression for ease of calculation.
     *
     * @param string $dynamicRuleType
     * @param AutoFilter\Column $filterColumn
     *
     * @return mixed[]
     */
    private function dynamicFilterDateRange($dynamicRuleType, &$filterColumn)
    {
        $rDateType = Functions::getReturnDateType();
        Functions::setReturnDateType(Functions::RETURNDATE_PHP_NUMERIC);
        $val = $maxVal = null;

        $ruleValues = [];
        $baseDate = DateTime::DATENOW();
        //    Calculate start/end dates for the required date range based on current date
        switch ($dynamicRuleType) {
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK:
                $baseDate = strtotime('-7 days', $baseDate);

                break;
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK:
                $baseDate = strtotime('-7 days', $baseDate);

                break;
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH:
                $baseDate = strtotime('-1 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));

                break;
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH:
                $baseDate = strtotime('+1 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));

                break;
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER:
                $baseDate = strtotime('-3 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));

                break;
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER:
                $baseDate = strtotime('+3 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));

                break;
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR:
                $baseDate = strtotime('-1 year', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));

                break;
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR:
                $baseDate = strtotime('+1 year', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));

                break;
        }

        switch ($dynamicRuleType) {
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TODAY:
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY:
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW:
                $maxVal = (int) Date::PHPtoExcel(strtotime('+1 day', $baseDate));
                $val = (int) Date::PHPToExcel($baseDate);

                break;
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE:
                $maxVal = (int) Date::PHPtoExcel(strtotime('+1 day', $baseDate));
                $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1, date('Y', $baseDate)));

                break;
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR:
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR:
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR:
                $maxVal = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 31, 12, date('Y', $baseDate)));
                ++$maxVal;
                $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1, date('Y', $baseDate)));

                break;
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER:
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER:
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER:
                $thisMonth = date('m', $baseDate);
                $thisQuarter = floor(--$thisMonth / 3);
                $maxVal = (int) Date::PHPtoExcel(gmmktime(0, 0, 0, date('t', $baseDate), (1 + $thisQuarter) * 3, date('Y', $baseDate)));
                ++$maxVal;
                $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1 + $thisQuarter * 3, date('Y', $baseDate)));

                break;
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH:
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH:
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH:
                $maxVal = (int) Date::PHPtoExcel(gmmktime(0, 0, 0, date('t', $baseDate), date('m', $baseDate), date('Y', $baseDate)));
                ++$maxVal;
                $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));

                break;
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK:
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK:
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK:
                $dayOfWeek = date('w', $baseDate);
                $val = (int) Date::PHPToExcel($baseDate) - $dayOfWeek;
                $maxVal = $val + 7;

                break;
        }

        switch ($dynamicRuleType) {
            //    Adjust Today dates for Yesterday and Tomorrow
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY:
                --$maxVal;
                --$val;

                break;
            case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW:
                ++$maxVal;
                ++$val;

                break;
        }

        //    Set the filter column rule attributes ready for writing
        $filterColumn->setAttributes(['val' => $val, 'maxVal' => $maxVal]);

        //    Set the rules for identifying rows for hide/show
        $ruleValues[] = ['operator' => AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, 'value' => $val];
        $ruleValues[] = ['operator' => AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN, 'value' => $maxVal];
        Functions::setReturnDateType($rDateType);

        return ['method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND]];
    }

    private function calculateTopTenValue($columnID, $startRow, $endRow, $ruleType, $ruleValue)
    {
        $range = $columnID . $startRow . ':' . $columnID . $endRow;
        $dataValues = Functions::flattenArray($this->workSheet->rangeToArray($range, null, true, false));

        $dataValues = array_filter($dataValues);
        if ($ruleType == AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) {
            rsort($dataValues);
        } else {
            sort($dataValues);
        }

        return array_pop(array_slice($dataValues, 0, $ruleValue));
    }

    /**
     * Apply the AutoFilter rules to the AutoFilter Range.
     *
     * @throws PhpSpreadsheetException
     *
     * @return AutoFilter
     */
    public function showHideRows()
    {
        list($rangeStart, $rangeEnd) = Coordinate::rangeBoundaries($this->range);

        //    The heading row should always be visible
        $this->workSheet->getRowDimension($rangeStart[1])->setVisible(true);

        $columnFilterTests = [];
        foreach ($this->columns as $columnID => $filterColumn) {
            $rules = $filterColumn->getRules();
            switch ($filterColumn->getFilterType()) {
                case AutoFilter\Column::AUTOFILTER_FILTERTYPE_FILTER:
                    $ruleType = null;
                    $ruleValues = [];
                    //    Build a list of the filter value selections
                    foreach ($rules as $rule) {
                        $ruleType = $rule->getRuleType();
                        $ruleValues[] = $rule->getValue();
                    }
                    //    Test if we want to include blanks in our filter criteria
                    $blanks = false;
                    $ruleDataSet = array_filter($ruleValues);
                    if (count($ruleValues) != count($ruleDataSet)) {
                        $blanks = true;
                    }
                    if ($ruleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_FILTER) {
                        //    Filter on absolute values
                        $columnFilterTests[$columnID] = [
                            'method' => 'filterTestInSimpleDataSet',
                            'arguments' => ['filterValues' => $ruleDataSet, 'blanks' => $blanks],
                        ];
                    } else {
                        //    Filter on date group values
                        $arguments = [
                            'date' => [],
                            'time' => [],
                            'dateTime' => [],
                        ];
                        foreach ($ruleDataSet as $ruleValue) {
                            $date = $time = '';
                            if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR])) &&
                                ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR] !== '')) {
                                $date .= sprintf('%04d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR]);
                            }
                            if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH])) &&
                                ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH] != '')) {
                                $date .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH]);
                            }
                            if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY])) &&
                                ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY] !== '')) {
                                $date .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY]);
                            }
                            if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR])) &&
                                ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR] !== '')) {
                                $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR]);
                            }
                            if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE])) &&
                                ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE] !== '')) {
                                $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE]);
                            }
                            if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND])) &&
                                ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND] !== '')) {
                                $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND]);
                            }
                            $dateTime = $date . $time;
                            $arguments['date'][] = $date;
                            $arguments['time'][] = $time;
                            $arguments['dateTime'][] = $dateTime;
                        }
                        //    Remove empty elements
                        $arguments['date'] = array_filter($arguments['date']);
                        $arguments['time'] = array_filter($arguments['time']);
                        $arguments['dateTime'] = array_filter($arguments['dateTime']);
                        $columnFilterTests[$columnID] = [
                            'method' => 'filterTestInDateGroupSet',
                            'arguments' => ['filterValues' => $arguments, 'blanks' => $blanks],
                        ];
                    }

                    break;
                case AutoFilter\Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER:
                    $customRuleForBlanks = false;
                    $ruleValues = [];
                    //    Build a list of the filter value selections
                    foreach ($rules as $rule) {
                        $ruleValue = $rule->getValue();
                        if (!is_numeric($ruleValue)) {
                            //    Convert to a regexp allowing for regexp reserved characters, wildcards and escaped wildcards
                            $ruleValue = preg_quote($ruleValue);
                            $ruleValue = str_replace(self::$fromReplace, self::$toReplace, $ruleValue);
                            if (trim($ruleValue) == '') {
                                $customRuleForBlanks = true;
                                $ruleValue = trim($ruleValue);
                            }
                        }
                        $ruleValues[] = ['operator' => $rule->getOperator(), 'value' => $ruleValue];
                    }
                    $join = $filterColumn->getJoin();
                    $columnFilterTests[$columnID] = [
                        'method' => 'filterTestInCustomDataSet',
                        'arguments' => ['filterRules' => $ruleValues, 'join' => $join, 'customRuleForBlanks' => $customRuleForBlanks],
                    ];

                    break;
                case AutoFilter\Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER:
                    $ruleValues = [];
                    foreach ($rules as $rule) {
                        //    We should only ever have one Dynamic Filter Rule anyway
                        $dynamicRuleType = $rule->getGrouping();
                        if (($dynamicRuleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) ||
                            ($dynamicRuleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE)) {
                            //    Number (Average) based
                            //    Calculate the average
                            $averageFormula = '=AVERAGE(' . $columnID . ($rangeStart[1] + 1) . ':' . $columnID . $rangeEnd[1] . ')';
                            $average = Calculation::getInstance()->calculateFormula($averageFormula, null, $this->workSheet->getCell('A1'));
                            //    Set above/below rule based on greaterThan or LessTan
                            $operator = ($dynamicRuleType === AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE)
                                ? AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN
                                : AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN;
                            $ruleValues[] = [
                                'operator' => $operator,
                                'value' => $average,
                            ];
                            $columnFilterTests[$columnID] = [
                                'method' => 'filterTestInCustomDataSet',
                                'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR],
                            ];
                        } else {
                            //    Date based
                            if ($dynamicRuleType[0] == 'M' || $dynamicRuleType[0] == 'Q') {
                                //    Month or Quarter
                                sscanf($dynamicRuleType, '%[A-Z]%d', $periodType, $period);
                                if ($periodType == 'M') {
                                    $ruleValues = [$period];
                                } else {
                                    --$period;
                                    $periodEnd = (1 + $period) * 3;
                                    $periodStart = 1 + $period * 3;
                                    $ruleValues = range($periodStart, $periodEnd);
                                }
                                $columnFilterTests[$columnID] = [
                                    'method' => 'filterTestInPeriodDateSet',
                                    'arguments' => $ruleValues,
                                ];
                                $filterColumn->setAttributes([]);
                            } else {
                                //    Date Range
                                $columnFilterTests[$columnID] = $this->dynamicFilterDateRange($dynamicRuleType, $filterColumn);

                                break;
                            }
                        }
                    }

                    break;
                case AutoFilter\Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER:
                    $ruleValues = [];
                    $dataRowCount = $rangeEnd[1] - $rangeStart[1];
                    foreach ($rules as $rule) {
                        //    We should only ever have one Dynamic Filter Rule anyway
                        $toptenRuleType = $rule->getGrouping();
                        $ruleValue = $rule->getValue();
                        $ruleOperator = $rule->getOperator();
                    }
                    if ($ruleOperator === AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) {
                        $ruleValue = floor($ruleValue * ($dataRowCount / 100));
                    }
                    if ($ruleValue < 1) {
                        $ruleValue = 1;
                    }
                    if ($ruleValue > 500) {
                        $ruleValue = 500;
                    }

                    $maxVal = $this->calculateTopTenValue($columnID, $rangeStart[1] + 1, $rangeEnd[1], $toptenRuleType, $ruleValue);

                    $operator = ($toptenRuleType == AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP)
                        ? AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL
                        : AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL;
                    $ruleValues[] = ['operator' => $operator, 'value' => $maxVal];
                    $columnFilterTests[$columnID] = [
                        'method' => 'filterTestInCustomDataSet',
                        'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR],
                    ];
                    $filterColumn->setAttributes(['maxVal' => $maxVal]);

                    break;
            }
        }

        //    Execute the column tests for each row in the autoFilter range to determine show/hide,
        for ($row = $rangeStart[1] + 1; $row <= $rangeEnd[1]; ++$row) {
            $result = true;
            foreach ($columnFilterTests as $columnID => $columnFilterTest) {
                $cellValue = $this->workSheet->getCell($columnID . $row)->getCalculatedValue();
                //    Execute the filter test
                $result = $result &&
                    call_user_func_array(
                        [self::class, $columnFilterTest['method']],
                        [$cellValue, $columnFilterTest['arguments']]
                    );
                //    If filter test has resulted in FALSE, exit the loop straightaway rather than running any more tests
                if (!$result) {
                    break;
                }
            }
            //    Set show/hide for the row based on the result of the autoFilter result
            $this->workSheet->getRowDimension($row)->setVisible($result);
        }

        return $this;
    }

    /**
     * Implement PHP __clone to create a deep clone, not just a shallow copy.
     */
    public function __clone()
    {
        $vars = get_object_vars($this);
        foreach ($vars as $key => $value) {
            if (is_object($value)) {
                if ($key == 'workSheet') {
                    //    Detach from worksheet
                    $this->{$key} = null;
                } else {
                    $this->{$key} = clone $value;
                }
            } elseif ((is_array($value)) && ($key == 'columns')) {
                //    The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\AutoFilter objects
                $this->{$key} = [];
                foreach ($value as $k => $v) {
                    $this->{$key}[$k] = clone $v;
                    // attach the new cloned Column to this new cloned Autofilter object
                    $this->{$key}[$k]->setParent($this);
                }
            } else {
                $this->{$key} = $value;
            }
        }
    }

    /**
     * toString method replicates previous behavior by returning the range if object is
     * referenced as a property of its parent.
     */
    public function __toString()
    {
        return (string) $this->range;
    }
}


<?php

namespace PhpOffice\PhpSpreadsheet\Shared\OLE;

// vim: set expandtab tabstop=4 shiftwidth=4:
// +----------------------------------------------------------------------+
// | PHP Version 4                                                        |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group                                |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license,      |
// | that is bundled with this package in the file LICENSE, and is        |
// | available at through the world-wide-web at                           |
// | http://www.php.net/license/2_02.txt.                                 |
// | If you did not receive a copy of the PHP license and are unable to   |
// | obtain it through the world-wide-web, please send a note to          |
// | license@php.net so we can mail you a copy immediately.               |
// +----------------------------------------------------------------------+
// | Author: Xavier Noguer <xnoguer@php.net>                              |
// | Based on OLE::Storage_Lite by Kawai, Takanori                        |
// +----------------------------------------------------------------------+
//
use PhpOffice\PhpSpreadsheet\Shared\OLE;

/**
 * Class for creating PPS's for OLE containers.
 *
 * @author   Xavier Noguer <xnoguer@php.net>
 *
 * @category PhpSpreadsheet
 */
class PPS
{
    /**
     * The PPS index.
     *
     * @var int
     */
    public $No;

    /**
     * The PPS name (in Unicode).
     *
     * @var string
     */
    public $Name;

    /**
     * The PPS type. Dir, Root or File.
     *
     * @var int
     */
    public $Type;

    /**
     * The index of the previous PPS.
     *
     * @var int
     */
    public $PrevPps;

    /**
     * The index of the next PPS.
     *
     * @var int
     */
    public $NextPps;

    /**
     * The index of it's first child if this is a Dir or Root PPS.
     *
     * @var int
     */
    public $DirPps;

    /**
     * A timestamp.
     *
     * @var int
     */
    public $Time1st;

    /**
     * A timestamp.
     *
     * @var int
     */
    public $Time2nd;

    /**
     * Starting block (small or big) for this PPS's data  inside the container.
     *
     * @var int
     */
    public $startBlock;

    /**
     * The size of the PPS's data (in bytes).
     *
     * @var int
     */
    public $Size;

    /**
     * The PPS's data (only used if it's not using a temporary file).
     *
     * @var string
     */
    public $_data;

    /**
     * Array of child PPS's (only used by Root and Dir PPS's).
     *
     * @var array
     */
    public $children = [];

    /**
     * Pointer to OLE container.
     *
     * @var OLE
     */
    public $ole;

    /**
     * The constructor.
     *
     * @param int $No The PPS index
     * @param string $name The PPS name
     * @param int $type The PPS type. Dir, Root or File
     * @param int $prev The index of the previous PPS
     * @param int $next The index of the next PPS
     * @param int $dir The index of it's first child if this is a Dir or Root PPS
     * @param int $time_1st A timestamp
     * @param int $time_2nd A timestamp
     * @param string $data The (usually binary) source data of the PPS
     * @param array $children Array containing children PPS for this PPS
     */
    public function __construct($No, $name, $type, $prev, $next, $dir, $time_1st, $time_2nd, $data, $children)
    {
        $this->No = $No;
        $this->Name = $name;
        $this->Type = $type;
        $this->PrevPps = $prev;
        $this->NextPps = $next;
        $this->DirPps = $dir;
        $this->Time1st = $time_1st;
        $this->Time2nd = $time_2nd;
        $this->_data = $data;
        $this->children = $children;
        if ($data != '') {
            $this->Size = strlen($data);
        } else {
            $this->Size = 0;
        }
    }

    /**
     * Returns the amount of data saved for this PPS.
     *
     * @return int The amount of data (in bytes)
     */
    public function getDataLen()
    {
        if (!isset($this->_data)) {
            return 0;
        }

        return strlen($this->_data);
    }

    /**
     * Returns a string with the PPS's WK (What is a WK?).
     *
     * @return string The binary string
     */
    public function _getPpsWk()
    {
        $ret = str_pad($this->Name, 64, "\x00");

        $ret .= pack('v', strlen($this->Name) + 2)  // 66
            . pack('c', $this->Type)              // 67
            . pack('c', 0x00) //UK                // 68
            . pack('V', $this->PrevPps) //Prev    // 72
            . pack('V', $this->NextPps) //Next    // 76
            . pack('V', $this->DirPps)  //Dir     // 80
            . "\x00\x09\x02\x00"                  // 84
            . "\x00\x00\x00\x00"                  // 88
            . "\xc0\x00\x00\x00"                  // 92
            . "\x00\x00\x00\x46"                  // 96 // Seems to be ok only for Root
            . "\x00\x00\x00\x00"                  // 100
            . OLE::localDateToOLE($this->Time1st)          // 108
            . OLE::localDateToOLE($this->Time2nd)          // 116
            . pack('V', isset($this->startBlock) ? $this->startBlock : 0)  // 120
            . pack('V', $this->Size)               // 124
            . pack('V', 0); // 128
        return $ret;
    }

    /**
     * Updates index and pointers to previous, next and children PPS's for this
     * PPS. I don't think it'll work with Dir PPS's.
     *
     * @param array &$raList Reference to the array of PPS's for the whole OLE
     *                          container
     * @param mixed $to_save
     * @param mixed $depth
     *
     * @return int The index for this PPS
     */
    public static function _savePpsSetPnt(&$raList, $to_save, $depth = 0)
    {
        if (!is_array($to_save) || (empty($to_save))) {
            return 0xFFFFFFFF;
        } elseif (count($to_save) == 1) {
            $cnt = count($raList);
            // If the first entry, it's the root... Don't clone it!
            $raList[$cnt] = ($depth == 0) ? $to_save[0] : clone $to_save[0];
            $raList[$cnt]->No = $cnt;
            $raList[$cnt]->PrevPps = 0xFFFFFFFF;
            $raList[$cnt]->NextPps = 0xFFFFFFFF;
            $raList[$cnt]->DirPps = self::_savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++);
        } else {
            $iPos = floor(count($to_save) / 2);
            $aPrev = array_slice($to_save, 0, $iPos);
            $aNext = array_slice($to_save, $iPos + 1);
            $cnt = count($raList);
            // If the first entry, it's the root... Don't clone it!
            $raList[$cnt] = ($depth == 0) ? $to_save[$iPos] : clone $to_save[$iPos];
            $raList[$cnt]->No = $cnt;
            $raList[$cnt]->PrevPps = self::_savePpsSetPnt($raList, $aPrev, $depth++);
            $raList[$cnt]->NextPps = self::_savePpsSetPnt($raList, $aNext, $depth++);
            $raList[$cnt]->DirPps = self::_savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++);
        }

        return $cnt;
    }
}