Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Worksheet;
   4  
   5  use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
   6  use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
   7  use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
   8  use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
   9  use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableStyle;
  10  
  11  class Table
  12  {
  13      /**
  14       * Table Name.
  15       *
  16       * @var string
  17       */
  18      private $name = '';
  19  
  20      /**
  21       * Show Header Row.
  22       *
  23       * @var bool
  24       */
  25      private $showHeaderRow = true;
  26  
  27      /**
  28       * Show Totals Row.
  29       *
  30       * @var bool
  31       */
  32      private $showTotalsRow = false;
  33  
  34      /**
  35       * Table Range.
  36       *
  37       * @var string
  38       */
  39      private $range = '';
  40  
  41      /**
  42       * Table Worksheet.
  43       *
  44       * @var null|Worksheet
  45       */
  46      private $workSheet;
  47  
  48      /**
  49       * Table Column.
  50       *
  51       * @var Table\Column[]
  52       */
  53      private $columns = [];
  54  
  55      /**
  56       * Table Style.
  57       *
  58       * @var TableStyle
  59       */
  60      private $style;
  61  
  62      /**
  63       * Create a new Table.
  64       *
  65       * @param AddressRange|array<int>|string $range
  66       *            A simple string containing a Cell range like 'A1:E10' is permitted
  67       *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
  68       *              or an AddressRange object.
  69       * @param string $name (e.g. Table1)
  70       */
  71      public function __construct($range = '', string $name = '')
  72      {
  73          $this->setRange($range);
  74          $this->setName($name);
  75          $this->style = new TableStyle();
  76      }
  77  
  78      /**
  79       * Get Table name.
  80       */
  81      public function getName(): string
  82      {
  83          return $this->name;
  84      }
  85  
  86      /**
  87       * Set Table name.
  88       */
  89      public function setName(string $name): self
  90      {
  91          $name = trim($name);
  92  
  93          if (!empty($name)) {
  94              if (strlen($name) === 1 && in_array($name, ['C', 'c', 'R', 'r'])) {
  95                  throw new PhpSpreadsheetException('The table name is invalid');
  96              }
  97              if (strlen($name) > 255) {
  98                  throw new PhpSpreadsheetException('The table name cannot be longer than 255 characters');
  99              }
 100              // Check for A1 or R1C1 cell reference notation
 101              if (
 102                  preg_match(Coordinate::A1_COORDINATE_REGEX, $name) ||
 103                  preg_match('/^R\[?\-?[0-9]*\]?C\[?\-?[0-9]*\]?$/i', $name)
 104              ) {
 105                  throw new PhpSpreadsheetException('The table name can\'t be the same as a cell reference');
 106              }
 107              if (!preg_match('/^[\p{L}_\\\\]/iu', $name)) {
 108                  throw new PhpSpreadsheetException('The table name must begin a name with a letter, an underscore character (_), or a backslash (\)');
 109              }
 110              if (!preg_match('/^[\p{L}_\\\\][\p{L}\p{M}0-9\._]+$/iu', $name)) {
 111                  throw new PhpSpreadsheetException('The table name contains invalid characters');
 112              }
 113          }
 114  
 115          $this->name = $name;
 116  
 117          return $this;
 118      }
 119  
 120      /**
 121       * Get show Header Row.
 122       */
 123      public function getShowHeaderRow(): bool
 124      {
 125          return $this->showHeaderRow;
 126      }
 127  
 128      /**
 129       * Set show Header Row.
 130       */
 131      public function setShowHeaderRow(bool $showHeaderRow): self
 132      {
 133          $this->showHeaderRow = $showHeaderRow;
 134  
 135          return $this;
 136      }
 137  
 138      /**
 139       * Get show Totals Row.
 140       */
 141      public function getShowTotalsRow(): bool
 142      {
 143          return $this->showTotalsRow;
 144      }
 145  
 146      /**
 147       * Set show Totals Row.
 148       */
 149      public function setShowTotalsRow(bool $showTotalsRow): self
 150      {
 151          $this->showTotalsRow = $showTotalsRow;
 152  
 153          return $this;
 154      }
 155  
 156      /**
 157       * Get Table Range.
 158       */
 159      public function getRange(): string
 160      {
 161          return $this->range;
 162      }
 163  
 164      /**
 165       * Set Table Cell Range.
 166       *
 167       * @param AddressRange|array<int>|string $range
 168       *            A simple string containing a Cell range like 'A1:E10' is permitted
 169       *              or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
 170       *              or an AddressRange object.
 171       */
 172      public function setRange($range = ''): self
 173      {
 174          // extract coordinate
 175          if ($range !== '') {
 176              [, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true);
 177          }
 178          if (empty($range)) {
 179              //    Discard all column rules
 180              $this->columns = [];
 181              $this->range = '';
 182  
 183              return $this;
 184          }
 185  
 186          if (strpos($range, ':') === false) {
 187              throw new PhpSpreadsheetException('Table must be set on a range of cells.');
 188          }
 189  
 190          [$width, $height] = Coordinate::rangeDimension($range);
 191          if ($width < 1 || $height < 2) {
 192              throw new PhpSpreadsheetException('The table range must be at least 1 column and 2 rows');
 193          }
 194  
 195          $this->range = $range;
 196          //    Discard any column ruless that are no longer valid within this range
 197          [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
 198          foreach ($this->columns as $key => $value) {
 199              $colIndex = Coordinate::columnIndexFromString($key);
 200              if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) {
 201                  unset($this->columns[$key]);
 202              }
 203          }
 204  
 205          return $this;
 206      }
 207  
 208      /**
 209       * Set Table Cell Range to max row.
 210       */
 211      public function setRangeToMaxRow(): self
 212      {
 213          if ($this->workSheet !== null) {
 214              $thisrange = $this->range;
 215              $range = (string) preg_replace('/\\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange);
 216              if ($range !== $thisrange) {
 217                  $this->setRange($range);
 218              }
 219          }
 220  
 221          return $this;
 222      }
 223  
 224      /**
 225       * Get Table's Worksheet.
 226       */
 227      public function getWorksheet(): ?Worksheet
 228      {
 229          return $this->workSheet;
 230      }
 231  
 232      /**
 233       * Set Table's Worksheet.
 234       */
 235      public function setWorksheet(?Worksheet $worksheet = null): self
 236      {
 237          if ($this->name !== '' && $worksheet !== null) {
 238              $spreadsheet = $worksheet->getParent();
 239              $tableName = StringHelper::strToUpper($this->name);
 240  
 241              foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
 242                  foreach ($sheet->getTableCollection() as $table) {
 243                      if (StringHelper::strToUpper($table->getName()) === $tableName) {
 244                          throw new PhpSpreadsheetException("Workbook already contains a table named '{$this->name}'");
 245                      }
 246                  }
 247              }
 248          }
 249  
 250          $this->workSheet = $worksheet;
 251  
 252          return $this;
 253      }
 254  
 255      /**
 256       * Get all Table Columns.
 257       *
 258       * @return Table\Column[]
 259       */
 260      public function getColumns(): array
 261      {
 262          return $this->columns;
 263      }
 264  
 265      /**
 266       * Validate that the specified column is in the Table range.
 267       *
 268       * @param string $column Column name (e.g. A)
 269       *
 270       * @return int The column offset within the table range
 271       */
 272      public function isColumnInRange(string $column): int
 273      {
 274          if (empty($this->range)) {
 275              throw new PhpSpreadsheetException('No table range is defined.');
 276          }
 277  
 278          $columnIndex = Coordinate::columnIndexFromString($column);
 279          [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
 280          if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) {
 281              throw new PhpSpreadsheetException('Column is outside of current table range.');
 282          }
 283  
 284          return $columnIndex - $rangeStart[0];
 285      }
 286  
 287      /**
 288       * Get a specified Table Column Offset within the defined Table range.
 289       *
 290       * @param string $column Column name (e.g. A)
 291       *
 292       * @return int The offset of the specified column within the table range
 293       */
 294      public function getColumnOffset($column): int
 295      {
 296          return $this->isColumnInRange($column);
 297      }
 298  
 299      /**
 300       * Get a specified Table Column.
 301       *
 302       * @param string $column Column name (e.g. A)
 303       */
 304      public function getColumn($column): Table\Column
 305      {
 306          $this->isColumnInRange($column);
 307  
 308          if (!isset($this->columns[$column])) {
 309              $this->columns[$column] = new Table\Column($column, $this);
 310          }
 311  
 312          return $this->columns[$column];
 313      }
 314  
 315      /**
 316       * Get a specified Table Column by it's offset.
 317       *
 318       * @param int $columnOffset Column offset within range (starting from 0)
 319       */
 320      public function getColumnByOffset($columnOffset): Table\Column
 321      {
 322          [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
 323          $pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $columnOffset);
 324  
 325          return $this->getColumn($pColumn);
 326      }
 327  
 328      /**
 329       * Set Table.
 330       *
 331       * @param string|Table\Column $columnObjectOrString
 332       *            A simple string containing a Column ID like 'A' is permitted
 333       */
 334      public function setColumn($columnObjectOrString): self
 335      {
 336          if ((is_string($columnObjectOrString)) && (!empty($columnObjectOrString))) {
 337              $column = $columnObjectOrString;
 338          } elseif (is_object($columnObjectOrString) && ($columnObjectOrString instanceof Table\Column)) {
 339              $column = $columnObjectOrString->getColumnIndex();
 340          } else {
 341              throw new PhpSpreadsheetException('Column is not within the table range.');
 342          }
 343          $this->isColumnInRange($column);
 344  
 345          if (is_string($columnObjectOrString)) {
 346              $this->columns[$columnObjectOrString] = new Table\Column($columnObjectOrString, $this);
 347          } else {
 348              $columnObjectOrString->setTable($this);
 349              $this->columns[$column] = $columnObjectOrString;
 350          }
 351          ksort($this->columns);
 352  
 353          return $this;
 354      }
 355  
 356      /**
 357       * Clear a specified Table Column.
 358       *
 359       * @param string $column Column name (e.g. A)
 360       */
 361      public function clearColumn($column): self
 362      {
 363          $this->isColumnInRange($column);
 364  
 365          if (isset($this->columns[$column])) {
 366              unset($this->columns[$column]);
 367          }
 368  
 369          return $this;
 370      }
 371  
 372      /**
 373       * Shift an Table Column Rule to a different column.
 374       *
 375       * Note: This method bypasses validation of the destination column to ensure it is within this Table range.
 376       *        Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value.
 377       *        Use with caution.
 378       *
 379       * @param string $fromColumn Column name (e.g. A)
 380       * @param string $toColumn Column name (e.g. B)
 381       */
 382      public function shiftColumn($fromColumn, $toColumn): self
 383      {
 384          $fromColumn = strtoupper($fromColumn);
 385          $toColumn = strtoupper($toColumn);
 386  
 387          if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) {
 388              $this->columns[$fromColumn]->setTable();
 389              $this->columns[$fromColumn]->setColumnIndex($toColumn);
 390              $this->columns[$toColumn] = $this->columns[$fromColumn];
 391              $this->columns[$toColumn]->setTable($this);
 392              unset($this->columns[$fromColumn]);
 393  
 394              ksort($this->columns);
 395          }
 396  
 397          return $this;
 398      }
 399  
 400      /**
 401       * Get table Style.
 402       */
 403      public function getStyle(): Table\TableStyle
 404      {
 405          return $this->style;
 406      }
 407  
 408      /**
 409       * Set table Style.
 410       */
 411      public function setStyle(TableStyle $style): self
 412      {
 413          $this->style = $style;
 414  
 415          return $this;
 416      }
 417  
 418      /**
 419       * Implement PHP __clone to create a deep clone, not just a shallow copy.
 420       */
 421      public function __clone()
 422      {
 423          $vars = get_object_vars($this);
 424          foreach ($vars as $key => $value) {
 425              if (is_object($value)) {
 426                  if ($key === 'workSheet') {
 427                      //    Detach from worksheet
 428                      $this->{$key} = null;
 429                  } else {
 430                      $this->{$key} = clone $value;
 431                  }
 432              } elseif ((is_array($value)) && ($key === 'columns')) {
 433                  //    The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\Table objects
 434                  $this->{$key} = [];
 435                  foreach ($value as $k => $v) {
 436                      $this->{$key}[$k] = clone $v;
 437                      // attach the new cloned Column to this new cloned Table object
 438                      $this->{$key}[$k]->setTable($this);
 439                  }
 440              } else {
 441                  $this->{$key} = $value;
 442              }
 443          }
 444      }
 445  
 446      /**
 447       * toString method replicates previous behavior by returning the range if object is
 448       * referenced as a property of its worksheet.
 449       */
 450      public function __toString()
 451      {
 452          return (string) $this->range;
 453      }
 454  }