See Release Notes
Long Term Support Release
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body