/home/kueuepay/public_html/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php
<?php

namespace PhpOffice\PhpSpreadsheet;

use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Style\Conditional;
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;

class ReferenceHelper
{
    /**    Constants                */
    /**    Regular Expressions      */
    const REFHELPER_REGEXP_CELLREF = '((\w*|\'[^!]*\')!)?(?<![:a-z\$])(\$?[a-z]{1,3}\$?\d+)(?=[^:!\d\'])';
    const REFHELPER_REGEXP_CELLRANGE = '((\w*|\'[^!]*\')!)?(\$?[a-z]{1,3}\$?\d+):(\$?[a-z]{1,3}\$?\d+)';
    const REFHELPER_REGEXP_ROWRANGE = '((\w*|\'[^!]*\')!)?(\$?\d+):(\$?\d+)';
    const REFHELPER_REGEXP_COLRANGE = '((\w*|\'[^!]*\')!)?(\$?[a-z]{1,3}):(\$?[a-z]{1,3})';

    /**
     * Instance of this class.
     *
     * @var ?ReferenceHelper
     */
    private static $instance;

    /**
     * @var CellReferenceHelper
     */
    private $cellReferenceHelper;

    /**
     * Get an instance of this class.
     *
     * @return ReferenceHelper
     */
    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    /**
     * Create a new ReferenceHelper.
     */
    protected function __construct()
    {
    }

    /**
     * Compare two column addresses
     * Intended for use as a Callback function for sorting column addresses by column.
     *
     * @param string $a First column to test (e.g. 'AA')
     * @param string $b Second column to test (e.g. 'Z')
     *
     * @return int
     */
    public static function columnSort($a, $b)
    {
        return strcasecmp(strlen($a) . $a, strlen($b) . $b);
    }

    /**
     * Compare two column addresses
     * Intended for use as a Callback function for reverse sorting column addresses by column.
     *
     * @param string $a First column to test (e.g. 'AA')
     * @param string $b Second column to test (e.g. 'Z')
     *
     * @return int
     */
    public static function columnReverseSort(string $a, string $b)
    {
        return -strcasecmp(strlen($a) . $a, strlen($b) . $b);
    }

    /**
     * Compare two cell addresses
     * Intended for use as a Callback function for sorting cell addresses by column and row.
     *
     * @param string $a First cell to test (e.g. 'AA1')
     * @param string $b Second cell to test (e.g. 'Z1')
     *
     * @return int
     */
    public static function cellSort(string $a, string $b)
    {
        /** @scrutinizer be-damned */
        sscanf($a, '%[A-Z]%d', $ac, $ar);
        /** @var int $ar */
        /** @var string $ac */
        /** @scrutinizer be-damned */
        sscanf($b, '%[A-Z]%d', $bc, $br);
        /** @var int $br */
        /** @var string $bc */
        if ($ar === $br) {
            return strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
        }

        return ($ar < $br) ? -1 : 1;
    }

    /**
     * Compare two cell addresses
     * Intended for use as a Callback function for sorting cell addresses by column and row.
     *
     * @param string $a First cell to test (e.g. 'AA1')
     * @param string $b Second cell to test (e.g. 'Z1')
     *
     * @return int
     */
    public static function cellReverseSort(string $a, string $b)
    {
        /** @scrutinizer be-damned */
        sscanf($a, '%[A-Z]%d', $ac, $ar);
        /** @var int $ar */
        /** @var string $ac */
        /** @scrutinizer be-damned */
        sscanf($b, '%[A-Z]%d', $bc, $br);
        /** @var int $br */
        /** @var string $bc */
        if ($ar === $br) {
            return -strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
        }

        return ($ar < $br) ? 1 : -1;
    }

    /**
     * Update page breaks when inserting/deleting rows/columns.
     *
     * @param Worksheet $worksheet The worksheet that we're editing
     * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
     * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
     */
    protected function adjustPageBreaks(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
    {
        $aBreaks = $worksheet->getBreaks();
        ($numberOfColumns > 0 || $numberOfRows > 0)
            ? uksort($aBreaks, [self::class, 'cellReverseSort'])
            : uksort($aBreaks, [self::class, 'cellSort']);

        foreach ($aBreaks as $cellAddress => $value) {
            if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === true) {
                //    If we're deleting, then clear any defined breaks that are within the range
                //        of rows/columns that we're deleting
                $worksheet->setBreak($cellAddress, Worksheet::BREAK_NONE);
            } else {
                //    Otherwise update any affected breaks by inserting a new break at the appropriate point
                //        and removing the old affected break
                $newReference = $this->updateCellReference($cellAddress);
                if ($cellAddress !== $newReference) {
                    $worksheet->setBreak($newReference, $value)
                        ->setBreak($cellAddress, Worksheet::BREAK_NONE);
                }
            }
        }
    }

