Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]
1 <?php 2 3 namespace PhpOffice\PhpSpreadsheet\Style; 4 5 use PhpOffice\PhpSpreadsheet\Cell\Coordinate; 6 use PhpOffice\PhpSpreadsheet\Spreadsheet; 7 8 class Style extends Supervisor 9 { 10 /** 11 * Font. 12 * 13 * @var Font 14 */ 15 protected $font; 16 17 /** 18 * Fill. 19 * 20 * @var Fill 21 */ 22 protected $fill; 23 24 /** 25 * Borders. 26 * 27 * @var Borders 28 */ 29 protected $borders; 30 31 /** 32 * Alignment. 33 * 34 * @var Alignment 35 */ 36 protected $alignment; 37 38 /** 39 * Number Format. 40 * 41 * @var NumberFormat 42 */ 43 protected $numberFormat; 44 45 /** 46 * Conditional styles. 47 * 48 * @var Conditional[] 49 */ 50 protected $conditionalStyles; 51 52 /** 53 * Protection. 54 * 55 * @var Protection 56 */ 57 protected $protection; 58 59 /** 60 * Index of style in collection. Only used for real style. 61 * 62 * @var int 63 */ 64 protected $index; 65 66 /** 67 * Use Quote Prefix when displaying in cell editor. Only used for real style. 68 * 69 * @var bool 70 */ 71 protected $quotePrefix = false; 72 73 /** 74 * Create a new Style. 75 * 76 * @param bool $isSupervisor Flag indicating if this is a supervisor or not 77 * Leave this value at default unless you understand exactly what 78 * its ramifications are 79 * @param bool $isConditional Flag indicating if this is a conditional style or not 80 * Leave this value at default unless you understand exactly what 81 * its ramifications are 82 */ 83 public function __construct($isSupervisor = false, $isConditional = false) 84 { 85 parent::__construct($isSupervisor); 86 87 // Initialise values 88 $this->conditionalStyles = []; 89 $this->font = new Font($isSupervisor, $isConditional); 90 $this->fill = new Fill($isSupervisor, $isConditional); 91 $this->borders = new Borders($isSupervisor, $isConditional); 92 $this->alignment = new Alignment($isSupervisor, $isConditional); 93 $this->numberFormat = new NumberFormat($isSupervisor, $isConditional); 94 $this->protection = new Protection($isSupervisor, $isConditional); 95 96 // bind parent if we are a supervisor 97 if ($isSupervisor) { 98 $this->font->bindParent($this); 99 $this->fill->bindParent($this); 100 $this->borders->bindParent($this); 101 $this->alignment->bindParent($this); 102 $this->numberFormat->bindParent($this); 103 $this->protection->bindParent($this); 104 } 105 } 106 107 /** 108 * Get the shared style component for the currently active cell in currently active sheet. 109 * Only used for style supervisor. 110 * 111 * @return Style 112 */ 113 public function getSharedComponent() 114 { 115 $activeSheet = $this->getActiveSheet(); 116 $selectedCell = $this->getActiveCell(); // e.g. 'A1' 117 118 if ($activeSheet->cellExists($selectedCell)) { 119 $xfIndex = $activeSheet->getCell($selectedCell)->getXfIndex(); 120 } else { 121 $xfIndex = 0; 122 } 123 124 return $this->parent->getCellXfByIndex($xfIndex); 125 } 126 127 /** 128 * Get parent. Only used for style supervisor. 129 * 130 * @return Spreadsheet 131 */ 132 public function getParent() 133 { 134 return $this->parent; 135 } 136 137 /** 138 * Build style array from subcomponents. 139 * 140 * @param array $array 141 * 142 * @return array 143 */ 144 public function getStyleArray($array) 145 { 146 return ['quotePrefix' => $array]; 147 } 148 149 /** 150 * Apply styles from array. 151 * 152 * <code> 153 * $spreadsheet->getActiveSheet()->getStyle('B2')->applyFromArray( 154 * [ 155 * 'font' => [ 156 * 'name' => 'Arial', 157 * 'bold' => true, 158 * 'italic' => false, 159 * 'underline' => Font::UNDERLINE_DOUBLE, 160 * 'strikethrough' => false, 161 * 'color' => [ 162 * 'rgb' => '808080' 163 * ] 164 * ], 165 * 'borders' => [ 166 * 'bottom' => [ 167 * 'borderStyle' => Border::BORDER_DASHDOT, 168 * 'color' => [ 169 * 'rgb' => '808080' 170 * ] 171 * ], 172 * 'top' => [ 173 * 'borderStyle' => Border::BORDER_DASHDOT, 174 * 'color' => [ 175 * 'rgb' => '808080' 176 * ] 177 * ] 178 * ], 179 * 'alignment' => [ 180 * 'horizontal' => Alignment::HORIZONTAL_CENTER, 181 * 'vertical' => Alignment::VERTICAL_CENTER, 182 * 'wrapText' => true, 183 * ], 184 * 'quotePrefix' => true 185 * ] 186 * ); 187 * </code> 188 * 189 * @param array $pStyles Array containing style information 190 * @param bool $pAdvanced advanced mode for setting borders 191 * 192 * @return Style 193 */ 194 public function applyFromArray(array $pStyles, $pAdvanced = true) 195 { 196 if ($this->isSupervisor) { 197 $pRange = $this->getSelectedCells(); 198 199 // Uppercase coordinate 200 $pRange = strtoupper($pRange); 201 202 // Is it a cell range or a single cell? 203 if (strpos($pRange, ':') === false) { 204 $rangeA = $pRange; 205 $rangeB = $pRange; 206 } else { 207 [$rangeA, $rangeB] = explode(':', $pRange); 208 } 209 210 // Calculate range outer borders 211 $rangeStart = Coordinate::coordinateFromString($rangeA); 212 $rangeEnd = Coordinate::coordinateFromString($rangeB); 213 214 // Translate column into index 215 $rangeStart[0] = Coordinate::columnIndexFromString($rangeStart[0]); 216 $rangeEnd[0] = Coordinate::columnIndexFromString($rangeEnd[0]); 217 218 // Make sure we can loop upwards on rows and columns 219 if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) { 220 $tmp = $rangeStart; 221 $rangeStart = $rangeEnd; 222 $rangeEnd = $tmp; 223 } 224 225 // ADVANCED MODE: 226 if ($pAdvanced && isset($pStyles['borders'])) { 227 // 'allBorders' is a shorthand property for 'outline' and 'inside' and 228 // it applies to components that have not been set explicitly 229 if (isset($pStyles['borders']['allBorders'])) { 230 foreach (['outline', 'inside'] as $component) { 231 if (!isset($pStyles['borders'][$component])) { 232 $pStyles['borders'][$component] = $pStyles['borders']['allBorders']; 233 } 234 } 235 unset($pStyles['borders']['allBorders']); // not needed any more 236 } 237 // 'outline' is a shorthand property for 'top', 'right', 'bottom', 'left' 238 // it applies to components that have not been set explicitly 239 if (isset($pStyles['borders']['outline'])) { 240 foreach (['top', 'right', 'bottom', 'left'] as $component) { 241 if (!isset($pStyles['borders'][$component])) { 242 $pStyles['borders'][$component] = $pStyles['borders']['outline']; 243 } 244 } 245 unset($pStyles['borders']['outline']); // not needed any more 246 } 247 // 'inside' is a shorthand property for 'vertical' and 'horizontal' 248 // it applies to components that have not been set explicitly 249 if (isset($pStyles['borders']['inside'])) { 250 foreach (['vertical', 'horizontal'] as $component) { 251 if (!isset($pStyles['borders'][$component])) { 252 $pStyles['borders'][$component] = $pStyles['borders']['inside']; 253 } 254 } 255 unset($pStyles['borders']['inside']); // not needed any more 256 } 257 // width and height characteristics of selection, 1, 2, or 3 (for 3 or more) 258 $xMax = min($rangeEnd[0] - $rangeStart[0] + 1, 3); 259 $yMax = min($rangeEnd[1] - $rangeStart[1] + 1, 3); 260 261 // loop through up to 3 x 3 = 9 regions 262 for ($x = 1; $x <= $xMax; ++$x) { 263 // start column index for region 264 $colStart = ($x == 3) ? 265 Coordinate::stringFromColumnIndex($rangeEnd[0]) 266 : Coordinate::stringFromColumnIndex($rangeStart[0] + $x - 1); 267 // end column index for region 268 $colEnd = ($x == 1) ? 269 Coordinate::stringFromColumnIndex($rangeStart[0]) 270 : Coordinate::stringFromColumnIndex($rangeEnd[0] - $xMax + $x); 271 272 for ($y = 1; $y <= $yMax; ++$y) { 273 // which edges are touching the region 274 $edges = []; 275 if ($x == 1) { 276 // are we at left edge 277 $edges[] = 'left'; 278 } 279 if ($x == $xMax) { 280 // are we at right edge 281 $edges[] = 'right'; 282 } 283 if ($y == 1) { 284 // are we at top edge? 285 $edges[] = 'top'; 286 } 287 if ($y == $yMax) { 288 // are we at bottom edge? 289 $edges[] = 'bottom'; 290 } 291 292 // start row index for region 293 $rowStart = ($y == 3) ? 294 $rangeEnd[1] : $rangeStart[1] + $y - 1; 295 296 // end row index for region 297 $rowEnd = ($y == 1) ? 298 $rangeStart[1] : $rangeEnd[1] - $yMax + $y; 299 300 // build range for region 301 $range = $colStart . $rowStart . ':' . $colEnd . $rowEnd; 302 303 // retrieve relevant style array for region 304 $regionStyles = $pStyles; 305 unset($regionStyles['borders']['inside']); 306 307 // what are the inner edges of the region when looking at the selection 308 $innerEdges = array_diff(['top', 'right', 'bottom', 'left'], $edges); 309 310 // inner edges that are not touching the region should take the 'inside' border properties if they have been set 311 foreach ($innerEdges as $innerEdge) { 312 switch ($innerEdge) { 313 case 'top': 314 case 'bottom': 315 // should pick up 'horizontal' border property if set 316 if (isset($pStyles['borders']['horizontal'])) { 317 $regionStyles['borders'][$innerEdge] = $pStyles['borders']['horizontal']; 318 } else { 319 unset($regionStyles['borders'][$innerEdge]); 320 } 321 322 break; 323 case 'left': 324 case 'right': 325 // should pick up 'vertical' border property if set 326 if (isset($pStyles['borders']['vertical'])) { 327 $regionStyles['borders'][$innerEdge] = $pStyles['borders']['vertical']; 328 } else { 329 unset($regionStyles['borders'][$innerEdge]); 330 } 331 332 break; 333 } 334 } 335 336 // apply region style to region by calling applyFromArray() in simple mode 337 $this->getActiveSheet()->getStyle($range)->applyFromArray($regionStyles, false); 338 } 339 } 340 341 // restore initial cell selection range 342 $this->getActiveSheet()->getStyle($pRange); 343 344 return $this; 345 } 346 347 // SIMPLE MODE: 348 // Selection type, inspect 349 if (preg_match('/^[A-Z]+1:[A-Z]+1048576$/', $pRange)) { 350 $selectionType = 'COLUMN'; 351 } elseif (preg_match('/^A\d+:XFD\d+$/', $pRange)) { 352 $selectionType = 'ROW'; 353 } else { 354 $selectionType = 'CELL'; 355 } 356 357 // First loop through columns, rows, or cells to find out which styles are affected by this operation 358 switch ($selectionType) { 359 case 'COLUMN': 360 $oldXfIndexes = []; 361 for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { 362 $oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true; 363 } 364 365 break; 366 case 'ROW': 367 $oldXfIndexes = []; 368 for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { 369 if ($this->getActiveSheet()->getRowDimension($row)->getXfIndex() == null) { 370 $oldXfIndexes[0] = true; // row without explicit style should be formatted based on default style 371 } else { 372 $oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true; 373 } 374 } 375 376 break; 377 case 'CELL': 378 $oldXfIndexes = []; 379 for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { 380 for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { 381 $oldXfIndexes[$this->getActiveSheet()->getCellByColumnAndRow($col, $row)->getXfIndex()] = true; 382 } 383 } 384 385 break; 386 } 387 388 // clone each of the affected styles, apply the style array, and add the new styles to the workbook 389 $workbook = $this->getActiveSheet()->getParent(); 390 foreach ($oldXfIndexes as $oldXfIndex => $dummy) { 391 $style = $workbook->getCellXfByIndex($oldXfIndex); 392 $newStyle = clone $style; 393 $newStyle->applyFromArray($pStyles); 394 395 if ($existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode())) { 396 // there is already such cell Xf in our collection 397 $newXfIndexes[$oldXfIndex] = $existingStyle->getIndex(); 398 } else { 399 // we don't have such a cell Xf, need to add 400 $workbook->addCellXf($newStyle); 401 $newXfIndexes[$oldXfIndex] = $newStyle->getIndex(); 402 } 403 } 404 405 // Loop through columns, rows, or cells again and update the XF index 406 switch ($selectionType) { 407 case 'COLUMN': 408 for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { 409 $columnDimension = $this->getActiveSheet()->getColumnDimensionByColumn($col); 410 $oldXfIndex = $columnDimension->getXfIndex(); 411 $columnDimension->setXfIndex($newXfIndexes[$oldXfIndex]); 412 } 413 414 break; 415 case 'ROW': 416 for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { 417 $rowDimension = $this->getActiveSheet()->getRowDimension($row); 418 $oldXfIndex = $rowDimension->getXfIndex() === null ? 419 0 : $rowDimension->getXfIndex(); // row without explicit style should be formatted based on default style 420 $rowDimension->setXfIndex($newXfIndexes[$oldXfIndex]); 421 } 422 423 break; 424 case 'CELL': 425 for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { 426 for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { 427 $cell = $this->getActiveSheet()->getCellByColumnAndRow($col, $row); 428 $oldXfIndex = $cell->getXfIndex(); 429 $cell->setXfIndex($newXfIndexes[$oldXfIndex]); 430 } 431 } 432 433 break; 434 } 435 } else { 436 // not a supervisor, just apply the style array directly on style object 437 if (isset($pStyles['fill'])) { 438 $this->getFill()->applyFromArray($pStyles['fill']); 439 } 440 if (isset($pStyles['font'])) { 441 $this->getFont()->applyFromArray($pStyles['font']); 442 } 443 if (isset($pStyles['borders'])) { 444 $this->getBorders()->applyFromArray($pStyles['borders']); 445 } 446 if (isset($pStyles['alignment'])) { 447 $this->getAlignment()->applyFromArray($pStyles['alignment']); 448 } 449 if (isset($pStyles['numberFormat'])) { 450 $this->getNumberFormat()->applyFromArray($pStyles['numberFormat']); 451 } 452 if (isset($pStyles['protection'])) { 453 $this->getProtection()->applyFromArray($pStyles['protection']); 454 } 455 if (isset($pStyles['quotePrefix'])) { 456 $this->quotePrefix = $pStyles['quotePrefix']; 457 } 458 } 459 460 return $this; 461 } 462 463 /** 464 * Get Fill. 465 * 466 * @return Fill 467 */ 468 public function getFill() 469 { 470 return $this->fill; 471 } 472 473 /** 474 * Get Font. 475 * 476 * @return Font 477 */ 478 public function getFont() 479 { 480 return $this->font; 481 } 482 483 /** 484 * Set font. 485 * 486 * @param Font $font 487 * 488 * @return Style 489 */ 490 public function setFont(Font $font) 491 { 492 $this->font = $font; 493 494 return $this; 495 } 496 497 /** 498 * Get Borders. 499 * 500 * @return Borders 501 */ 502 public function getBorders() 503 { 504 return $this->borders; 505 } 506 507 /** 508 * Get Alignment. 509 * 510 * @return Alignment 511 */ 512 public function getAlignment() 513 { 514 return $this->alignment; 515 } 516 517 /** 518 * Get Number Format. 519 * 520 * @return NumberFormat 521 */ 522 public function getNumberFormat() 523 { 524 return $this->numberFormat; 525 } 526 527 /** 528 * Get Conditional Styles. Only used on supervisor. 529 * 530 * @return Conditional[] 531 */ 532 public function getConditionalStyles() 533 { 534 return $this->getActiveSheet()->getConditionalStyles($this->getActiveCell()); 535 } 536 537 /** 538 * Set Conditional Styles. Only used on supervisor. 539 * 540 * @param Conditional[] $pValue Array of conditional styles 541 * 542 * @return Style 543 */ 544 public function setConditionalStyles(array $pValue) 545 { 546 $this->getActiveSheet()->setConditionalStyles($this->getSelectedCells(), $pValue); 547 548 return $this; 549 } 550 551 /** 552 * Get Protection. 553 * 554 * @return Protection 555 */ 556 public function getProtection() 557 { 558 return $this->protection; 559 } 560 561 /** 562 * Get quote prefix. 563 * 564 * @return bool 565 */ 566 public function getQuotePrefix() 567 { 568 if ($this->isSupervisor) { 569 return $this->getSharedComponent()->getQuotePrefix(); 570 } 571 572 return $this->quotePrefix; 573 } 574 575 /** 576 * Set quote prefix. 577 * 578 * @param bool $pValue 579 * 580 * @return Style 581 */ 582 public function setQuotePrefix($pValue) 583 { 584 if ($pValue == '') { 585 $pValue = false; 586 } 587 if ($this->isSupervisor) { 588 $styleArray = ['quotePrefix' => $pValue]; 589 $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); 590 } else { 591 $this->quotePrefix = (bool) $pValue; 592 } 593 594 return $this; 595 } 596 597 /** 598 * Get hash code. 599 * 600 * @return string Hash code 601 */ 602 public function getHashCode() 603 { 604 $hashConditionals = ''; 605 foreach ($this->conditionalStyles as $conditional) { 606 $hashConditionals .= $conditional->getHashCode(); 607 } 608 609 return md5( 610 $this->fill->getHashCode() . 611 $this->font->getHashCode() . 612 $this->borders->getHashCode() . 613 $this->alignment->getHashCode() . 614 $this->numberFormat->getHashCode() . 615 $hashConditionals . 616 $this->protection->getHashCode() . 617 ($this->quotePrefix ? 't' : 'f') . 618 __CLASS__ 619 ); 620 } 621 622 /** 623 * Get own index in style collection. 624 * 625 * @return int 626 */ 627 public function getIndex() 628 { 629 return $this->index; 630 } 631 632 /** 633 * Set own index in style collection. 634 * 635 * @param int $pValue 636 */ 637 public function setIndex($pValue) 638 { 639 $this->index = $pValue; 640 } 641 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body