Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 400 and 402] [Versions 401 and 402]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;
   4  
   5  use Exception;
   6  use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
   7  use PhpOffice\PhpSpreadsheet\Calculation\Functions;
   8  use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
   9  use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
  10  use PhpOffice\PhpSpreadsheet\Cell\Cell;
  11  use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
  12  use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
  13  
  14  class Indirect
  15  {
  16      /**
  17       * Determine whether cell address is in A1 (true) or R1C1 (false) format.
  18       *
  19       * @param mixed $a1fmt Expect bool Helpers::CELLADDRESS_USE_A1 or CELLADDRESS_USE_R1C1,
  20       *                      but can be provided as numeric which is cast to bool
  21       */
  22      private static function a1Format($a1fmt): bool
  23      {
  24          $a1fmt = Functions::flattenSingleValue($a1fmt);
  25          if ($a1fmt === null) {
  26              return Helpers::CELLADDRESS_USE_A1;
  27          }
  28          if (is_string($a1fmt)) {
  29              throw new Exception(ExcelError::VALUE());
  30          }
  31  
  32          return (bool) $a1fmt;
  33      }
  34  
  35      /**
  36       * Convert cellAddress to string, verify not null string.
  37       *
  38       * @param array|string $cellAddress
  39       */
  40      private static function validateAddress($cellAddress): string
  41      {
  42          $cellAddress = Functions::flattenSingleValue($cellAddress);
  43          if (!is_string($cellAddress) || !$cellAddress) {
  44              throw new Exception(ExcelError::REF());
  45          }
  46  
  47          return $cellAddress;
  48      }
  49  
  50      /**
  51       * INDIRECT.
  52       *
  53       * Returns the reference specified by a text string.
  54       * References are immediately evaluated to display their contents.
  55       *
  56       * Excel Function:
  57       *        =INDIRECT(cellAddress, bool) where the bool argument is optional
  58       *
  59       * @param array|string $cellAddress $cellAddress The cell address of the current cell (containing this formula)
  60       * @param mixed $a1fmt Expect bool Helpers::CELLADDRESS_USE_A1 or CELLADDRESS_USE_R1C1,
  61       *                      but can be provided as numeric which is cast to bool
  62       * @param Cell $cell The current cell (containing this formula)
  63       *
  64       * @return array|string An array containing a cell or range of cells, or a string on error
  65       */
  66      public static function INDIRECT($cellAddress, $a1fmt, Cell $cell)
  67      {
  68          [$baseCol, $baseRow] = Coordinate::indexesFromString($cell->getCoordinate());
  69  
  70          try {
  71              $a1 = self::a1Format($a1fmt);
  72              $cellAddress = self::validateAddress($cellAddress);
  73          } catch (Exception $e) {
  74              return $e->getMessage();
  75          }
  76  
  77          [$cellAddress, $worksheet, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell);
  78  
  79          if (preg_match('/^' . Calculation::CALCULATION_REGEXP_COLUMNRANGE_RELATIVE . '$/miu', $cellAddress, $matches)) {
  80              $cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress));
  81          } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_ROWRANGE_RELATIVE . '$/miu', $cellAddress, $matches)) {
  82              $cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress));
  83          }
  84  
  85          try {
  86              [$cellAddress1, $cellAddress2, $cellAddress] = Helpers::extractCellAddresses($cellAddress, $a1, $cell->getWorkSheet(), $sheetName, $baseRow, $baseCol);
  87          } catch (Exception $e) {
  88              return ExcelError::REF();
  89          }
  90  
  91          if (
  92              (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress1, $matches)) ||
  93              (($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress2, $matches)))
  94          ) {
  95              return ExcelError::REF();
  96          }
  97  
  98          return self::extractRequiredCells($worksheet, $cellAddress);
  99      }
 100  
 101      /**
 102       * Extract range values.
 103       *
 104       * @return mixed Array of values in range if range contains more than one element.
 105       *                  Otherwise, a single value is returned.
 106       */
 107      private static function extractRequiredCells(?Worksheet $worksheet, string $cellAddress)
 108      {
 109          return Calculation::getInstance($worksheet !== null ? $worksheet->getParent() : null)
 110              ->extractCellRange($cellAddress, $worksheet, false);
 111      }
 112  
 113      private static function handleRowColumnRanges(?Worksheet $worksheet, string $start, string $end): string
 114      {
 115          // Being lazy, we're only checking a single row/column to get the max
 116          if (ctype_digit($start) && $start <= 1048576) {
 117              // Max 16,384 columns for Excel2007
 118              $endColRef = ($worksheet !== null) ? $worksheet->getHighestDataColumn((int) $start) : AddressRange::MAX_COLUMN;
 119  
 120              return "A{$start}:{$endColRef}{$end}";
 121          } elseif (ctype_alpha($start) && strlen($start) <= 3) {
 122              // Max 1,048,576 rows for Excel2007
 123              $endRowRef = ($worksheet !== null) ? $worksheet->getHighestDataRow($start) : AddressRange::MAX_ROW;
 124  
 125              return "{$start}1:{$end}{$endRowRef}";
 126          }
 127  
 128          return "{$start}:{$end}";
 129      }
 130  }