    /**
     * Update cell comments when inserting/deleting rows/columns.
     *
     * @param Worksheet $worksheet The worksheet that we're editing
     */
    protected function adjustComments(Worksheet $worksheet): void
    {
        $aComments = $worksheet->getComments();
        $aNewComments = []; // the new array of all comments

        foreach ($aComments as $cellAddress => &$value) {
            // Any comments inside a deleted range will be ignored
            if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === false) {
                // Otherwise build a new array of comments indexed by the adjusted cell reference
                $newReference = $this->updateCellReference($cellAddress);
                $aNewComments[$newReference] = $value;
            }
        }
        //    Replace the comments array with the new set of comments
        $worksheet->setComments($aNewComments);
    }

    /**
     * Update hyperlinks when inserting/deleting rows/columns.
     *
     * @param Worksheet $worksheet The worksheet that we're editing
     * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
     * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
     */
    protected function adjustHyperlinks(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
    {
        $aHyperlinkCollection = $worksheet->getHyperlinkCollection();
        ($numberOfColumns > 0 || $numberOfRows > 0)
            ? uksort($aHyperlinkCollection, [self::class, 'cellReverseSort'])
            : uksort($aHyperlinkCollection, [self::class, 'cellSort']);

        foreach ($aHyperlinkCollection as $cellAddress => $value) {
            $newReference = $this->updateCellReference($cellAddress);
            if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === true) {
                $worksheet->setHyperlink($cellAddress, null);
            } elseif ($cellAddress !== $newReference) {
                $worksheet->setHyperlink($newReference, $value);
                $worksheet->setHyperlink($cellAddress, null);
            }
        }
    }

    /**
     * Update conditional formatting styles when inserting/deleting rows/columns.
     *
     * @param Worksheet $worksheet The worksheet that we're editing
     * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
     * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
     */
    protected function adjustConditionalFormatting(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
    {
        $aStyles = $worksheet->getConditionalStylesCollection();
        ($numberOfColumns > 0 || $numberOfRows > 0)
            ? uksort($aStyles, [self::class, 'cellReverseSort'])
            : uksort($aStyles, [self::class, 'cellSort']);

        foreach ($aStyles as $cellAddress => $cfRules) {
            $worksheet->removeConditionalStyles($cellAddress);
            $newReference = $this->updateCellReference($cellAddress);

            foreach ($cfRules as &$cfRule) {
                /** @var Conditional $cfRule */
                $conditions = $cfRule->getConditions();
                foreach ($conditions as &$condition) {
                    if (is_string($condition)) {
                        $condition = $this->updateFormulaReferences(
                            $condition,
                            $this->cellReferenceHelper->beforeCellAddress(),
                            $numberOfColumns,
                            $numberOfRows,
                            $worksheet->getTitle(),
                            true
                        );
                    }
                }
                $cfRule->setConditions($conditions);
            }
            $worksheet->setConditionalStyles($newReference, $cfRules);
        }
    }

    /**
     * Update data validations when inserting/deleting rows/columns.
     *
     * @param Worksheet $worksheet The worksheet that we're editing
     * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
     * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
     */
    protected function adjustDataValidations(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
    {
        $aDataValidationCollection = $worksheet->getDataValidationCollection();
        ($numberOfColumns > 0 || $numberOfRows > 0)
            ? uksort($aDataValidationCollection, [self::class, 'cellReverseSort'])
            : uksort($aDataValidationCollection, [self::class, 'cellSort']);

        foreach ($aDataValidationCollection as $cellAddress => $dataValidation) {
            $newReference = $this->updateCellReference($cellAddress);
            if ($cellAddress !== $newReference) {
                $dataValidation->setSqref($newReference);
                $worksheet->setDataValidation($newReference, $dataValidation);
                $worksheet->setDataValidation($cellAddress, null);
            }
        }
    }

    /**
     * Update merged cells when inserting/deleting rows/columns.
     *
     * @param Worksheet $worksheet The worksheet that we're editing
     */
    protected function adjustMergeCells(Worksheet $worksheet): void
    {
        $aMergeCells = $worksheet->getMergeCells();
        $aNewMergeCells = []; // the new array of all merge cells
        foreach ($aMergeCells as $cellAddress => &$value) {
            $newReference = $this->updateCellReference($cellAddress);
            $aNewMergeCells[$newReference] = $newReference;
        }
        $worksheet->setMergeCells($aNewMergeCells); // replace the merge cells array
    }

    /**
     * Update protected cells when inserting/deleting rows/columns.
     *
     * @param Worksheet $worksheet The worksheet that we're editing
     * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
     * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
     */
    protected function adjustProtectedCells(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
    {
        $aProtectedCells = $worksheet->getProtectedCells();
        ($numberOfColumns > 0 || $numberOfRows > 0)
            ? uksort($aProtectedCells, [self::class, 'cellReverseSort'])
            : uksort($aProtectedCells, [self::class, 'cellSort']);
        foreach ($aProtectedCells as $cellAddress => $value) {
            $newReference = $this->updateCellReference($cellAddress);
            if ($cellAddress !== $newReference) {
                $worksheet->protectCells($newReference, $value, true);
                $worksheet->unprotectCells($cellAddress);
            }
        }
    }

    /**
     * Update column dimensions when inserting/deleting rows/columns.
     *
     * @param Worksheet $worksheet The worksheet that we're editing
     */
    protected function adjustColumnDimensions(Worksheet $worksheet): void
    {
        $aColumnDimensions = array_reverse($worksheet->getColumnDimensions(), true);
        if (!empty($aColumnDimensions)) {
            foreach ($aColumnDimensions as $objColumnDimension) {
                $newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1');
                [$newReference] = Coordinate::coordinateFromString($newReference);
                if ($objColumnDimension->getColumnIndex() !== $newReference) {
                    $objColumnDimension->setColumnIndex($newReference);
                }
            }

            $worksheet->refreshColumnDimensions();
        }
    }

    /**
     * Update row dimensions when inserting/deleting rows/columns.
     *
     * @param Worksheet $worksheet The worksheet that we're editing
     * @param int $beforeRow Number of the row we're inserting/deleting before
     * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
     */
    protected function adjustRowDimensions(Worksheet $worksheet, $beforeRow, $numberOfRows): void
    {
        $aRowDimensions = array_reverse($worksheet->getRowDimensions(), true);
        if (!empty($aRowDimensions)) {
            foreach ($aRowDimensions as $objRowDimension) {
                $newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex());
                [, $newReference] = Coordinate::coordinateFromString($newReference);
                $newRoweference = (int) $newReference;
                if ($objRowDimension->getRowIndex() !== $newRoweference) {
                    $objRowDimension->setRowIndex($newRoweference);
                }
            }

            $worksheet->refreshRowDimensions();

            $copyDimension = $worksheet->getRowDimension($beforeRow - 1);
            for ($i = $beforeRow; $i <= $beforeRow - 1 + $numberOfRows; ++$i) {
                $newDimension = $worksheet->getRowDimension($i);
                $newDimension->setRowHeight($copyDimension->getRowHeight());
                $newDimension->setVisible($copyDimension->getVisible());
                $newDimension->setOutlineLevel($copyDimension->getOutlineLevel());
                $newDimension->setCollapsed($copyDimension->getCollapsed());
            }
        }
    }

    /**
     * Insert a new column or row, updating all possible related data.
     *
     * @param string $beforeCellAddress Insert before this cell address (e.g. 'A1')
     * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
     * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
     * @param Worksheet $worksheet The worksheet that we're editing
     */
    public function insertNewBefore(
        string $beforeCellAddress,
        int $numberOfColumns,
        int $numberOfRows,
        Worksheet $worksheet
    ): void {
        $remove = ($numberOfColumns < 0 || $numberOfRows < 0);

        if (
            $this->cellReferenceHelper === null ||
            $this->cellReferenceHelper->refreshRequired($beforeCellAddress, $numberOfColumns, $numberOfRows)
        ) {
            $this->cellReferenceHelper = new CellReferenceHelper($beforeCellAddress, $numberOfColumns, $numberOfRows);
        }

        // Get coordinate of $beforeCellAddress
        [$beforeColumn, $beforeRow] = Coordinate::indexesFromString($beforeCellAddress);

        // Clear cells if we are removing columns or rows
        $highestColumn = $worksheet->getHighestColumn();
        $highestRow = $worksheet->getHighestRow();

        // 1. Clear column strips if we are removing columns
        if ($numberOfColumns < 0 && $beforeColumn - 2 + $numberOfColumns > 0) {
            $this->clearColumnStrips($highestRow, $beforeColumn, $numberOfColumns, $worksheet);
        }

        // 2. Clear row strips if we are removing rows
        if ($numberOfRows < 0 && $beforeRow - 1 + $numberOfRows > 0) {
            $this->clearRowStrips($highestColumn, $beforeColumn, $beforeRow, $numberOfRows, $worksheet);
        }

        // Find missing coordinates. This is important when inserting column before the last column
        $cellCollection = $worksheet->getCellCollection();
        $missingCoordinates = array_filter(
            array_map(function ($row) use ($highestColumn) {
                return "{$highestColumn}{$row}";
            }, range(1, $highestRow)),
            function ($coordinate) use ($cellCollection) {
                return $cellCollection->has($coordinate) === false;
            }
        );

        // Create missing cells with null values
        if (!empty($missingCoordinates)) {
            foreach ($missingCoordinates as $coordinate) {
                $worksheet->createNewCell($coordinate);
            }
        }

        $allCoordinates = $worksheet->getCoordinates();
        if ($remove) {
            // It's faster to reverse and pop than to use unshift, especially with large cell collections
            $allCoordinates = array_reverse($allCoordinates);
        }

        // Loop through cells, bottom-up, and change cell coordinate
        while ($coordinate = array_pop($allCoordinates)) {
            $cell = $worksheet->getCell($coordinate);
            $cellIndex = Coordinate::columnIndexFromString($cell->getColumn());

            if ($cellIndex - 1 + $numberOfColumns < 0) {
                continue;
            }

            // New coordinate
            $newCoordinate = Coordinate::stringFromColumnIndex($cellIndex + $numberOfColumns) . ($cell->getRow() + $numberOfRows);

            // Should the cell be updated? Move value and cellXf index from one cell to another.
            if (($cellIndex >= $beforeColumn) && ($cell->getRow() >= $beforeRow)) {
                // Update cell styles
                $worksheet->getCell($newCoordinate)->setXfIndex($cell->getXfIndex());

                // Insert this cell at its new location
                if ($cell->getDataType() === DataType::TYPE_FORMULA) {
                    // Formula should be adjusted
                    $worksheet->getCell($newCoordinate)
                        ->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true));
                } else {
                    // Cell value should not be adjusted
                    $worksheet->getCell($newCoordinate)->setValueExplicit($cell->getValue(), $cell->getDataType());
                }

                // Clear the original cell
                $worksheet->getCellCollection()->delete($coordinate);
            } else {
                /*    We don't need to update styles for rows/columns before our insertion position,
                        but we do still need to adjust any formulae in those cells                    */
                if ($cell->getDataType() === DataType::TYPE_FORMULA) {
                    // Formula should be adjusted
                    $cell->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true));
                }
            }
        }

        // Duplicate styles for the newly inserted cells
        $highestColumn = $worksheet->getHighestColumn();
        $highestRow = $worksheet->getHighestRow();

        if ($numberOfColumns > 0 && $beforeColumn - 2 > 0) {
            $this->duplicateStylesByColumn($worksheet, $beforeColumn, $beforeRow, $highestRow, $numberOfColumns);
        }

        if ($numberOfRows > 0 && $beforeRow - 1 > 0) {
            $this->duplicateStylesByRow($worksheet, $beforeColumn, $beforeRow, $highestColumn, $numberOfRows);
        }

        // Update worksheet: column dimensions
        $this->adjustColumnDimensions($worksheet);

        // Update worksheet: row dimensions
        $this->adjustRowDimensions($worksheet, $beforeRow, $numberOfRows);

        //    Update worksheet: page breaks
        $this->adjustPageBreaks($worksheet, $numberOfColumns, $numberOfRows);

        //    Update worksheet: comments
        $this->adjustComments($worksheet);

        // Update worksheet: hyperlinks
        $this->adjustHyperlinks($worksheet, $numberOfColumns, $numberOfRows);

        // Update worksheet: conditional formatting styles
        $this->adjustConditionalFormatting($worksheet, $numberOfColumns, $numberOfRows);

        // Update worksheet: data validations
        $this->adjustDataValidations($worksheet, $numberOfColumns, $numberOfRows);

        // Update worksheet: merge cells
        $this->adjustMergeCells($worksheet);

        // Update worksheet: protected cells
        $this->adjustProtectedCells($worksheet, $numberOfColumns, $numberOfRows);

        // Update worksheet: autofilter
        $this->adjustAutoFilter($worksheet, $beforeCellAddress, $numberOfColumns);

        // Update worksheet: table
        $this->adjustTable($worksheet, $beforeCellAddress, $numberOfColumns);

        // Update worksheet: freeze pane
        if ($worksheet->getFreezePane()) {
            $splitCell = $worksheet->getFreezePane();
            $topLeftCell = $worksheet->getTopLeftCell() ?? '';

            $splitCell = $this->updateCellReference($splitCell);
            $topLeftCell = $this->updateCellReference($topLeftCell);

            $worksheet->freezePane($splitCell, $topLeftCell);
        }

        // Page setup
        if ($worksheet->getPageSetup()->isPrintAreaSet()) {
            $worksheet->getPageSetup()->setPrintArea(
                $this->updateCellReference($worksheet->getPageSetup()->getPrintArea())
            );
        }

        // Update worksheet: drawings
        $aDrawings = $worksheet->getDrawingCollection();
        foreach ($aDrawings as $objDrawing) {
            $newReference = $this->updateCellReference($objDrawing->getCoordinates());
            if ($objDrawing->getCoordinates() != $newReference) {
                $objDrawing->setCoordinates($newReference);
            }
            if ($objDrawing->getCoordinates2() !== '') {
                $newReference = $this->updateCellReference($objDrawing->getCoordinates2());
                if ($objDrawing->getCoordinates2() != $newReference) {
                    $objDrawing->setCoordinates2($newReference);
                }
            }
        }

        // Update workbook: define names
        if (count($worksheet->getParentOrThrow()->getDefinedNames()) > 0) {
            $this->updateDefinedNames($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
        }

        // Garbage collect
        $worksheet->garbageCollect();
    }

    /**
     * Update references within formulas.
     *
     * @param string $formula Formula to update
     * @param string $beforeCellAddress Insert before this one
     * @param int $numberOfColumns Number of columns to insert
     * @param int $numberOfRows Number of rows to insert
     * @param string $worksheetName Worksheet name/title
     *
     * @return string Updated formula
     */
    public function updateFormulaReferences(
        $formula = '',
        $beforeCellAddress = 'A1',
        $numberOfColumns = 0,
        $numberOfRows = 0,
        $worksheetName = '',
        bool $includeAbsoluteReferences = false
    ) {
        if (
            $this->cellReferenceHelper === null ||
            $this->cellReferenceHelper->refreshRequired($beforeCellAddress, $numberOfColumns, $numberOfRows)
        ) {
            $this->cellReferenceHelper = new CellReferenceHelper($beforeCellAddress, $numberOfColumns, $numberOfRows);
        }

        //    Update cell references in the formula
        $formulaBlocks = explode('"', $formula);
        $i = false;
        foreach ($formulaBlocks as &$formulaBlock) {
            //    Ignore blocks that were enclosed in quotes (alternating entries in the $formulaBlocks array after the explode)
            $i = $i === false;
            if ($i) {
                $adjustCount = 0;
                $newCellTokens = $cellTokens = [];
                //    Search for row ranges (e.g. 'Sheet1'!3:5 or 3:5) with or without $ absolutes (e.g. $3:5)
                $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
                if ($matchCount > 0) {
                    foreach ($matches as $match) {
                        $fromString = ($match[2] > '') ? $match[2] . '!' : '';
                        $fromString .= $match[3] . ':' . $match[4];
                        $modified3 = substr($this->updateCellReference('$A' . $match[3], $includeAbsoluteReferences), 2);
                        $modified4 = substr($this->updateCellReference('$A' . $match[4], $includeAbsoluteReferences), 2);

                        if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
                            if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
                                $toString = ($match[2] > '') ? $match[2] . '!' : '';
                                $toString .= $modified3 . ':' . $modified4;
                                //    Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
                                $column = 100000;
                                $row = 10000000 + (int) trim($match[3], '$');
                                $cellIndex = "{$column}{$row}";

                                $newCellTokens[$cellIndex] = preg_quote($toString, '/');
                                $cellTokens[$cellIndex] = '/(?<!\d\$\!)' . preg_quote($fromString, '/') . '(?!\d)/i';
                                ++$adjustCount;
                            }
                        }
                    }
                }
                //    Search for column ranges (e.g. 'Sheet1'!C:E or C:E) with or without $ absolutes (e.g. $C:E)
                $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_COLRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
                if ($matchCount > 0) {
                    foreach ($matches as $match) {
                        $fromString = ($match[2] > '') ? $match[2] . '!' : '';
                        $fromString .= $match[3] . ':' . $match[4];
                        $modified3 = substr($this->updateCellReference($match[3] . '$1', $includeAbsoluteReferences), 0, -2);
                        $modified4 = substr($this->updateCellReference($match[4] . '$1', $includeAbsoluteReferences), 0, -2);

                        if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) {
                            if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
                                $toString = ($match[2] > '') ? $match[2] . '!' : '';
                                $toString .= $modified3 . ':' . $modified4;
                                //    Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
                                $column = Coordinate::columnIndexFromString(trim($match[3], '$')) + 100000;
                                $row = 10000000;
                                $cellIndex = "{$column}{$row}";

                                $newCellTokens[$cellIndex] = preg_quote($toString, '/');
                                $cellTokens[$cellIndex] = '/(?<![A-Z\$\!])' . preg_quote($fromString, '/') . '(?![A-Z])/i';
                                ++$adjustCount;
                            }
                        }
                    }
                }
                //    Search for cell ranges (e.g. 'Sheet1'!A3:C5 or A3:C5) with or without $ absolutes (e.g. $A1:C$5)
                $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);
                if ($matchCount > 0) {
                    foreach ($matches as $match) {
                        $fromString = ($match[2] > '') ? $match[2] . '!' : '';
                        $fromString .= $match[3] . ':' . $match[4];
                        $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences);
                        $modified4 = $this->updateCellReference($match[4], $includeAbsoluteReferences);

                        if ($match[3] . $match[4] !== $modified3 . $modified4) {
                            if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
                                $toString = ($match[2] > '') ? $match[2] . '!' : '';
                                $toString .= $modified3 . ':' . $modified4;
                                [$column, $row] = Coordinate::coordinateFromString($match[3]);
                                //    Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
                                $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000;
                                $row = (int) trim($row, '$') + 10000000;
                                $cellIndex = "{$column}{$row}";

                                $newCellTokens[$cellIndex] = preg_quote($toString, '/');
                                $cellTokens[$cellIndex] = '/(?<![A-Z]\$\!)' . preg_quote($fromString, '/') . '(?!\d)/i';
                                ++$adjustCount;
                            }
                        }
                    }
                }
                //    Search for cell references (e.g. 'Sheet1'!A3 or C5) with or without $ absolutes (e.g. $A1 or C$5)
                $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLREF . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER);

                if ($matchCount > 0) {
                    foreach ($matches as $match) {
                        $fromString = ($match[2] > '') ? $match[2] . '!' : '';
                        $fromString .= $match[3];

                        $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences);
                        if ($match[3] !== $modified3) {
                            if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) {
                                $toString = ($match[2] > '') ? $match[2] . '!' : '';
                                $toString .= $modified3;
                                [$column, $row] = Coordinate::coordinateFromString($match[3]);
                                $columnAdditionalIndex = $column[0] === '$' ? 1 : 0;
                                $rowAdditionalIndex = $row[0] === '$' ? 1 : 0;
                                //    Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
                                $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000;
                                $row = (int) trim($row, '$') + 10000000;
                                $cellIndex = $row . $rowAdditionalIndex . $column . $columnAdditionalIndex;

                                $newCellTokens[$cellIndex] = preg_quote($toString, '/');
                                $cellTokens[$cellIndex] = '/(?<![A-Z\$\!])' . preg_quote($fromString, '/') . '(?!\d)/i';
                                ++$adjustCount;
                            }
                        }
                    }
                }
                if ($adjustCount > 0) {
                    if ($numberOfColumns > 0 || $numberOfRows > 0) {
                        krsort($cellTokens);
                        krsort($newCellTokens);
                    } else {
                        ksort($cellTokens);
                        ksort($newCellTokens);
                    }   //  Update cell references in the formula
                    $formulaBlock = str_replace('\\', '', (string) preg_replace($cellTokens, $newCellTokens, $formulaBlock));
                }
            }
        }
        unset($formulaBlock);

        //    Then rebuild the formula string
        return implode('"', $formulaBlocks);
    }

    /**
     * Update all cell references within a formula, irrespective of worksheet.
     */
    public function updateFormulaReferencesAnyWorksheet(string $formula = '', int $numberOfColumns = 0, int $numberOfRows = 0): string
    {
        $formula = $this->updateCellReferencesAllWorksheets($formula, $numberOfColumns, $numberOfRows);

        if ($numberOfColumns !== 0) {
            $formula = $this->updateColumnRangesAllWorksheets($formula, $numberOfColumns);
        }

        if ($numberOfRows !== 0) {
            $formula = $this->updateRowRangesAllWorksheets($formula, $numberOfRows);
        }

        return $formula;
    }

    private function updateCellReferencesAllWorksheets(string $formula, int $numberOfColumns, int $numberOfRows): string
    {
        $splitCount = preg_match_all(
            '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui',
            $formula,
            $splitRanges,
            PREG_OFFSET_CAPTURE
        );

        $columnLengths = array_map('strlen', array_column($splitRanges[6], 0));
        $rowLengths = array_map('strlen', array_column($splitRanges[7], 0));
        $columnOffsets = array_column($splitRanges[6], 1);
        $rowOffsets = array_column($splitRanges[7], 1);

        $columns = $splitRanges[6];
        $rows = $splitRanges[7];

        while ($splitCount > 0) {
            --$splitCount;
            $columnLength = $columnLengths[$splitCount];
            $rowLength = $rowLengths[$splitCount];
            $columnOffset = $columnOffsets[$splitCount];
            $rowOffset = $rowOffsets[$splitCount];
            $column = $columns[$splitCount][0];
            $row = $rows[$splitCount][0];

            if (!empty($column) && $column[0] !== '$') {
                $column = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($column) + $numberOfColumns);
                $formula = substr($formula, 0, $columnOffset) . $column . substr($formula, $columnOffset + $columnLength);
            }
            if (!empty($row) && $row[0] !== '$') {
                $row = (int) $row + $numberOfRows;
                $formula = substr($formula, 0, $rowOffset) . $row . substr($formula, $rowOffset + $rowLength);
            }
        }

        return $formula;
    }

    private function updateColumnRangesAllWorksheets(string $formula, int $numberOfColumns): string
    {
        $splitCount = preg_match_all(
            '/' . Calculation::CALCULATION_REGEXP_COLUMNRANGE_RELATIVE . '/mui',
            $formula,
            $splitRanges,
            PREG_OFFSET_CAPTURE
        );

        $fromColumnLengths = array_map('strlen', array_column($splitRanges[1], 0));
        $fromColumnOffsets = array_column($splitRanges[1], 1);
        $toColumnLengths = array_map('strlen', array_column($splitRanges[2], 0));
        $toColumnOffsets = array_column($splitRanges[2], 1);

        $fromColumns = $splitRanges[1];
        $toColumns = $splitRanges[2];

        while ($splitCount > 0) {
            --$splitCount;
            $fromColumnLength = $fromColumnLengths[$splitCount];
            $toColumnLength = $toColumnLengths[$splitCount];
            $fromColumnOffset = $fromColumnOffsets[$splitCount];
            $toColumnOffset = $toColumnOffsets[$splitCount];
            $fromColumn = $fromColumns[$splitCount][0];
            $toColumn = $toColumns[$splitCount][0];

            if (!empty($fromColumn) && $fromColumn[0] !== '$') {
                $fromColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($fromColumn) + $numberOfColumns);
                $formula = substr($formula, 0, $fromColumnOffset) . $fromColumn . substr($formula, $fromColumnOffset + $fromColumnLength);
            }
            if (!empty($toColumn) && $toColumn[0] !== '$') {
                $toColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($toColumn) + $numberOfColumns);
                $formula = substr($formula, 0, $toColumnOffset) . $toColumn . substr($formula, $toColumnOffset + $toColumnLength);
            }
        }

        return $formula;
    }

    private function updateRowRangesAllWorksheets(string $formula, int $numberOfRows): string
    {
        $splitCount = preg_match_all(
            '/' . Calculation::CALCULATION_REGEXP_ROWRANGE_RELATIVE . '/mui',
            $formula,
            $splitRanges,
            PREG_OFFSET_CAPTURE
        );

        $fromRowLengths = array_map('strlen', array_column($splitRanges[1], 0));
        $fromRowOffsets = array_column($splitRanges[1], 1);
        $toRowLengths = array_map('strlen', array_column($splitRanges[2], 0));
        $toRowOffsets = array_column($splitRanges[2], 1);

        $fromRows = $splitRanges[1];
        $toRows = $splitRanges[2];

        while ($splitCount > 0) {
            --$splitCount;
            $fromRowLength = $fromRowLengths[$splitCount];
            $toRowLength = $toRowLengths[$splitCount];
            $fromRowOffset = $fromRowOffsets[$splitCount];
            $toRowOffset = $toRowOffsets[$splitCount];
            $fromRow = $fromRows[$splitCount][0];
            $toRow = $toRows[$splitCount][0];

            if (!empty($fromRow) && $fromRow[0] !== '$') {
                $fromRow = (int) $fromRow + $numberOfRows;
                $formula = substr($formula, 0, $fromRowOffset) . $fromRow . substr($formula, $fromRowOffset + $fromRowLength);
            }
            if (!empty($toRow) && $toRow[0] !== '$') {
                $toRow = (int) $toRow + $numberOfRows;
                $formula = substr($formula, 0, $toRowOffset) . $toRow . substr($formula, $toRowOffset + $toRowLength);
            }
        }

        return $formula;
    }

    /**
     * Update cell reference.
     *
     * @param string $cellReference Cell address or range of addresses
     *
     * @return string Updated cell range
     */
    private function updateCellReference($cellReference = 'A1', bool $includeAbsoluteReferences = false)
    {
        // Is it in another worksheet? Will not have to update anything.
        if (strpos($cellReference, '!') !== false) {
            return $cellReference;
        }
        // Is it a range or a single cell?
        if (!Coordinate::coordinateIsRange($cellReference)) {
            // Single cell
            return $this->cellReferenceHelper->updateCellReference($cellReference, $includeAbsoluteReferences);
        }

        // Range
        return $this->updateCellRange($cellReference, $includeAbsoluteReferences);
    }

    /**
     * Update named formulae (i.e. containing worksheet references / named ranges).
     *
     * @param Spreadsheet $spreadsheet Object to update
     * @param string $oldName Old name (name to replace)
     * @param string $newName New name
     */
    public function updateNamedFormulae(Spreadsheet $spreadsheet, $oldName = '', $newName = ''): void
    {
        if ($oldName == '') {
            return;
        }

        foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
            foreach ($sheet->getCoordinates(false) as $coordinate) {
                $cell = $sheet->getCell($coordinate);
                if ($cell->getDataType() === DataType::TYPE_FORMULA) {
                    $formula = $cell->getValue();
                    if (strpos($formula, $oldName) !== false) {
                        $formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula);
                        $formula = str_replace($oldName . '!', $newName . '!', $formula);
                        $cell->setValueExplicit($formula, DataType::TYPE_FORMULA);
                    }
                }
            }
        }
    }

    private function updateDefinedNames(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
    {
        foreach ($worksheet->getParentOrThrow()->getDefinedNames() as $definedName) {
            if ($definedName->isFormula() === false) {
                $this->updateNamedRange($definedName, $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
            } else {
                $this->updateNamedFormula($definedName, $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
            }
        }
    }

    private function updateNamedRange(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
    {
        $cellAddress = $definedName->getValue();
        $asFormula = ($cellAddress[0] === '=');
        if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
            /**
             * If we delete the entire range that is referenced by a Named Range, MS Excel sets the value to #REF!
             * PhpSpreadsheet still only does a basic adjustment, so the Named Range will still reference Cells.
             * Note that this applies only when deleting columns/rows; subsequent insertion won't fix the #REF!
             * TODO Can we work out a method to identify Named Ranges that cease to be valid, so that we can replace
             *      them with a #REF!
             */
            if ($asFormula === true) {
                $formula = $this->updateFormulaReferences($cellAddress, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true);
                $definedName->setValue($formula);
            } else {
                $definedName->setValue($this->updateCellReference(ltrim($cellAddress, '='), true));
            }
        }
    }

    private function updateNamedFormula(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
    {
        if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
            /**
             * If we delete the entire range that is referenced by a Named Formula, MS Excel sets the value to #REF!
             * PhpSpreadsheet still only does a basic adjustment, so the Named Formula will still reference Cells.
             * Note that this applies only when deleting columns/rows; subsequent insertion won't fix the #REF!
             * TODO Can we work out a method to identify Named Ranges that cease to be valid, so that we can replace
             *      them with a #REF!
             */
            $formula = $definedName->getValue();
            $formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true);
            $definedName->setValue($formula);
        }
    }

    /**
     * Update cell range.
     *
     * @param string $cellRange Cell range    (e.g. 'B2:D4', 'B:C' or '2:3')
     *
     * @return string Updated cell range
     */
    private function updateCellRange(string $cellRange = 'A1:A1', bool $includeAbsoluteReferences = false): string
    {
        if (!Coordinate::coordinateIsRange($cellRange)) {
            throw new Exception('Only cell ranges may be passed to this method.');
        }

        // Update range
        $range = Coordinate::splitRange($cellRange);
        $ic = count($range);
        for ($i = 0; $i < $ic; ++$i) {
            $jc = count($range[$i]);
            for ($j = 0; $j < $jc; ++$j) {
                if (ctype_alpha($range[$i][$j])) {
                    $range[$i][$j] = Coordinate::coordinateFromString(
                        $this->cellReferenceHelper->updateCellReference($range[$i][$j] . '1', $includeAbsoluteReferences)
                    )[0];
                } elseif (ctype_digit($range[$i][$j])) {
                    $range[$i][$j] = Coordinate::coordinateFromString(
                        $this->cellReferenceHelper->updateCellReference('A' . $range[$i][$j], $includeAbsoluteReferences)
                    )[1];
                } else {
                    $range[$i][$j] = $this->cellReferenceHelper->updateCellReference($range[$i][$j], $includeAbsoluteReferences);
                }
            }
        }

        // Recreate range string
        return Coordinate::buildRange($range);
    }

    private function clearColumnStrips(int $highestRow, int $beforeColumn, int $numberOfColumns, Worksheet $worksheet): void
    {
        $startColumnId = Coordinate::stringFromColumnIndex($beforeColumn + $numberOfColumns);
        $endColumnId = Coordinate::stringFromColumnIndex($beforeColumn);

        for ($row = 1; $row <= $highestRow - 1; ++$row) {
            for ($column = $startColumnId; $column !== $endColumnId; ++$column) {
                $coordinate = $column . $row;
                $this->clearStripCell($worksheet, $coordinate);
            }
        }
    }

    private function clearRowStrips(string $highestColumn, int $beforeColumn, int $beforeRow, int $numberOfRows, Worksheet $worksheet): void
    {
        $startColumnId = Coordinate::stringFromColumnIndex($beforeColumn);
        ++$highestColumn;

        for ($column = $startColumnId; $column !== $highestColumn; ++$column) {
            for ($row = $beforeRow + $numberOfRows; $row <= $beforeRow - 1; ++$row) {
                $coordinate = $column . $row;
                $this->clearStripCell($worksheet, $coordinate);
            }
        }
    }

    private function clearStripCell(Worksheet $worksheet, string $coordinate): void
    {
        $worksheet->removeConditionalStyles($coordinate);
        $worksheet->setHyperlink($coordinate);
        $worksheet->setDataValidation($coordinate);
        $worksheet->removeComment($coordinate);

        if ($worksheet->cellExists($coordinate)) {
            $worksheet->getCell($coordinate)->setValueExplicit(null, DataType::TYPE_NULL);
            $worksheet->getCell($coordinate)->setXfIndex(0);
        }
    }

    private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void
    {
        $autoFilter = $worksheet->getAutoFilter();
        $autoFilterRange = $autoFilter->getRange();
        if (!empty($autoFilterRange)) {
            if ($numberOfColumns !== 0) {
                $autoFilterColumns = $autoFilter->getColumns();
                if (count($autoFilterColumns) > 0) {
                    $column = '';
                    $row = 0;
                    sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row);
                    $columnIndex = Coordinate::columnIndexFromString((string) $column);
                    [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange);
                    if ($columnIndex <= $rangeEnd[0]) {
                        if ($numberOfColumns < 0) {
                            $this->adjustAutoFilterDeleteRules($columnIndex, $numberOfColumns, $autoFilterColumns, $autoFilter);
                        }
                        $startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];

                        //    Shuffle columns in autofilter range
                        if ($numberOfColumns > 0) {
                            $this->adjustAutoFilterInsert($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter);
                        } else {
                            $this->adjustAutoFilterDelete($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter);
                        }
                    }
                }
            }

            $worksheet->setAutoFilter(
                $this->updateCellReference($autoFilterRange)
            );
        }
    }

    private function adjustAutoFilterDeleteRules(int $columnIndex, int $numberOfColumns, array $autoFilterColumns, AutoFilter $autoFilter): void
    {
        // If we're actually deleting any columns that fall within the autofilter range,
        //    then we delete any rules for those columns
        $deleteColumn = $columnIndex + $numberOfColumns - 1;
        $deleteCount = abs($numberOfColumns);

        for ($i = 1; $i <= $deleteCount; ++$i) {
            $columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1);
            if (isset($autoFilterColumns[$columnName])) {
                $autoFilter->clearColumn($columnName);
            }
            ++$deleteColumn;
        }
    }

    private function adjustAutoFilterInsert(int $startCol, int $numberOfColumns, int $rangeEnd, AutoFilter $autoFilter): void
    {
        $startColRef = $startCol;
        $endColRef = $rangeEnd;
        $toColRef = $rangeEnd + $numberOfColumns;

        do {
            $autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
            --$endColRef;
            --$toColRef;
        } while ($startColRef <= $endColRef);
    }

    private function adjustAutoFilterDelete(int $startCol, int $numberOfColumns, int $rangeEnd, AutoFilter $autoFilter): void
    {
        // For delete, we shuffle from beginning to end to avoid overwriting
        $startColID = Coordinate::stringFromColumnIndex($startCol);
        $toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
        $endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1);

        do {
            $autoFilter->shiftColumn($startColID, $toColID);
            ++$startColID;
            ++$toColID;
        } while ($startColID !== $endColID);
    }

    private function adjustTable(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void
    {
        $tableCollection = $worksheet->getTableCollection();

        foreach ($tableCollection as $table) {
            $tableRange = $table->getRange();
            if (!empty($tableRange)) {
                if ($numberOfColumns !== 0) {
                    $tableColumns = $table->getColumns();
                    if (count($tableColumns) > 0) {
                        $column = '';
                        $row = 0;
                        sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row);
                        $columnIndex = Coordinate::columnIndexFromString((string) $column);
                        [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($tableRange);
                        if ($columnIndex <= $rangeEnd[0]) {
                            if ($numberOfColumns < 0) {
                                $this->adjustTableDeleteRules($columnIndex, $numberOfColumns, $tableColumns, $table);
                            }
                            $startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];

                            //    Shuffle columns in table range
                            if ($numberOfColumns > 0) {
                                $this->adjustTableInsert($startCol, $numberOfColumns, $rangeEnd[0], $table);
                            } else {
                                $this->adjustTableDelete($startCol, $numberOfColumns, $rangeEnd[0], $table);
                            }
                        }
                    }
                }

                $table->setRange($this->updateCellReference($tableRange));
            }
        }
    }

    private function adjustTableDeleteRules(int $columnIndex, int $numberOfColumns, array $tableColumns, Table $table): void
    {
        // If we're actually deleting any columns that fall within the table range,
        //    then we delete any rules for those columns
        $deleteColumn = $columnIndex + $numberOfColumns - 1;
        $deleteCount = abs($numberOfColumns);

        for ($i = 1; $i <= $deleteCount; ++$i) {
            $columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1);
            if (isset($tableColumns[$columnName])) {
                $table->clearColumn($columnName);
            }
            ++$deleteColumn;
        }
    }

    private function adjustTableInsert(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void
    {
        $startColRef = $startCol;
        $endColRef = $rangeEnd;
        $toColRef = $rangeEnd + $numberOfColumns;

        do {
            $table->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
            --$endColRef;
            --$toColRef;
        } while ($startColRef <= $endColRef);
    }

    private function adjustTableDelete(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void
    {
        // For delete, we shuffle from beginning to end to avoid overwriting
        $startColID = Coordinate::stringFromColumnIndex($startCol);
        $toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
        $endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1);

        do {
            $table->shiftColumn($startColID, $toColID);
            ++$startColID;
            ++$toColID;
        } while ($startColID !== $endColID);
    }

    private function duplicateStylesByColumn(Worksheet $worksheet, int $beforeColumn, int $beforeRow, int $highestRow, int $numberOfColumns): void
    {
        $beforeColumnName = Coordinate::stringFromColumnIndex($beforeColumn - 1);
        for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) {
            // Style
            $coordinate = $beforeColumnName . $i;
            if ($worksheet->cellExists($coordinate)) {
                $xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
                for ($j = $beforeColumn; $j <= $beforeColumn - 1 + $numberOfColumns; ++$j) {
                    $worksheet->getCell([$j, $i])->setXfIndex($xfIndex);
                }
            }
        }
    }

    private function duplicateStylesByRow(Worksheet $worksheet, int $beforeColumn, int $beforeRow, string $highestColumn, int $numberOfRows): void
    {
        $highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
        for ($i = $beforeColumn; $i <= $highestColumnIndex; ++$i) {
            // Style
            $coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1);
            if ($worksheet->cellExists($coordinate)) {
                $xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
                for ($j = $beforeRow; $j <= $beforeRow - 1 + $numberOfRows; ++$j) {
                    $worksheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex);
                }
            }
        }
    }

    /**
     * __clone implementation. Cloning should not be allowed in a Singleton!
     */
    final public function __clone()
    {
        throw new Exception('Cloning a Singleton is not allowed!');
    }
}
Kueue Pay | Contactless Payment System
top

