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 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet;
   4  
   5  use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
   6  use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
   7  use PhpOffice\PhpSpreadsheet\Shared\File;
   8  use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
   9  use PhpOffice\PhpSpreadsheet\Style\Style;
  10  use PhpOffice\PhpSpreadsheet\Worksheet\Iterator;
  11  use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
  12  use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
  13  
  14  class Spreadsheet
  15  {
  16      // Allowable values for workbook window visilbity
  17      const VISIBILITY_VISIBLE = 'visible';
  18      const VISIBILITY_HIDDEN = 'hidden';
  19      const VISIBILITY_VERY_HIDDEN = 'veryHidden';
  20  
  21      private const DEFINED_NAME_IS_RANGE = false;
  22      private const DEFINED_NAME_IS_FORMULA = true;
  23  
  24      private static $workbookViewVisibilityValues = [
  25          self::VISIBILITY_VISIBLE,
  26          self::VISIBILITY_HIDDEN,
  27          self::VISIBILITY_VERY_HIDDEN,
  28      ];
  29  
  30      /**
  31       * Unique ID.
  32       *
  33       * @var string
  34       */
  35      private $uniqueID;
  36  
  37      /**
  38       * Document properties.
  39       *
  40       * @var Document\Properties
  41       */
  42      private $properties;
  43  
  44      /**
  45       * Document security.
  46       *
  47       * @var Document\Security
  48       */
  49      private $security;
  50  
  51      /**
  52       * Collection of Worksheet objects.
  53       *
  54       * @var Worksheet[]
  55       */
  56      private $workSheetCollection = [];
  57  
  58      /**
  59       * Calculation Engine.
  60       *
  61       * @var null|Calculation
  62       */
  63      private $calculationEngine;
  64  
  65      /**
  66       * Active sheet index.
  67       *
  68       * @var int
  69       */
  70      private $activeSheetIndex = 0;
  71  
  72      /**
  73       * Named ranges.
  74       *
  75       * @var DefinedName[]
  76       */
  77      private $definedNames = [];
  78  
  79      /**
  80       * CellXf supervisor.
  81       *
  82       * @var Style
  83       */
  84      private $cellXfSupervisor;
  85  
  86      /**
  87       * CellXf collection.
  88       *
  89       * @var Style[]
  90       */
  91      private $cellXfCollection = [];
  92  
  93      /**
  94       * CellStyleXf collection.
  95       *
  96       * @var Style[]
  97       */
  98      private $cellStyleXfCollection = [];
  99  
 100      /**
 101       * hasMacros : this workbook have macros ?
 102       *
 103       * @var bool
 104       */
 105      private $hasMacros = false;
 106  
 107      /**
 108       * macrosCode : all macros code as binary data (the vbaProject.bin file, this include form, code,  etc.), null if no macro.
 109       *
 110       * @var null|string
 111       */
 112      private $macrosCode;
 113  
 114      /**
 115       * macrosCertificate : if macros are signed, contains binary data vbaProjectSignature.bin file, null if not signed.
 116       *
 117       * @var null|string
 118       */
 119      private $macrosCertificate;
 120  
 121      /**
 122       * ribbonXMLData : null if workbook is'nt Excel 2007 or not contain a customized UI.
 123       *
 124       * @var null|array{target: string, data: string}
 125       */
 126      private $ribbonXMLData;
 127  
 128      /**
 129       * ribbonBinObjects : null if workbook is'nt Excel 2007 or not contain embedded objects (picture(s)) for Ribbon Elements
 130       * ignored if $ribbonXMLData is null.
 131       *
 132       * @var null|array
 133       */
 134      private $ribbonBinObjects;
 135  
 136      /**
 137       * List of unparsed loaded data for export to same format with better compatibility.
 138       * It has to be minimized when the library start to support currently unparsed data.
 139       *
 140       * @var array
 141       */
 142      private $unparsedLoadedData = [];
 143  
 144      /**
 145       * Controls visibility of the horizonal scroll bar in the application.
 146       *
 147       * @var bool
 148       */
 149      private $showHorizontalScroll = true;
 150  
 151      /**
 152       * Controls visibility of the horizonal scroll bar in the application.
 153       *
 154       * @var bool
 155       */
 156      private $showVerticalScroll = true;
 157  
 158      /**
 159       * Controls visibility of the sheet tabs in the application.
 160       *
 161       * @var bool
 162       */
 163      private $showSheetTabs = true;
 164  
 165      /**
 166       * Specifies a boolean value that indicates whether the workbook window
 167       * is minimized.
 168       *
 169       * @var bool
 170       */
 171      private $minimized = false;
 172  
 173      /**
 174       * Specifies a boolean value that indicates whether to group dates
 175       * when presenting the user with filtering optiomd in the user
 176       * interface.
 177       *
 178       * @var bool
 179       */
 180      private $autoFilterDateGrouping = true;
 181  
 182      /**
 183       * Specifies the index to the first sheet in the book view.
 184       *
 185       * @var int
 186       */
 187      private $firstSheetIndex = 0;
 188  
 189      /**
 190       * Specifies the visible status of the workbook.
 191       *
 192       * @var string
 193       */
 194      private $visibility = self::VISIBILITY_VISIBLE;
 195  
 196      /**
 197       * Specifies the ratio between the workbook tabs bar and the horizontal
 198       * scroll bar.  TabRatio is assumed to be out of 1000 of the horizontal
 199       * window width.
 200       *
 201       * @var int
 202       */
 203      private $tabRatio = 600;
 204  
 205      /**
 206       * The workbook has macros ?
 207       *
 208       * @return bool
 209       */
 210      public function hasMacros()
 211      {
 212          return $this->hasMacros;
 213      }
 214  
 215      /**
 216       * Define if a workbook has macros.
 217       *
 218       * @param bool $hasMacros true|false
 219       */
 220      public function setHasMacros($hasMacros): void
 221      {
 222          $this->hasMacros = (bool) $hasMacros;
 223      }
 224  
 225      /**
 226       * Set the macros code.
 227       *
 228       * @param string $macroCode string|null
 229       */
 230      public function setMacrosCode($macroCode): void
 231      {
 232          $this->macrosCode = $macroCode;
 233          $this->setHasMacros($macroCode !== null);
 234      }
 235  
 236      /**
 237       * Return the macros code.
 238       *
 239       * @return null|string
 240       */
 241      public function getMacrosCode()
 242      {
 243          return $this->macrosCode;
 244      }
 245  
 246      /**
 247       * Set the macros certificate.
 248       *
 249       * @param null|string $certificate
 250       */
 251      public function setMacrosCertificate($certificate): void
 252      {
 253          $this->macrosCertificate = $certificate;
 254      }
 255  
 256      /**
 257       * Is the project signed ?
 258       *
 259       * @return bool true|false
 260       */
 261      public function hasMacrosCertificate()
 262      {
 263          return $this->macrosCertificate !== null;
 264      }
 265  
 266      /**
 267       * Return the macros certificate.
 268       *
 269       * @return null|string
 270       */
 271      public function getMacrosCertificate()
 272      {
 273          return $this->macrosCertificate;
 274      }
 275  
 276      /**
 277       * Remove all macros, certificate from spreadsheet.
 278       */
 279      public function discardMacros(): void
 280      {
 281          $this->hasMacros = false;
 282          $this->macrosCode = null;
 283          $this->macrosCertificate = null;
 284      }
 285  
 286      /**
 287       * set ribbon XML data.
 288       *
 289       * @param null|mixed $target
 290       * @param null|mixed $xmlData
 291       */
 292      public function setRibbonXMLData($target, $xmlData): void
 293      {
 294          if ($target !== null && $xmlData !== null) {
 295              $this->ribbonXMLData = ['target' => $target, 'data' => $xmlData];
 296          } else {
 297              $this->ribbonXMLData = null;
 298          }
 299      }
 300  
 301      /**
 302       * retrieve ribbon XML Data.
 303       *
 304       * @param string $what
 305       *
 306       * @return null|array|string
 307       */
 308      public function getRibbonXMLData($what = 'all') //we need some constants here...
 309      {
 310          $returnData = null;
 311          $what = strtolower($what);
 312          switch ($what) {
 313              case 'all':
 314                  $returnData = $this->ribbonXMLData;
 315  
 316                  break;
 317              case 'target':
 318              case 'data':
 319                  if (is_array($this->ribbonXMLData)) {
 320                      $returnData = $this->ribbonXMLData[$what];
 321                  }
 322  
 323                  break;
 324          }
 325  
 326          return $returnData;
 327      }
 328  
 329      /**
 330       * store binaries ribbon objects (pictures).
 331       *
 332       * @param null|mixed $BinObjectsNames
 333       * @param null|mixed $BinObjectsData
 334       */
 335      public function setRibbonBinObjects($BinObjectsNames, $BinObjectsData): void
 336      {
 337          if ($BinObjectsNames !== null && $BinObjectsData !== null) {
 338              $this->ribbonBinObjects = ['names' => $BinObjectsNames, 'data' => $BinObjectsData];
 339          } else {
 340              $this->ribbonBinObjects = null;
 341          }
 342      }
 343  
 344      /**
 345       * List of unparsed loaded data for export to same format with better compatibility.
 346       * It has to be minimized when the library start to support currently unparsed data.
 347       *
 348       * @internal
 349       *
 350       * @return array
 351       */
 352      public function getUnparsedLoadedData()
 353      {
 354          return $this->unparsedLoadedData;
 355      }
 356  
 357      /**
 358       * List of unparsed loaded data for export to same format with better compatibility.
 359       * It has to be minimized when the library start to support currently unparsed data.
 360       *
 361       * @internal
 362       */
 363      public function setUnparsedLoadedData(array $unparsedLoadedData): void
 364      {
 365          $this->unparsedLoadedData = $unparsedLoadedData;
 366      }
 367  
 368      /**
 369       * return the extension of a filename. Internal use for a array_map callback (php<5.3 don't like lambda function).
 370       *
 371       * @param mixed $path
 372       *
 373       * @return string
 374       */
 375      private function getExtensionOnly($path)
 376      {
 377          $extension = pathinfo($path, PATHINFO_EXTENSION);
 378  
 379          return is_array($extension) ? '' : $extension;
 380      }
 381  
 382      /**
 383       * retrieve Binaries Ribbon Objects.
 384       *
 385       * @param string $what
 386       *
 387       * @return null|array
 388       */
 389      public function getRibbonBinObjects($what = 'all')
 390      {
 391          $ReturnData = null;
 392          $what = strtolower($what);
 393          switch ($what) {
 394              case 'all':
 395                  return $this->ribbonBinObjects;
 396  
 397                  break;
 398              case 'names':
 399              case 'data':
 400                  if (is_array($this->ribbonBinObjects) && isset($this->ribbonBinObjects[$what])) {
 401                      $ReturnData = $this->ribbonBinObjects[$what];
 402                  }
 403  
 404                  break;
 405              case 'types':
 406                  if (
 407                      is_array($this->ribbonBinObjects) &&
 408                      isset($this->ribbonBinObjects['data']) && is_array($this->ribbonBinObjects['data'])
 409                  ) {
 410                      $tmpTypes = array_keys($this->ribbonBinObjects['data']);
 411                      $ReturnData = array_unique(array_map([$this, 'getExtensionOnly'], $tmpTypes));
 412                  } else {
 413                      $ReturnData = []; // the caller want an array... not null if empty
 414                  }
 415  
 416                  break;
 417          }
 418  
 419          return $ReturnData;
 420      }
 421  
 422      /**
 423       * This workbook have a custom UI ?
 424       *
 425       * @return bool
 426       */
 427      public function hasRibbon()
 428      {
 429          return $this->ribbonXMLData !== null;
 430      }
 431  
 432      /**
 433       * This workbook have additionnal object for the ribbon ?
 434       *
 435       * @return bool
 436       */
 437      public function hasRibbonBinObjects()
 438      {
 439          return $this->ribbonBinObjects !== null;
 440      }
 441  
 442      /**
 443       * Check if a sheet with a specified code name already exists.
 444       *
 445       * @param string $codeName Name of the worksheet to check
 446       *
 447       * @return bool
 448       */
 449      public function sheetCodeNameExists($codeName)
 450      {
 451          return $this->getSheetByCodeName($codeName) !== null;
 452      }
 453  
 454      /**
 455       * Get sheet by code name. Warning : sheet don't have always a code name !
 456       *
 457       * @param string $codeName Sheet name
 458       *
 459       * @return null|Worksheet
 460       */
 461      public function getSheetByCodeName($codeName)
 462      {
 463          $worksheetCount = count($this->workSheetCollection);
 464          for ($i = 0; $i < $worksheetCount; ++$i) {
 465              if ($this->workSheetCollection[$i]->getCodeName() == $codeName) {
 466                  return $this->workSheetCollection[$i];
 467              }
 468          }
 469  
 470          return null;
 471      }
 472  
 473      /**
 474       * Create a new PhpSpreadsheet with one Worksheet.
 475       */
 476      public function __construct()
 477      {
 478          $this->uniqueID = uniqid('', true);
 479          $this->calculationEngine = new Calculation($this);
 480  
 481          // Initialise worksheet collection and add one worksheet
 482          $this->workSheetCollection = [];
 483          $this->workSheetCollection[] = new Worksheet($this);
 484          $this->activeSheetIndex = 0;
 485  
 486          // Create document properties
 487          $this->properties = new Document\Properties();
 488  
 489          // Create document security
 490          $this->security = new Document\Security();
 491  
 492          // Set defined names
 493          $this->definedNames = [];
 494  
 495          // Create the cellXf supervisor
 496          $this->cellXfSupervisor = new Style(true);
 497          $this->cellXfSupervisor->bindParent($this);
 498  
 499          // Create the default style
 500          $this->addCellXf(new Style());
 501          $this->addCellStyleXf(new Style());
 502      }
 503  
 504      /**
 505       * Code to execute when this worksheet is unset().
 506       */
 507      public function __destruct()
 508      {
 509          $this->disconnectWorksheets();
 510          $this->calculationEngine = null;
 511          $this->cellXfCollection = [];
 512          $this->cellStyleXfCollection = [];
 513      }
 514  
 515      /**
 516       * Disconnect all worksheets from this PhpSpreadsheet workbook object,
 517       * typically so that the PhpSpreadsheet object can be unset.
 518       */
 519      public function disconnectWorksheets(): void
 520      {
 521          foreach ($this->workSheetCollection as $worksheet) {
 522              $worksheet->disconnectCells();
 523              unset($worksheet);
 524          }
 525          $this->workSheetCollection = [];
 526      }
 527  
 528      /**
 529       * Return the calculation engine for this worksheet.
 530       *
 531       * @return null|Calculation
 532       */
 533      public function getCalculationEngine()
 534      {
 535          return $this->calculationEngine;
 536      }
 537  
 538      /**
 539       * Get properties.
 540       *
 541       * @return Document\Properties
 542       */
 543      public function getProperties()
 544      {
 545          return $this->properties;
 546      }
 547  
 548      /**
 549       * Set properties.
 550       */
 551      public function setProperties(Document\Properties $documentProperties): void
 552      {
 553          $this->properties = $documentProperties;
 554      }
 555  
 556      /**
 557       * Get security.
 558       *
 559       * @return Document\Security
 560       */
 561      public function getSecurity()
 562      {
 563          return $this->security;
 564      }
 565  
 566      /**
 567       * Set security.
 568       */
 569      public function setSecurity(Document\Security $documentSecurity): void
 570      {
 571          $this->security = $documentSecurity;
 572      }
 573  
 574      /**
 575       * Get active sheet.
 576       *
 577       * @return Worksheet
 578       */
 579      public function getActiveSheet()
 580      {
 581          return $this->getSheet($this->activeSheetIndex);
 582      }
 583  
 584      /**
 585       * Create sheet and add it to this workbook.
 586       *
 587       * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
 588       *
 589       * @return Worksheet
 590       */
 591      public function createSheet($sheetIndex = null)
 592      {
 593          $newSheet = new Worksheet($this);
 594          $this->addSheet($newSheet, $sheetIndex);
 595  
 596          return $newSheet;
 597      }
 598  
 599      /**
 600       * Check if a sheet with a specified name already exists.
 601       *
 602       * @param string $worksheetName Name of the worksheet to check
 603       *
 604       * @return bool
 605       */
 606      public function sheetNameExists($worksheetName)
 607      {
 608          return $this->getSheetByName($worksheetName) !== null;
 609      }
 610  
 611      /**
 612       * Add sheet.
 613       *
 614       * @param Worksheet $worksheet The worksheet to add
 615       * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
 616       *
 617       * @return Worksheet
 618       */
 619      public function addSheet(Worksheet $worksheet, $sheetIndex = null)
 620      {
 621          if ($this->sheetNameExists($worksheet->getTitle())) {
 622              throw new Exception(
 623                  "Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename this worksheet first."
 624              );
 625          }
 626  
 627          if ($sheetIndex === null) {
 628              if ($this->activeSheetIndex < 0) {
 629                  $this->activeSheetIndex = 0;
 630              }
 631              $this->workSheetCollection[] = $worksheet;
 632          } else {
 633              // Insert the sheet at the requested index
 634              array_splice(
 635                  $this->workSheetCollection,
 636                  $sheetIndex,
 637                  0,
 638                  [$worksheet]
 639              );
 640  
 641              // Adjust active sheet index if necessary
 642              if ($this->activeSheetIndex >= $sheetIndex) {
 643                  ++$this->activeSheetIndex;
 644              }
 645          }
 646  
 647          if ($worksheet->getParent() === null) {
 648              $worksheet->rebindParent($this);
 649          }
 650  
 651          return $worksheet;
 652      }
 653  
 654      /**
 655       * Remove sheet by index.
 656       *
 657       * @param int $sheetIndex Index position of the worksheet to remove
 658       */
 659      public function removeSheetByIndex($sheetIndex): void
 660      {
 661          $numSheets = count($this->workSheetCollection);
 662          if ($sheetIndex > $numSheets - 1) {
 663              throw new Exception(
 664                  "You tried to remove a sheet by the out of bounds index: {$sheetIndex}. The actual number of sheets is {$numSheets}."
 665              );
 666          }
 667          array_splice($this->workSheetCollection, $sheetIndex, 1);
 668  
 669          // Adjust active sheet index if necessary
 670          if (
 671              ($this->activeSheetIndex >= $sheetIndex) &&
 672              ($this->activeSheetIndex > 0 || $numSheets <= 1)
 673          ) {
 674              --$this->activeSheetIndex;
 675          }
 676      }
 677  
 678      /**
 679       * Get sheet by index.
 680       *
 681       * @param int $sheetIndex Sheet index
 682       *
 683       * @return Worksheet
 684       */
 685      public function getSheet($sheetIndex)
 686      {
 687          if (!isset($this->workSheetCollection[$sheetIndex])) {
 688              $numSheets = $this->getSheetCount();
 689  
 690              throw new Exception(
 691                  "Your requested sheet index: {$sheetIndex} is out of bounds. The actual number of sheets is {$numSheets}."
 692              );
 693          }
 694  
 695          return $this->workSheetCollection[$sheetIndex];
 696      }
 697  
 698      /**
 699       * Get all sheets.
 700       *
 701       * @return Worksheet[]
 702       */
 703      public function getAllSheets()
 704      {
 705          return $this->workSheetCollection;
 706      }
 707  
 708      /**
 709       * Get sheet by name.
 710       *
 711       * @param string $worksheetName Sheet name
 712       *
 713       * @return null|Worksheet
 714       */
 715      public function getSheetByName($worksheetName)
 716      {
 717          $worksheetCount = count($this->workSheetCollection);
 718          for ($i = 0; $i < $worksheetCount; ++$i) {
 719              if ($this->workSheetCollection[$i]->getTitle() === trim($worksheetName, "'")) {
 720                  return $this->workSheetCollection[$i];
 721              }
 722          }
 723  
 724          return null;
 725      }
 726  
 727      /**
 728       * Get sheet by name, throwing exception if not found.
 729       */
 730      public function getSheetByNameOrThrow(string $worksheetName): Worksheet
 731      {
 732          $worksheet = $this->getSheetByName($worksheetName);
 733          if ($worksheet === null) {
 734              throw new Exception("Sheet $worksheetName does not exist.");
 735          }
 736  
 737          return $worksheet;
 738      }
 739  
 740      /**
 741       * Get index for sheet.
 742       *
 743       * @return int index
 744       */
 745      public function getIndex(Worksheet $worksheet)
 746      {
 747          foreach ($this->workSheetCollection as $key => $value) {
 748              if ($value->getHashCode() === $worksheet->getHashCode()) {
 749                  return $key;
 750              }
 751          }
 752  
 753          throw new Exception('Sheet does not exist.');
 754      }
 755  
 756      /**
 757       * Set index for sheet by sheet name.
 758       *
 759       * @param string $worksheetName Sheet name to modify index for
 760       * @param int $newIndexPosition New index for the sheet
 761       *
 762       * @return int New sheet index
 763       */
 764      public function setIndexByName($worksheetName, $newIndexPosition)
 765      {
 766          $oldIndex = $this->getIndex($this->getSheetByName($worksheetName));
 767          $worksheet = array_splice(
 768              $this->workSheetCollection,
 769              $oldIndex,
 770              1
 771          );
 772          array_splice(
 773              $this->workSheetCollection,
 774              $newIndexPosition,
 775              0,
 776              $worksheet
 777          );
 778  
 779          return $newIndexPosition;
 780      }
 781  
 782      /**
 783       * Get sheet count.
 784       *
 785       * @return int
 786       */
 787      public function getSheetCount()
 788      {
 789          return count($this->workSheetCollection);
 790      }
 791  
 792      /**
 793       * Get active sheet index.
 794       *
 795       * @return int Active sheet index
 796       */
 797      public function getActiveSheetIndex()
 798      {
 799          return $this->activeSheetIndex;
 800      }
 801  
 802      /**
 803       * Set active sheet index.
 804       *
 805       * @param int $worksheetIndex Active sheet index
 806       *
 807       * @return Worksheet
 808       */
 809      public function setActiveSheetIndex($worksheetIndex)
 810      {
 811          $numSheets = count($this->workSheetCollection);
 812  
 813          if ($worksheetIndex > $numSheets - 1) {
 814              throw new Exception(
 815                  "You tried to set a sheet active by the out of bounds index: {$worksheetIndex}. The actual number of sheets is {$numSheets}."
 816              );
 817          }
 818          $this->activeSheetIndex = $worksheetIndex;
 819  
 820          return $this->getActiveSheet();
 821      }
 822  
 823      /**
 824       * Set active sheet index by name.
 825       *
 826       * @param string $worksheetName Sheet title
 827       *
 828       * @return Worksheet
 829       */
 830      public function setActiveSheetIndexByName($worksheetName)
 831      {
 832          if (($worksheet = $this->getSheetByName($worksheetName)) instanceof Worksheet) {
 833              $this->setActiveSheetIndex($this->getIndex($worksheet));
 834  
 835              return $worksheet;
 836          }
 837  
 838          throw new Exception('Workbook does not contain sheet:' . $worksheetName);
 839      }
 840  
 841      /**
 842       * Get sheet names.
 843       *
 844       * @return string[]
 845       */
 846      public function getSheetNames()
 847      {
 848          $returnValue = [];
 849          $worksheetCount = $this->getSheetCount();
 850          for ($i = 0; $i < $worksheetCount; ++$i) {
 851              $returnValue[] = $this->getSheet($i)->getTitle();
 852          }
 853  
 854          return $returnValue;
 855      }
 856  
 857      /**
 858       * Add external sheet.
 859       *
 860       * @param Worksheet $worksheet External sheet to add
 861       * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
 862       *
 863       * @return Worksheet
 864       */
 865      public function addExternalSheet(Worksheet $worksheet, $sheetIndex = null)
 866      {
 867          if ($this->sheetNameExists($worksheet->getTitle())) {
 868              throw new Exception("Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename the external sheet first.");
 869          }
 870  
 871          // count how many cellXfs there are in this workbook currently, we will need this below
 872          $countCellXfs = count($this->cellXfCollection);
 873  
 874          // copy all the shared cellXfs from the external workbook and append them to the current
 875          foreach ($worksheet->getParent()->getCellXfCollection() as $cellXf) {
 876              $this->addCellXf(clone $cellXf);
 877          }
 878  
 879          // move sheet to this workbook
 880          $worksheet->rebindParent($this);
 881  
 882          // update the cellXfs
 883          foreach ($worksheet->getCoordinates(false) as $coordinate) {
 884              $cell = $worksheet->getCell($coordinate);
 885              $cell->setXfIndex($cell->getXfIndex() + $countCellXfs);
 886          }
 887  
 888          // update the column dimensions Xfs
 889          foreach ($worksheet->getColumnDimensions() as $columnDimension) {
 890              $columnDimension->setXfIndex($columnDimension->getXfIndex() + $countCellXfs);
 891          }
 892  
 893          // update the row dimensions Xfs
 894          foreach ($worksheet->getRowDimensions() as $rowDimension) {
 895              $xfIndex = $rowDimension->getXfIndex();
 896              if ($xfIndex !== null) {
 897                  $rowDimension->setXfIndex($xfIndex + $countCellXfs);
 898              }
 899          }
 900  
 901          return $this->addSheet($worksheet, $sheetIndex);
 902      }
 903  
 904      /**
 905       * Get an array of all Named Ranges.
 906       *
 907       * @return DefinedName[]
 908       */
 909      public function getNamedRanges(): array
 910      {
 911          return array_filter(
 912              $this->definedNames,
 913              function (DefinedName $definedName) {
 914                  return $definedName->isFormula() === self::DEFINED_NAME_IS_RANGE;
 915              }
 916          );
 917      }
 918  
 919      /**
 920       * Get an array of all Named Formulae.
 921       *
 922       * @return DefinedName[]
 923       */
 924      public function getNamedFormulae(): array
 925      {
 926          return array_filter(
 927              $this->definedNames,
 928              function (DefinedName $definedName) {
 929                  return $definedName->isFormula() === self::DEFINED_NAME_IS_FORMULA;
 930              }
 931          );
 932      }
 933  
 934      /**
 935       * Get an array of all Defined Names (both named ranges and named formulae).
 936       *
 937       * @return DefinedName[]
 938       */
 939      public function getDefinedNames(): array
 940      {
 941          return $this->definedNames;
 942      }
 943  
 944      /**
 945       * Add a named range.
 946       * If a named range with this name already exists, then this will replace the existing value.
 947       */
 948      public function addNamedRange(NamedRange $namedRange): void
 949      {
 950          $this->addDefinedName($namedRange);
 951      }
 952  
 953      /**
 954       * Add a named formula.
 955       * If a named formula with this name already exists, then this will replace the existing value.
 956       */
 957      public function addNamedFormula(NamedFormula $namedFormula): void
 958      {
 959          $this->addDefinedName($namedFormula);
 960      }
 961  
 962      /**
 963       * Add a defined name (either a named range or a named formula).
 964       * If a defined named with this name already exists, then this will replace the existing value.
 965       */
 966      public function addDefinedName(DefinedName $definedName): void
 967      {
 968          $upperCaseName = StringHelper::strToUpper($definedName->getName());
 969          if ($definedName->getScope() == null) {
 970              // global scope
 971              $this->definedNames[$upperCaseName] = $definedName;
 972          } else {
 973              // local scope
 974              $this->definedNames[$definedName->getScope()->getTitle() . '!' . $upperCaseName] = $definedName;
 975          }
 976      }
 977  
 978      /**
 979       * Get named range.
 980       *
 981       * @param null|Worksheet $worksheet Scope. Use null for global scope
 982       */
 983      public function getNamedRange(string $namedRange, ?Worksheet $worksheet = null): ?NamedRange
 984      {
 985          $returnValue = null;
 986  
 987          if ($namedRange !== '') {
 988              $namedRange = StringHelper::strToUpper($namedRange);
 989              // first look for global named range
 990              $returnValue = $this->getGlobalDefinedNameByType($namedRange, self::DEFINED_NAME_IS_RANGE);
 991              // then look for local named range (has priority over global named range if both names exist)
 992              $returnValue = $this->getLocalDefinedNameByType($namedRange, self::DEFINED_NAME_IS_RANGE, $worksheet) ?: $returnValue;
 993          }
 994  
 995          return $returnValue instanceof NamedRange ? $returnValue : null;
 996      }
 997  
 998      /**
 999       * Get named formula.
1000       *
1001       * @param null|Worksheet $worksheet Scope. Use null for global scope
1002       */
1003      public function getNamedFormula(string $namedFormula, ?Worksheet $worksheet = null): ?NamedFormula
1004      {
1005          $returnValue = null;
1006  
1007          if ($namedFormula !== '') {
1008              $namedFormula = StringHelper::strToUpper($namedFormula);
1009              // first look for global named formula
1010              $returnValue = $this->getGlobalDefinedNameByType($namedFormula, self::DEFINED_NAME_IS_FORMULA);
1011              // then look for local named formula (has priority over global named formula if both names exist)
1012              $returnValue = $this->getLocalDefinedNameByType($namedFormula, self::DEFINED_NAME_IS_FORMULA, $worksheet) ?: $returnValue;
1013          }
1014  
1015          return $returnValue instanceof NamedFormula ? $returnValue : null;
1016      }
1017  
1018      private function getGlobalDefinedNameByType(string $name, bool $type): ?DefinedName
1019      {
1020          if (isset($this->definedNames[$name]) && $this->definedNames[$name]->isFormula() === $type) {
1021              return $this->definedNames[$name];
1022          }
1023  
1024          return null;
1025      }
1026  
1027      private function getLocalDefinedNameByType(string $name, bool $type, ?Worksheet $worksheet = null): ?DefinedName
1028      {
1029          if (
1030              ($worksheet !== null) && isset($this->definedNames[$worksheet->getTitle() . '!' . $name])
1031              && $this->definedNames[$worksheet->getTitle() . '!' . $name]->isFormula() === $type
1032          ) {
1033              return $this->definedNames[$worksheet->getTitle() . '!' . $name];
1034          }
1035  
1036          return null;
1037      }
1038  
1039      /**
1040       * Get named range.
1041       *
1042       * @param null|Worksheet $worksheet Scope. Use null for global scope
1043       */
1044      public function getDefinedName(string $definedName, ?Worksheet $worksheet = null): ?DefinedName
1045      {
1046          $returnValue = null;
1047  
1048          if ($definedName !== '') {
1049              $definedName = StringHelper::strToUpper($definedName);
1050              // first look for global defined name
1051              if (isset($this->definedNames[$definedName])) {
1052                  $returnValue = $this->definedNames[$definedName];
1053              }
1054  
1055              // then look for local defined name (has priority over global defined name if both names exist)
1056              if (($worksheet !== null) && isset($this->definedNames[$worksheet->getTitle() . '!' . $definedName])) {
1057                  $returnValue = $this->definedNames[$worksheet->getTitle() . '!' . $definedName];
1058              }
1059          }
1060  
1061          return $returnValue;
1062      }
1063  
1064      /**
1065       * Remove named range.
1066       *
1067       * @param null|Worksheet $worksheet scope: use null for global scope
1068       *
1069       * @return $this
1070       */
1071      public function removeNamedRange(string $namedRange, ?Worksheet $worksheet = null): self
1072      {
1073          if ($this->getNamedRange($namedRange, $worksheet) === null) {
1074              return $this;
1075          }
1076  
1077          return $this->removeDefinedName($namedRange, $worksheet);
1078      }
1079  
1080      /**
1081       * Remove named formula.
1082       *
1083       * @param null|Worksheet $worksheet scope: use null for global scope
1084       *
1085       * @return $this
1086       */
1087      public function removeNamedFormula(string $namedFormula, ?Worksheet $worksheet = null): self
1088      {
1089          if ($this->getNamedFormula($namedFormula, $worksheet) === null) {
1090              return $this;
1091          }
1092  
1093          return $this->removeDefinedName($namedFormula, $worksheet);
1094      }
1095  
1096      /**
1097       * Remove defined name.
1098       *
1099       * @param null|Worksheet $worksheet scope: use null for global scope
1100       *
1101       * @return $this
1102       */
1103      public function removeDefinedName(string $definedName, ?Worksheet $worksheet = null): self
1104      {
1105          $definedName = StringHelper::strToUpper($definedName);
1106  
1107          if ($worksheet === null) {
1108              if (isset($this->definedNames[$definedName])) {
1109                  unset($this->definedNames[$definedName]);
1110              }
1111          } else {
1112              if (isset($this->definedNames[$worksheet->getTitle() . '!' . $definedName])) {
1113                  unset($this->definedNames[$worksheet->getTitle() . '!' . $definedName]);
1114              } elseif (isset($this->definedNames[$definedName])) {
1115                  unset($this->definedNames[$definedName]);
1116              }
1117          }
1118  
1119          return $this;
1120      }
1121  
1122      /**
1123       * Get worksheet iterator.
1124       *
1125       * @return Iterator
1126       */
1127      public function getWorksheetIterator()
1128      {
1129          return new Iterator($this);
1130      }
1131  
1132      /**
1133       * Copy workbook (!= clone!).
1134       *
1135       * @return Spreadsheet
1136       */
1137      public function copy()
1138      {
1139          $filename = File::temporaryFilename();
1140          $writer = new XlsxWriter($this);
1141          $writer->setIncludeCharts(true);
1142          $writer->save($filename);
1143  
1144          $reader = new XlsxReader();
1145          $reader->setIncludeCharts(true);
1146          $reloadedSpreadsheet = $reader->load($filename);
1147          unlink($filename);
1148  
1149          return $reloadedSpreadsheet;
1150      }
1151  
1152      public function __clone()
1153      {
1154          throw new Exception(
1155              'Do not use clone on spreadsheet. Use spreadsheet->copy() instead.'
1156          );
1157      }
1158  
1159      /**
1160       * Get the workbook collection of cellXfs.
1161       *
1162       * @return Style[]
1163       */
1164      public function getCellXfCollection()
1165      {
1166          return $this->cellXfCollection;
1167      }
1168  
1169      /**
1170       * Get cellXf by index.
1171       *
1172       * @param int $cellStyleIndex
1173       *
1174       * @return Style
1175       */
1176      public function getCellXfByIndex($cellStyleIndex)
1177      {
1178          return $this->cellXfCollection[$cellStyleIndex];
1179      }
1180  
1181      /**
1182       * Get cellXf by hash code.
1183       *
1184       * @param string $hashcode
1185       *
1186       * @return false|Style
1187       */
1188      public function getCellXfByHashCode($hashcode)
1189      {
1190          foreach ($this->cellXfCollection as $cellXf) {
1191              if ($cellXf->getHashCode() === $hashcode) {
1192                  return $cellXf;
1193              }
1194          }
1195  
1196          return false;
1197      }
1198  
1199      /**
1200       * Check if style exists in style collection.
1201       *
1202       * @return bool
1203       */
1204      public function cellXfExists(Style $cellStyleIndex)
1205      {
1206          return in_array($cellStyleIndex, $this->cellXfCollection, true);
1207      }
1208  
1209      /**
1210       * Get default style.
1211       *
1212       * @return Style
1213       */
1214      public function getDefaultStyle()
1215      {
1216          if (isset($this->cellXfCollection[0])) {
1217              return $this->cellXfCollection[0];
1218          }
1219  
1220          throw new Exception('No default style found for this workbook');
1221      }
1222  
1223      /**
1224       * Add a cellXf to the workbook.
1225       */
1226      public function addCellXf(Style $style): void
1227      {
1228          $this->cellXfCollection[] = $style;
1229          $style->setIndex(count($this->cellXfCollection) - 1);
1230      }
1231  
1232      /**
1233       * Remove cellXf by index. It is ensured that all cells get their xf index updated.
1234       *
1235       * @param int $cellStyleIndex Index to cellXf
1236       */
1237      public function removeCellXfByIndex($cellStyleIndex): void
1238      {
1239          if ($cellStyleIndex > count($this->cellXfCollection) - 1) {
1240              throw new Exception('CellXf index is out of bounds.');
1241          }
1242  
1243          // first remove the cellXf
1244          array_splice($this->cellXfCollection, $cellStyleIndex, 1);
1245  
1246          // then update cellXf indexes for cells
1247          foreach ($this->workSheetCollection as $worksheet) {
1248              foreach ($worksheet->getCoordinates(false) as $coordinate) {
1249                  $cell = $worksheet->getCell($coordinate);
1250                  $xfIndex = $cell->getXfIndex();
1251                  if ($xfIndex > $cellStyleIndex) {
1252                      // decrease xf index by 1
1253                      $cell->setXfIndex($xfIndex - 1);
1254                  } elseif ($xfIndex == $cellStyleIndex) {
1255                      // set to default xf index 0
1256                      $cell->setXfIndex(0);
1257                  }
1258              }
1259          }
1260      }
1261  
1262      /**
1263       * Get the cellXf supervisor.
1264       *
1265       * @return Style
1266       */
1267      public function getCellXfSupervisor()
1268      {
1269          return $this->cellXfSupervisor;
1270      }
1271  
1272      /**
1273       * Get the workbook collection of cellStyleXfs.
1274       *
1275       * @return Style[]
1276       */
1277      public function getCellStyleXfCollection()
1278      {
1279          return $this->cellStyleXfCollection;
1280      }
1281  
1282      /**
1283       * Get cellStyleXf by index.
1284       *
1285       * @param int $cellStyleIndex Index to cellXf
1286       *
1287       * @return Style
1288       */
1289      public function getCellStyleXfByIndex($cellStyleIndex)
1290      {
1291          return $this->cellStyleXfCollection[$cellStyleIndex];
1292      }
1293  
1294      /**
1295       * Get cellStyleXf by hash code.
1296       *
1297       * @param string $hashcode
1298       *
1299       * @return false|Style
1300       */
1301      public function getCellStyleXfByHashCode($hashcode)
1302      {
1303          foreach ($this->cellStyleXfCollection as $cellStyleXf) {
1304              if ($cellStyleXf->getHashCode() === $hashcode) {
1305                  return $cellStyleXf;
1306              }
1307          }
1308  
1309          return false;
1310      }
1311  
1312      /**
1313       * Add a cellStyleXf to the workbook.
1314       */
1315      public function addCellStyleXf(Style $style): void
1316      {
1317          $this->cellStyleXfCollection[] = $style;
1318          $style->setIndex(count($this->cellStyleXfCollection) - 1);
1319      }
1320  
1321      /**
1322       * Remove cellStyleXf by index.
1323       *
1324       * @param int $cellStyleIndex Index to cellXf
1325       */
1326      public function removeCellStyleXfByIndex($cellStyleIndex): void
1327      {
1328          if ($cellStyleIndex > count($this->cellStyleXfCollection) - 1) {
1329              throw new Exception('CellStyleXf index is out of bounds.');
1330          }
1331          array_splice($this->cellStyleXfCollection, $cellStyleIndex, 1);
1332      }
1333  
1334      /**
1335       * Eliminate all unneeded cellXf and afterwards update the xfIndex for all cells
1336       * and columns in the workbook.
1337       */
1338      public function garbageCollect(): void
1339      {
1340          // how many references are there to each cellXf ?
1341          $countReferencesCellXf = [];
1342          foreach ($this->cellXfCollection as $index => $cellXf) {
1343              $countReferencesCellXf[$index] = 0;
1344          }
1345  
1346          foreach ($this->getWorksheetIterator() as $sheet) {
1347              // from cells
1348              foreach ($sheet->getCoordinates(false) as $coordinate) {
1349                  $cell = $sheet->getCell($coordinate);
1350                  ++$countReferencesCellXf[$cell->getXfIndex()];
1351              }
1352  
1353              // from row dimensions
1354              foreach ($sheet->getRowDimensions() as $rowDimension) {
1355                  if ($rowDimension->getXfIndex() !== null) {
1356                      ++$countReferencesCellXf[$rowDimension->getXfIndex()];
1357                  }
1358              }
1359  
1360              // from column dimensions
1361              foreach ($sheet->getColumnDimensions() as $columnDimension) {
1362                  ++$countReferencesCellXf[$columnDimension->getXfIndex()];
1363              }
1364          }
1365  
1366          // remove cellXfs without references and create mapping so we can update xfIndex
1367          // for all cells and columns
1368          $countNeededCellXfs = 0;
1369          $map = [];
1370          foreach ($this->cellXfCollection as $index => $cellXf) {
1371              if ($countReferencesCellXf[$index] > 0 || $index == 0) { // we must never remove the first cellXf
1372                  ++$countNeededCellXfs;
1373              } else {
1374                  unset($this->cellXfCollection[$index]);
1375              }
1376              $map[$index] = $countNeededCellXfs - 1;
1377          }
1378          $this->cellXfCollection = array_values($this->cellXfCollection);
1379  
1380          // update the index for all cellXfs
1381          foreach ($this->cellXfCollection as $i => $cellXf) {
1382              $cellXf->setIndex($i);
1383          }
1384  
1385          // make sure there is always at least one cellXf (there should be)
1386          if (empty($this->cellXfCollection)) {
1387              $this->cellXfCollection[] = new Style();
1388          }
1389  
1390          // update the xfIndex for all cells, row dimensions, column dimensions
1391          foreach ($this->getWorksheetIterator() as $sheet) {
1392              // for all cells
1393              foreach ($sheet->getCoordinates(false) as $coordinate) {
1394                  $cell = $sheet->getCell($coordinate);
1395                  $cell->setXfIndex($map[$cell->getXfIndex()]);
1396              }
1397  
1398              // for all row dimensions
1399              foreach ($sheet->getRowDimensions() as $rowDimension) {
1400                  if ($rowDimension->getXfIndex() !== null) {
1401                      $rowDimension->setXfIndex($map[$rowDimension->getXfIndex()]);
1402                  }
1403              }
1404  
1405              // for all column dimensions
1406              foreach ($sheet->getColumnDimensions() as $columnDimension) {
1407                  $columnDimension->setXfIndex($map[$columnDimension->getXfIndex()]);
1408              }
1409  
1410              // also do garbage collection for all the sheets
1411              $sheet->garbageCollect();
1412          }
1413      }
1414  
1415      /**
1416       * Return the unique ID value assigned to this spreadsheet workbook.
1417       *
1418       * @return string
1419       */
1420      public function getID()
1421      {
1422          return $this->uniqueID;
1423      }
1424  
1425      /**
1426       * Get the visibility of the horizonal scroll bar in the application.
1427       *
1428       * @return bool True if horizonal scroll bar is visible
1429       */
1430      public function getShowHorizontalScroll()
1431      {
1432          return $this->showHorizontalScroll;
1433      }
1434  
1435      /**
1436       * Set the visibility of the horizonal scroll bar in the application.
1437       *
1438       * @param bool $showHorizontalScroll True if horizonal scroll bar is visible
1439       */
1440      public function setShowHorizontalScroll($showHorizontalScroll): void
1441      {
1442          $this->showHorizontalScroll = (bool) $showHorizontalScroll;
1443      }
1444  
1445      /**
1446       * Get the visibility of the vertical scroll bar in the application.
1447       *
1448       * @return bool True if vertical scroll bar is visible
1449       */
1450      public function getShowVerticalScroll()
1451      {
1452          return $this->showVerticalScroll;
1453      }
1454  
1455      /**
1456       * Set the visibility of the vertical scroll bar in the application.
1457       *
1458       * @param bool $showVerticalScroll True if vertical scroll bar is visible
1459       */
1460      public function setShowVerticalScroll($showVerticalScroll): void
1461      {
1462          $this->showVerticalScroll = (bool) $showVerticalScroll;
1463      }
1464  
1465      /**
1466       * Get the visibility of the sheet tabs in the application.
1467       *
1468       * @return bool True if the sheet tabs are visible
1469       */
1470      public function getShowSheetTabs()
1471      {
1472          return $this->showSheetTabs;
1473      }
1474  
1475      /**
1476       * Set the visibility of the sheet tabs  in the application.
1477       *
1478       * @param bool $showSheetTabs True if sheet tabs are visible
1479       */
1480      public function setShowSheetTabs($showSheetTabs): void
1481      {
1482          $this->showSheetTabs = (bool) $showSheetTabs;
1483      }
1484  
1485      /**
1486       * Return whether the workbook window is minimized.
1487       *
1488       * @return bool true if workbook window is minimized
1489       */
1490      public function getMinimized()
1491      {
1492          return $this->minimized;
1493      }
1494  
1495      /**
1496       * Set whether the workbook window is minimized.
1497       *
1498       * @param bool $minimized true if workbook window is minimized
1499       */
1500      public function setMinimized($minimized): void
1501      {
1502          $this->minimized = (bool) $minimized;
1503      }
1504  
1505      /**
1506       * Return whether to group dates when presenting the user with
1507       * filtering optiomd in the user interface.
1508       *
1509       * @return bool true if workbook window is minimized
1510       */
1511      public function getAutoFilterDateGrouping()
1512      {
1513          return $this->autoFilterDateGrouping;
1514      }
1515  
1516      /**
1517       * Set whether to group dates when presenting the user with
1518       * filtering optiomd in the user interface.
1519       *
1520       * @param bool $autoFilterDateGrouping true if workbook window is minimized
1521       */
1522      public function setAutoFilterDateGrouping($autoFilterDateGrouping): void
1523      {
1524          $this->autoFilterDateGrouping = (bool) $autoFilterDateGrouping;
1525      }
1526  
1527      /**
1528       * Return the first sheet in the book view.
1529       *
1530       * @return int First sheet in book view
1531       */
1532      public function getFirstSheetIndex()
1533      {
1534          return $this->firstSheetIndex;
1535      }
1536  
1537      /**
1538       * Set the first sheet in the book view.
1539       *
1540       * @param int $firstSheetIndex First sheet in book view
1541       */
1542      public function setFirstSheetIndex($firstSheetIndex): void
1543      {
1544          if ($firstSheetIndex >= 0) {
1545              $this->firstSheetIndex = (int) $firstSheetIndex;
1546          } else {
1547              throw new Exception('First sheet index must be a positive integer.');
1548          }
1549      }
1550  
1551      /**
1552       * Return the visibility status of the workbook.
1553       *
1554       * This may be one of the following three values:
1555       * - visibile
1556       *
1557       * @return string Visible status
1558       */
1559      public function getVisibility()
1560      {
1561          return $this->visibility;
1562      }
1563  
1564      /**
1565       * Set the visibility status of the workbook.
1566       *
1567       * Valid values are:
1568       *  - 'visible' (self::VISIBILITY_VISIBLE):
1569       *       Workbook window is visible
1570       *  - 'hidden' (self::VISIBILITY_HIDDEN):
1571       *       Workbook window is hidden, but can be shown by the user
1572       *       via the user interface
1573       *  - 'veryHidden' (self::VISIBILITY_VERY_HIDDEN):
1574       *       Workbook window is hidden and cannot be shown in the
1575       *       user interface.
1576       *
1577       * @param null|string $visibility visibility status of the workbook
1578       */
1579      public function setVisibility($visibility): void
1580      {
1581          if ($visibility === null) {
1582              $visibility = self::VISIBILITY_VISIBLE;
1583          }
1584  
1585          if (in_array($visibility, self::$workbookViewVisibilityValues)) {
1586              $this->visibility = $visibility;
1587          } else {
1588              throw new Exception('Invalid visibility value.');
1589          }
1590      }
1591  
1592      /**
1593       * Get the ratio between the workbook tabs bar and the horizontal scroll bar.
1594       * TabRatio is assumed to be out of 1000 of the horizontal window width.
1595       *
1596       * @return int Ratio between the workbook tabs bar and the horizontal scroll bar
1597       */
1598      public function getTabRatio()
1599      {
1600          return $this->tabRatio;
1601      }
1602  
1603      /**
1604       * Set the ratio between the workbook tabs bar and the horizontal scroll bar
1605       * TabRatio is assumed to be out of 1000 of the horizontal window width.
1606       *
1607       * @param int $tabRatio Ratio between the tabs bar and the horizontal scroll bar
1608       */
1609      public function setTabRatio($tabRatio): void
1610      {
1611          if ($tabRatio >= 0 && $tabRatio <= 1000) {
1612              $this->tabRatio = (int) $tabRatio;
1613          } else {
1614              throw new Exception('Tab ratio must be between 0 and 1000.');
1615          }
1616      }
1617  
1618      public function reevaluateAutoFilters(bool $resetToMax): void
1619      {
1620          foreach ($this->workSheetCollection as $sheet) {
1621              $filter = $sheet->getAutoFilter();
1622              if (!empty($filter->getRange())) {
1623                  if ($resetToMax) {
1624                      $filter->setRangeToMaxRow();
1625                  }
1626                  $filter->showHideRows();
1627              }
1628          }
1629      }
1630  
1631      /**
1632       * Silliness to mollify Scrutinizer.
1633       *
1634       * @codeCoverageIgnore
1635       */
1636      public function getSharedComponent(): Style
1637      {
1638          return new Style();
1639      }
1640  }