Quick Steps to NFC Pay

Getting started with NFC Pay is simple and quick. Register your account, add your cards, and you're ready to make payments in no time. Whether you're paying at a store, sending money to a friend, or managing your merchant transactions, NFC Pay makes it easy and secure.

1

Register Your Account

Download the NFC Pay app and sign up with your email or phone number. Complete the registration process by verifying your identity, and set up your secure PIN to protect your account.

2

Add Your Cards

Link your debit or credit cards to your NFC Pay wallet. Simply scan your card or enter the details manually, and you’re set to load funds, shop, and pay with ease.

3

Make Payment

To pay, simply tap your phone or scan the QR code at checkout. You can also transfer money to other users with a few taps. Enjoy fast, contactless payments with top-notch security.

Advanced Security Features Designed to Protect Your Information Effectively

NFC Pay prioritizes your security with advanced features that safeguard every transaction. From SMS or email verification to end-to-end encryption, we've implemented robust measures to ensure your data is always protected. Our security systems are designed to prevent unauthorized access and provide you with a safe and reliable payment experience.

img

SMS or Email Verification

Receive instant alerts for every transaction to keep track of your account activities.

img

KYC Solution

Verify your identity through our Know Your Customer process to prevent fraud and enhance security.

img

Two Factor Authentication

Dramatically supply transparent backward deliverables before caward comp internal or "organic" sources.

img

End-to-End Encryption

All your data and transactions are encrypted, ensuring that your sensitive information remains private.

img

Behavior Tracking

Monitor unusual activity patterns to detect and prevent suspicious behavior in real-time.

Top Reasons to Choose Us for Reliable and Expert Solutions

With NFC Pay, you get a trusted platform backed by proven expertise and a commitment to quality. We put our customers first, offering innovative solutions tailored to your needs, ensuring every transaction is secure, swift, and seamless.

1

Proven Expertise

Our team brings years of experience in the digital payments industry to provide reliable services.

2

Commitment to Quality

We prioritize excellence, ensuring that every aspect of our platform meets the highest standards.

3

Customer-Centric Approach

Your needs drive our solutions, and we are dedicated to delivering a superior user experience.

4

Innovative Solutions

We continuously evolve, integrating the latest technologies to enhance your payment experience.

Customer Feedback: Real Experiences from Satisfied Clients and Partners

Hear from our users who trust NFC Pay for their everyday transactions. Our commitment to security, ease of use, and exceptional service shines through in their experiences. See why our clients choose NFC Pay for their payment needs and how it has transformed the way they manage their finances.

"NFC Pay has made my transactions incredibly simple and secure. The intuitive interface and quick payment options are game-changers for my business"

"I love how NFC Pay prioritizes security without compromising on convenience. The two-factor authentication and instant alerts give me peace of mind every time I use it."

"Setting up my merchant account was a breeze, and now I can accept payments effortlessly. NFC Pay has truly streamlined my operations, saving me time and hassle."

Get the NFC Pay App for Seamless Transactions Anytime, Anywhere

Unlock the full potential of NFC Pay by downloading our app, designed to bring secure, swift, and smart transactions to your fingertips. Whether you're paying at a store, transferring money to friends, or managing your business payments, the NFC Pay app makes it effortless. Available on both iOS and Android, it's your all-in-one solution for convenient and reliable digital payments. Download now and experience the future of payments!

img