Livraison d ela gestion des opérations v0.4.0

This commit is contained in:
d6soft
2025-06-24 13:01:43 +02:00
parent 25c9d5874c
commit 416d648a14
813 changed files with 234012 additions and 73933 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,381 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
class Column
{
const AUTOFILTER_FILTERTYPE_FILTER = 'filters';
const AUTOFILTER_FILTERTYPE_CUSTOMFILTER = 'customFilters';
// Supports no more than 2 rules, with an And/Or join criteria
// if more than 1 rule is defined
const AUTOFILTER_FILTERTYPE_DYNAMICFILTER = 'dynamicFilter';
// Even though the filter rule is constant, the filtered data can vary
// e.g. filtered by date = TODAY
const AUTOFILTER_FILTERTYPE_TOPTENFILTER = 'top10';
/**
* Types of autofilter rules.
*
* @var string[]
*/
private static array $filterTypes = [
// Currently we're not handling
// colorFilter
// extLst
// iconFilter
self::AUTOFILTER_FILTERTYPE_FILTER,
self::AUTOFILTER_FILTERTYPE_CUSTOMFILTER,
self::AUTOFILTER_FILTERTYPE_DYNAMICFILTER,
self::AUTOFILTER_FILTERTYPE_TOPTENFILTER,
];
// Multiple Rule Connections
const AUTOFILTER_COLUMN_JOIN_AND = 'and';
const AUTOFILTER_COLUMN_JOIN_OR = 'or';
/**
* Join options for autofilter rules.
*
* @var string[]
*/
private static array $ruleJoins = [
self::AUTOFILTER_COLUMN_JOIN_AND,
self::AUTOFILTER_COLUMN_JOIN_OR,
];
/**
* Autofilter.
*/
private ?AutoFilter $parent;
/**
* Autofilter Column Index.
*/
private string $columnIndex;
/**
* Autofilter Column Filter Type.
*/
private string $filterType = self::AUTOFILTER_FILTERTYPE_FILTER;
/**
* Autofilter Multiple Rules And/Or.
*/
private string $join = self::AUTOFILTER_COLUMN_JOIN_OR;
/**
* Autofilter Column Rules.
*
* @var Column\Rule[]
*/
private array $ruleset = [];
/**
* Autofilter Column Dynamic Attributes.
*
* @var (float|int|string)[]
*/
private array $attributes = [];
/**
* Create a new Column.
*
* @param string $column Column (e.g. A)
* @param ?AutoFilter $parent Autofilter for this column
*/
public function __construct(string $column, ?AutoFilter $parent = null)
{
$this->columnIndex = $column;
$this->parent = $parent;
}
public function setEvaluatedFalse(): void
{
if ($this->parent !== null) {
$this->parent->setEvaluated(false);
}
}
/**
* Get AutoFilter column index as string eg: 'A'.
*/
public function getColumnIndex(): string
{
return $this->columnIndex;
}
/**
* Set AutoFilter column index as string eg: 'A'.
*
* @param string $column Column (e.g. A)
*
* @return $this
*/
public function setColumnIndex(string $column): static
{
$this->setEvaluatedFalse();
// Uppercase coordinate
$column = strtoupper($column);
if ($this->parent !== null) {
$this->parent->testColumnInRange($column);
}
$this->columnIndex = $column;
return $this;
}
/**
* Get this Column's AutoFilter Parent.
*/
public function getParent(): ?AutoFilter
{
return $this->parent;
}
/**
* Set this Column's AutoFilter Parent.
*
* @return $this
*/
public function setParent(?AutoFilter $parent = null): static
{
$this->setEvaluatedFalse();
$this->parent = $parent;
return $this;
}
/**
* Get AutoFilter Type.
*/
public function getFilterType(): string
{
return $this->filterType;
}
/**
* Set AutoFilter Type.
*
* @return $this
*/
public function setFilterType(string $filterType): static
{
$this->setEvaluatedFalse();
if (!in_array($filterType, self::$filterTypes)) {
throw new PhpSpreadsheetException('Invalid filter type for column AutoFilter.');
}
if ($filterType === self::AUTOFILTER_FILTERTYPE_CUSTOMFILTER && count($this->ruleset) > 2) {
throw new PhpSpreadsheetException('No more than 2 rules are allowed in a Custom Filter');
}
$this->filterType = $filterType;
return $this;
}
/**
* Get AutoFilter Multiple Rules And/Or Join.
*/
public function getJoin(): string
{
return $this->join;
}
/**
* Set AutoFilter Multiple Rules And/Or.
*
* @param string $join And/Or
*
* @return $this
*/
public function setJoin(string $join): static
{
$this->setEvaluatedFalse();
// Lowercase And/Or
$join = strtolower($join);
if (!in_array($join, self::$ruleJoins)) {
throw new PhpSpreadsheetException('Invalid rule connection for column AutoFilter.');
}
$this->join = $join;
return $this;
}
/**
* Set AutoFilter Attributes.
*
* @param (float|int|string)[] $attributes
*
* @return $this
*/
public function setAttributes(array $attributes): static
{
$this->setEvaluatedFalse();
$this->attributes = $attributes;
return $this;
}
/**
* Set An AutoFilter Attribute.
*
* @param string $name Attribute Name
* @param float|int|string $value Attribute Value
*
* @return $this
*/
public function setAttribute(string $name, $value): static
{
$this->setEvaluatedFalse();
$this->attributes[$name] = $value;
return $this;
}
/**
* Get AutoFilter Column Attributes.
*
* @return (float|int|string)[]
*/
public function getAttributes(): array
{
return $this->attributes;
}
/**
* Get specific AutoFilter Column Attribute.
*
* @param string $name Attribute Name
*/
public function getAttribute(string $name): null|float|int|string
{
if (isset($this->attributes[$name])) {
return $this->attributes[$name];
}
return null;
}
public function ruleCount(): int
{
return count($this->ruleset);
}
/**
* Get all AutoFilter Column Rules.
*
* @return Column\Rule[]
*/
public function getRules(): array
{
return $this->ruleset;
}
/**
* Get a specified AutoFilter Column Rule.
*
* @param int $index Rule index in the ruleset array
*/
public function getRule(int $index): Column\Rule
{
if (!isset($this->ruleset[$index])) {
$this->ruleset[$index] = new Column\Rule($this);
}
return $this->ruleset[$index];
}
/**
* Create a new AutoFilter Column Rule in the ruleset.
*/
public function createRule(): Column\Rule
{
$this->setEvaluatedFalse();
if ($this->filterType === self::AUTOFILTER_FILTERTYPE_CUSTOMFILTER && count($this->ruleset) >= 2) {
throw new PhpSpreadsheetException('No more than 2 rules are allowed in a Custom Filter');
}
$this->ruleset[] = new Column\Rule($this);
return end($this->ruleset);
}
/**
* Add a new AutoFilter Column Rule to the ruleset.
*
* @return $this
*/
public function addRule(Column\Rule $rule): static
{
$this->setEvaluatedFalse();
$rule->setParent($this);
$this->ruleset[] = $rule;
return $this;
}
/**
* Delete a specified AutoFilter Column Rule
* If the number of rules is reduced to 1, then we reset And/Or logic to Or.
*
* @param int $index Rule index in the ruleset array
*
* @return $this
*/
public function deleteRule(int $index): static
{
$this->setEvaluatedFalse();
if (isset($this->ruleset[$index])) {
unset($this->ruleset[$index]);
// If we've just deleted down to a single rule, then reset And/Or joining to Or
if (count($this->ruleset) <= 1) {
$this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR);
}
}
return $this;
}
/**
* Delete all AutoFilter Column Rules.
*
* @return $this
*/
public function clearRules(): static
{
$this->setEvaluatedFalse();
$this->ruleset = [];
$this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR);
return $this;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
/** @var AutoFilter\Column\Rule[] $value */
foreach ($vars as $key => $value) {
if ($key === 'parent') {
// Detach from autofilter parent
$this->parent = null;
} elseif ($key === 'ruleset') {
// The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\AutoFilter objects
$this->ruleset = [];
foreach ($value as $k => $v) {
$cloned = clone $v;
$cloned->setParent($this); // attach the new cloned Rule to this new cloned Autofilter Cloned object
$this->ruleset[$k] = $cloned;
}
} else {
$this->$key = $value;
}
}
}
}

View File

@@ -0,0 +1,406 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column;
class Rule
{
const AUTOFILTER_RULETYPE_FILTER = 'filter';
const AUTOFILTER_RULETYPE_DATEGROUP = 'dateGroupItem';
const AUTOFILTER_RULETYPE_CUSTOMFILTER = 'customFilter';
const AUTOFILTER_RULETYPE_DYNAMICFILTER = 'dynamicFilter';
const AUTOFILTER_RULETYPE_TOPTENFILTER = 'top10Filter';
private const RULE_TYPES = [
// Currently we're not handling
// colorFilter
// extLst
// iconFilter
self::AUTOFILTER_RULETYPE_FILTER,
self::AUTOFILTER_RULETYPE_DATEGROUP,
self::AUTOFILTER_RULETYPE_CUSTOMFILTER,
self::AUTOFILTER_RULETYPE_DYNAMICFILTER,
self::AUTOFILTER_RULETYPE_TOPTENFILTER,
];
const AUTOFILTER_RULETYPE_DATEGROUP_YEAR = 'year';
const AUTOFILTER_RULETYPE_DATEGROUP_MONTH = 'month';
const AUTOFILTER_RULETYPE_DATEGROUP_DAY = 'day';
const AUTOFILTER_RULETYPE_DATEGROUP_HOUR = 'hour';
const AUTOFILTER_RULETYPE_DATEGROUP_MINUTE = 'minute';
const AUTOFILTER_RULETYPE_DATEGROUP_SECOND = 'second';
private const DATE_TIME_GROUPS = [
self::AUTOFILTER_RULETYPE_DATEGROUP_YEAR,
self::AUTOFILTER_RULETYPE_DATEGROUP_MONTH,
self::AUTOFILTER_RULETYPE_DATEGROUP_DAY,
self::AUTOFILTER_RULETYPE_DATEGROUP_HOUR,
self::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE,
self::AUTOFILTER_RULETYPE_DATEGROUP_SECOND,
];
const AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY = 'yesterday';
const AUTOFILTER_RULETYPE_DYNAMIC_TODAY = 'today';
const AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW = 'tomorrow';
const AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE = 'yearToDate';
const AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR = 'thisYear';
const AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER = 'thisQuarter';
const AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH = 'thisMonth';
const AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK = 'thisWeek';
const AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR = 'lastYear';
const AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER = 'lastQuarter';
const AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH = 'lastMonth';
const AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK = 'lastWeek';
const AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR = 'nextYear';
const AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER = 'nextQuarter';
const AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH = 'nextMonth';
const AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK = 'nextWeek';
const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_1 = 'M1';
const AUTOFILTER_RULETYPE_DYNAMIC_JANUARY = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_1;
const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_2 = 'M2';
const AUTOFILTER_RULETYPE_DYNAMIC_FEBRUARY = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_2;
const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_3 = 'M3';
const AUTOFILTER_RULETYPE_DYNAMIC_MARCH = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_3;
const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_4 = 'M4';
const AUTOFILTER_RULETYPE_DYNAMIC_APRIL = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_4;
const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_5 = 'M5';
const AUTOFILTER_RULETYPE_DYNAMIC_MAY = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_5;
const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_6 = 'M6';
const AUTOFILTER_RULETYPE_DYNAMIC_JUNE = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_6;
const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_7 = 'M7';
const AUTOFILTER_RULETYPE_DYNAMIC_JULY = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_7;
const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_8 = 'M8';
const AUTOFILTER_RULETYPE_DYNAMIC_AUGUST = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_8;
const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_9 = 'M9';
const AUTOFILTER_RULETYPE_DYNAMIC_SEPTEMBER = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_9;
const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_10 = 'M10';
const AUTOFILTER_RULETYPE_DYNAMIC_OCTOBER = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_10;
const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_11 = 'M11';
const AUTOFILTER_RULETYPE_DYNAMIC_NOVEMBER = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_11;
const AUTOFILTER_RULETYPE_DYNAMIC_MONTH_12 = 'M12';
const AUTOFILTER_RULETYPE_DYNAMIC_DECEMBER = self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_12;
const AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_1 = 'Q1';
const AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_2 = 'Q2';
const AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_3 = 'Q3';
const AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_4 = 'Q4';
const AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE = 'aboveAverage';
const AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE = 'belowAverage';
private const DYNAMIC_TYPES = [
self::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY,
self::AUTOFILTER_RULETYPE_DYNAMIC_TODAY,
self::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW,
self::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE,
self::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR,
self::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER,
self::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH,
self::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK,
self::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR,
self::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER,
self::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH,
self::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK,
self::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR,
self::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER,
self::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH,
self::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK,
self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_1,
self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_2,
self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_3,
self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_4,
self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_5,
self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_6,
self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_7,
self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_8,
self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_9,
self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_10,
self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_11,
self::AUTOFILTER_RULETYPE_DYNAMIC_MONTH_12,
self::AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_1,
self::AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_2,
self::AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_3,
self::AUTOFILTER_RULETYPE_DYNAMIC_QUARTER_4,
self::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE,
self::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE,
];
// Filter rule operators for filter and customFilter types.
const AUTOFILTER_COLUMN_RULE_EQUAL = 'equal';
const AUTOFILTER_COLUMN_RULE_NOTEQUAL = 'notEqual';
const AUTOFILTER_COLUMN_RULE_GREATERTHAN = 'greaterThan';
const AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL = 'greaterThanOrEqual';
const AUTOFILTER_COLUMN_RULE_LESSTHAN = 'lessThan';
const AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL = 'lessThanOrEqual';
private const OPERATORS = [
self::AUTOFILTER_COLUMN_RULE_EQUAL,
self::AUTOFILTER_COLUMN_RULE_NOTEQUAL,
self::AUTOFILTER_COLUMN_RULE_GREATERTHAN,
self::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL,
self::AUTOFILTER_COLUMN_RULE_LESSTHAN,
self::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL,
];
const AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE = 'byValue';
const AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT = 'byPercent';
private const TOP_TEN_VALUE = [
self::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE,
self::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT,
];
const AUTOFILTER_COLUMN_RULE_TOPTEN_TOP = 'top';
const AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM = 'bottom';
private const TOP_TEN_TYPE = [
self::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP,
self::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM,
];
// Unimplented Rule Operators (Numeric, Boolean etc)
// const AUTOFILTER_COLUMN_RULE_BETWEEN = 'between'; // greaterThanOrEqual 1 && lessThanOrEqual 2
// Rule Operators (Numeric Special) which are translated to standard numeric operators with calculated values
// Rule Operators (String) which are set as wild-carded values
// const AUTOFILTER_COLUMN_RULE_BEGINSWITH = 'beginsWith'; // A*
// const AUTOFILTER_COLUMN_RULE_ENDSWITH = 'endsWith'; // *Z
// const AUTOFILTER_COLUMN_RULE_CONTAINS = 'contains'; // *B*
// const AUTOFILTER_COLUMN_RULE_DOESNTCONTAIN = 'notEqual'; // notEqual *B*
// Rule Operators (Date Special) which are translated to standard numeric operators with calculated values
// const AUTOFILTER_COLUMN_RULE_BEFORE = 'lessThan';
// const AUTOFILTER_COLUMN_RULE_AFTER = 'greaterThan';
/**
* Autofilter Column.
*/
private ?Column $parent;
/**
* Autofilter Rule Type.
*/
private string $ruleType = self::AUTOFILTER_RULETYPE_FILTER;
/**
* Autofilter Rule Value.
*
* @var int|int[]|string|string[]
*/
private $value = '';
/**
* Autofilter Rule Operator.
*/
private string $operator = self::AUTOFILTER_COLUMN_RULE_EQUAL;
/**
* DateTimeGrouping Group Value.
*/
private string $grouping = '';
/**
* Create a new Rule.
*/
public function __construct(?Column $parent = null)
{
$this->parent = $parent;
}
private function setEvaluatedFalse(): void
{
if ($this->parent !== null) {
$this->parent->setEvaluatedFalse();
}
}
/**
* Get AutoFilter Rule Type.
*/
public function getRuleType(): string
{
return $this->ruleType;
}
/**
* Set AutoFilter Rule Type.
*
* @param string $ruleType see self::AUTOFILTER_RULETYPE_*
*
* @return $this
*/
public function setRuleType(string $ruleType): static
{
$this->setEvaluatedFalse();
if (!in_array($ruleType, self::RULE_TYPES)) {
throw new PhpSpreadsheetException('Invalid rule type for column AutoFilter Rule.');
}
$this->ruleType = $ruleType;
return $this;
}
/**
* Get AutoFilter Rule Value.
*
* @return int|int[]|string|string[]
*/
public function getValue()
{
return $this->value;
}
/**
* Set AutoFilter Rule Value.
*
* @param int|int[]|string|string[] $value
*
* @return $this
*/
public function setValue($value): static
{
$this->setEvaluatedFalse();
if (is_array($value)) {
$grouping = -1;
foreach ($value as $key => $v) {
// Validate array entries
if (!in_array($key, self::DATE_TIME_GROUPS)) {
// Remove any invalid entries from the value array
unset($value[$key]);
} else {
// Work out what the dateTime grouping will be
$grouping = max($grouping, array_search($key, self::DATE_TIME_GROUPS));
}
}
if (count($value) == 0) {
throw new PhpSpreadsheetException('Invalid rule value for column AutoFilter Rule.');
}
// Set the dateTime grouping that we've anticipated
$this->setGrouping(self::DATE_TIME_GROUPS[$grouping]);
}
$this->value = $value;
return $this;
}
/**
* Get AutoFilter Rule Operator.
*/
public function getOperator(): string
{
return $this->operator;
}
/**
* Set AutoFilter Rule Operator.
*
* @param string $operator see self::AUTOFILTER_COLUMN_RULE_*
*
* @return $this
*/
public function setOperator(string $operator): static
{
$this->setEvaluatedFalse();
if (empty($operator)) {
$operator = self::AUTOFILTER_COLUMN_RULE_EQUAL;
}
if (
(!in_array($operator, self::OPERATORS))
&& (!in_array($operator, self::TOP_TEN_VALUE))
) {
throw new PhpSpreadsheetException('Invalid operator for column AutoFilter Rule.');
}
$this->operator = $operator;
return $this;
}
/**
* Get AutoFilter Rule Grouping.
*/
public function getGrouping(): string
{
return $this->grouping;
}
/**
* Set AutoFilter Rule Grouping.
*
* @return $this
*/
public function setGrouping(string $grouping): static
{
$this->setEvaluatedFalse();
if (
($grouping !== null)
&& (!in_array($grouping, self::DATE_TIME_GROUPS))
&& (!in_array($grouping, self::DYNAMIC_TYPES))
&& (!in_array($grouping, self::TOP_TEN_TYPE))
) {
throw new PhpSpreadsheetException('Invalid grouping for column AutoFilter Rule.');
}
$this->grouping = $grouping;
return $this;
}
/**
* Set AutoFilter Rule.
*
* @param string $operator see self::AUTOFILTER_COLUMN_RULE_*
* @param int|int[]|string|string[] $value
*
* @return $this
*/
public function setRule(string $operator, $value, ?string $grouping = null): static
{
$this->setEvaluatedFalse();
$this->setOperator($operator);
$this->setValue($value);
// Only set grouping if it's been passed in as a user-supplied argument,
// otherwise we're calculating it when we setValue() and don't want to overwrite that
// If the user supplies an argumnet for grouping, then on their own head be it
if ($grouping !== null) {
$this->setGrouping($grouping);
}
return $this;
}
/**
* Get this Rule's AutoFilter Column Parent.
*/
public function getParent(): ?Column
{
return $this->parent;
}
/**
* Set this Rule's AutoFilter Column Parent.
*
* @return $this
*/
public function setParent(?Column $parent = null): static
{
$this->setEvaluatedFalse();
$this->parent = $parent;
return $this;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
if (is_object($value)) {
if ($key == 'parent') { // this is only object
// Detach from autofilter column parent
$this->$key = null;
}
} else {
$this->$key = $value;
}
}
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Cell\CellAddress;
use PhpOffice\PhpSpreadsheet\Cell\CellRange;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
class AutoFit
{
protected Worksheet $worksheet;
public function __construct(Worksheet $worksheet)
{
$this->worksheet = $worksheet;
}
public function getAutoFilterIndentRanges(): array
{
$autoFilterIndentRanges = [];
$autoFilterIndentRanges[] = $this->getAutoFilterIndentRange($this->worksheet->getAutoFilter());
foreach ($this->worksheet->getTableCollection() as $table) {
/** @var Table $table */
if ($table->getShowHeaderRow() === true && $table->getAllowFilter() === true) {
$autoFilter = $table->getAutoFilter();
if ($autoFilter !== null) {
$autoFilterIndentRanges[] = $this->getAutoFilterIndentRange($autoFilter);
}
}
}
return array_filter($autoFilterIndentRanges);
}
private function getAutoFilterIndentRange(AutoFilter $autoFilter): ?string
{
$autoFilterRange = $autoFilter->getRange();
$autoFilterIndentRange = null;
if (!empty($autoFilterRange)) {
$autoFilterRangeBoundaries = Coordinate::rangeBoundaries($autoFilterRange);
$autoFilterIndentRange = (string) new CellRange(
CellAddress::fromColumnAndRow($autoFilterRangeBoundaries[0][0], $autoFilterRangeBoundaries[0][1]),
CellAddress::fromColumnAndRow($autoFilterRangeBoundaries[1][0], $autoFilterRangeBoundaries[0][1])
);
}
return $autoFilterIndentRange;
}
}

View File

@@ -0,0 +1,557 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\IComparable;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing\Shadow;
use SimpleXMLElement;
class BaseDrawing implements IComparable
{
const EDIT_AS_ABSOLUTE = 'absolute';
const EDIT_AS_ONECELL = 'oneCell';
const EDIT_AS_TWOCELL = 'twoCell';
private const VALID_EDIT_AS = [
self::EDIT_AS_ABSOLUTE,
self::EDIT_AS_ONECELL,
self::EDIT_AS_TWOCELL,
];
/**
* The editAs attribute, used only with two cell anchor.
*/
protected string $editAs = '';
/**
* Image counter.
*/
private static int $imageCounter = 0;
/**
* Image index.
*/
private int $imageIndex;
/**
* Name.
*/
protected string $name = '';
/**
* Description.
*/
protected string $description = '';
/**
* Worksheet.
*/
protected ?Worksheet $worksheet = null;
/**
* Coordinates.
*/
protected string $coordinates = 'A1';
/**
* Offset X.
*/
protected int $offsetX = 0;
/**
* Offset Y.
*/
protected int $offsetY = 0;
/**
* Coordinates2.
*/
protected string $coordinates2 = '';
/**
* Offset X2.
*/
protected int $offsetX2 = 0;
/**
* Offset Y2.
*/
protected int $offsetY2 = 0;
/**
* Width.
*/
protected int $width = 0;
/**
* Height.
*/
protected int $height = 0;
/**
* Pixel width of image. See $width for the size the Drawing will be in the sheet.
*/
protected int $imageWidth = 0;
/**
* Pixel width of image. See $height for the size the Drawing will be in the sheet.
*/
protected int $imageHeight = 0;
/**
* Proportional resize.
*/
protected bool $resizeProportional = true;
/**
* Rotation.
*/
protected int $rotation = 0;
protected bool $flipVertical = false;
protected bool $flipHorizontal = false;
/**
* Shadow.
*/
protected Shadow $shadow;
/**
* Image hyperlink.
*/
private ?Hyperlink $hyperlink = null;
/**
* Image type.
*/
protected int $type = IMAGETYPE_UNKNOWN;
/** @var null|SimpleXMLElement|string[] */
protected $srcRect = [];
/**
* Create a new BaseDrawing.
*/
public function __construct()
{
// Initialise values
$this->setShadow();
// Set image index
++self::$imageCounter;
$this->imageIndex = self::$imageCounter;
}
public function __destruct()
{
$this->worksheet = null;
}
public function getImageIndex(): int
{
return $this->imageIndex;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getDescription(): string
{
return $this->description;
}
public function setDescription(string $description): self
{
$this->description = $description;
return $this;
}
public function getWorksheet(): ?Worksheet
{
return $this->worksheet;
}
/**
* Set Worksheet.
*
* @param bool $overrideOld If a Worksheet has already been assigned, overwrite it and remove image from old Worksheet?
*/
public function setWorksheet(?Worksheet $worksheet = null, bool $overrideOld = false): self
{
if ($this->worksheet === null) {
// Add drawing to Worksheet
if ($worksheet !== null) {
$this->worksheet = $worksheet;
if (!($this instanceof Drawing && $this->getPath() === '')) {
$this->worksheet->getCell($this->coordinates);
}
$this->worksheet->getDrawingCollection()
->append($this);
}
} else {
if ($overrideOld) {
// Remove drawing from old Worksheet
$iterator = $this->worksheet->getDrawingCollection()->getIterator();
while ($iterator->valid()) {
if ($iterator->current()->getHashCode() === $this->getHashCode()) {
$this->worksheet->getDrawingCollection()->offsetUnset($iterator->key());
$this->worksheet = null;
break;
}
}
// Set new Worksheet
$this->setWorksheet($worksheet);
} else {
throw new PhpSpreadsheetException('A Worksheet has already been assigned. Drawings can only exist on one Worksheet.');
}
}
return $this;
}
public function getCoordinates(): string
{
return $this->coordinates;
}
public function setCoordinates(string $coordinates): self
{
$this->coordinates = $coordinates;
if ($this->worksheet !== null) {
if (!($this instanceof Drawing && $this->getPath() === '')) {
$this->worksheet->getCell($this->coordinates);
}
}
return $this;
}
public function getOffsetX(): int
{
return $this->offsetX;
}
public function setOffsetX(int $offsetX): self
{
$this->offsetX = $offsetX;
return $this;
}
public function getOffsetY(): int
{
return $this->offsetY;
}
public function setOffsetY(int $offsetY): self
{
$this->offsetY = $offsetY;
return $this;
}
public function getCoordinates2(): string
{
return $this->coordinates2;
}
public function setCoordinates2(string $coordinates2): self
{
$this->coordinates2 = $coordinates2;
return $this;
}
public function getOffsetX2(): int
{
return $this->offsetX2;
}
public function setOffsetX2(int $offsetX2): self
{
$this->offsetX2 = $offsetX2;
return $this;
}
public function getOffsetY2(): int
{
return $this->offsetY2;
}
public function setOffsetY2(int $offsetY2): self
{
$this->offsetY2 = $offsetY2;
return $this;
}
public function getWidth(): int
{
return $this->width;
}
public function setWidth(int $width): self
{
// Resize proportional?
if ($this->resizeProportional && $width != 0) {
$ratio = $this->height / ($this->width != 0 ? $this->width : 1);
$this->height = (int) round($ratio * $width);
}
// Set width
$this->width = $width;
return $this;
}
public function getHeight(): int
{
return $this->height;
}
public function setHeight(int $height): self
{
// Resize proportional?
if ($this->resizeProportional && $height != 0) {
$ratio = $this->width / ($this->height != 0 ? $this->height : 1);
$this->width = (int) round($ratio * $height);
}
// Set height
$this->height = $height;
return $this;
}
/**
* Set width and height with proportional resize.
*
* Example:
* <code>
* $objDrawing->setResizeProportional(true);
* $objDrawing->setWidthAndHeight(160,120);
* </code>
*
* @author Vincent@luo MSN:kele_100@hotmail.com
*/
public function setWidthAndHeight(int $width, int $height): self
{
$xratio = $width / ($this->width != 0 ? $this->width : 1);
$yratio = $height / ($this->height != 0 ? $this->height : 1);
if ($this->resizeProportional && !($width == 0 || $height == 0)) {
if (($xratio * $this->height) < $height) {
$this->height = (int) ceil($xratio * $this->height);
$this->width = $width;
} else {
$this->width = (int) ceil($yratio * $this->width);
$this->height = $height;
}
} else {
$this->width = $width;
$this->height = $height;
}
return $this;
}
public function getResizeProportional(): bool
{
return $this->resizeProportional;
}
public function setResizeProportional(bool $resizeProportional): self
{
$this->resizeProportional = $resizeProportional;
return $this;
}
public function getRotation(): int
{
return $this->rotation;
}
public function setRotation(int $rotation): self
{
$this->rotation = $rotation;
return $this;
}
public function getShadow(): Shadow
{
return $this->shadow;
}
public function setShadow(?Shadow $shadow = null): self
{
$this->shadow = $shadow ?? new Shadow();
return $this;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode(): string
{
return md5(
$this->name
. $this->description
. (($this->worksheet === null) ? '' : (string) $this->worksheet->getHashInt())
. $this->coordinates
. $this->offsetX
. $this->offsetY
. $this->coordinates2
. $this->offsetX2
. $this->offsetY2
. $this->width
. $this->height
. $this->rotation
. $this->shadow->getHashCode()
. __CLASS__
);
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
if ($key == 'worksheet') {
$this->worksheet = null;
} elseif (is_object($value)) {
$this->$key = clone $value;
} else {
$this->$key = $value;
}
}
}
public function setHyperlink(?Hyperlink $hyperlink = null): void
{
$this->hyperlink = $hyperlink;
}
public function getHyperlink(): ?Hyperlink
{
return $this->hyperlink;
}
/**
* Set Fact Sizes and Type of Image.
*/
protected function setSizesAndType(string $path): void
{
if ($this->imageWidth === 0 && $this->imageHeight === 0 && $this->type === IMAGETYPE_UNKNOWN) {
$imageData = getimagesize($path);
if (!empty($imageData)) {
$this->imageWidth = $imageData[0];
$this->imageHeight = $imageData[1];
$this->type = $imageData[2];
}
}
if ($this->width === 0 && $this->height === 0) {
$this->width = $this->imageWidth;
$this->height = $this->imageHeight;
}
}
/**
* Get Image Type.
*/
public function getType(): int
{
return $this->type;
}
public function getImageWidth(): int
{
return $this->imageWidth;
}
public function getImageHeight(): int
{
return $this->imageHeight;
}
public function getEditAs(): string
{
return $this->editAs;
}
public function setEditAs(string $editAs): self
{
$this->editAs = $editAs;
return $this;
}
public function validEditAs(): bool
{
return in_array($this->editAs, self::VALID_EDIT_AS, true);
}
/**
* @return null|SimpleXMLElement|string[]
*/
public function getSrcRect()
{
return $this->srcRect;
}
/**
* @param null|SimpleXMLElement|string[] $srcRect
*/
public function setSrcRect($srcRect): self
{
$this->srcRect = $srcRect;
return $this;
}
public function setFlipHorizontal(bool $flipHorizontal): self
{
$this->flipHorizontal = $flipHorizontal;
return $this;
}
public function getFlipHorizontal(): bool
{
return $this->flipHorizontal;
}
public function setFlipVertical(bool $flipVertical): self
{
$this->flipVertical = $flipVertical;
return $this;
}
public function getFlipVertical(): bool
{
return $this->flipVertical;
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use Iterator as NativeIterator;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Collection\Cells;
/**
* @template TKey
*
* @implements NativeIterator<TKey, Cell>
*/
abstract class CellIterator implements NativeIterator
{
public const TREAT_NULL_VALUE_AS_EMPTY_CELL = 1;
public const TREAT_EMPTY_STRING_AS_EMPTY_CELL = 2;
public const IF_NOT_EXISTS_RETURN_NULL = false;
public const IF_NOT_EXISTS_CREATE_NEW = true;
/**
* Worksheet to iterate.
*/
protected Worksheet $worksheet;
/**
* Cell Collection to iterate.
*/
protected Cells $cellCollection;
/**
* Iterate only existing cells.
*/
protected bool $onlyExistingCells = false;
/**
* If iterating all cells, and a cell doesn't exist, identifies whether a new cell should be created,
* or if the iterator should return a null value.
*/
protected bool $ifNotExists = self::IF_NOT_EXISTS_CREATE_NEW;
/**
* Destructor.
*/
public function __destruct()
{
unset($this->worksheet, $this->cellCollection);
}
public function getIfNotExists(): bool
{
return $this->ifNotExists;
}
public function setIfNotExists(bool $ifNotExists = self::IF_NOT_EXISTS_CREATE_NEW): void
{
$this->ifNotExists = $ifNotExists;
}
/**
* Get loop only existing cells.
*/
public function getIterateOnlyExistingCells(): bool
{
return $this->onlyExistingCells;
}
/**
* Validate start/end values for 'IterateOnlyExistingCells' mode, and adjust if necessary.
*/
abstract protected function adjustForExistingOnlyRange(): void;
/**
* Set the iterator to loop only existing cells.
*/
public function setIterateOnlyExistingCells(bool $value): void
{
$this->onlyExistingCells = (bool) $value;
$this->adjustForExistingOnlyRange();
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
class Column
{
private Worksheet $worksheet;
/**
* Column index.
*/
private string $columnIndex;
/**
* Create a new column.
*/
public function __construct(Worksheet $worksheet, string $columnIndex = 'A')
{
// Set parent and column index
$this->worksheet = $worksheet;
$this->columnIndex = $columnIndex;
}
/**
* Destructor.
*/
public function __destruct()
{
unset($this->worksheet);
}
/**
* Get column index as string eg: 'A'.
*/
public function getColumnIndex(): string
{
return $this->columnIndex;
}
/**
* Get cell iterator.
*
* @param int $startRow The row number at which to start iterating
* @param ?int $endRow Optionally, the row number at which to stop iterating
*/
public function getCellIterator(int $startRow = 1, ?int $endRow = null, bool $iterateOnlyExistingCells = false): ColumnCellIterator
{
return new ColumnCellIterator($this->worksheet, $this->columnIndex, $startRow, $endRow, $iterateOnlyExistingCells);
}
/**
* Get row iterator. Synonym for getCellIterator().
*
* @param int $startRow The row number at which to start iterating
* @param ?int $endRow Optionally, the row number at which to stop iterating
*/
public function getRowIterator(int $startRow = 1, ?int $endRow = null, bool $iterateOnlyExistingCells = false): ColumnCellIterator
{
return $this->getCellIterator($startRow, $endRow, $iterateOnlyExistingCells);
}
/**
* Returns a boolean true if the column contains no cells. By default, this means that no cell records exist in the
* collection for this column. false will be returned otherwise.
* This rule can be modified by passing a $definitionOfEmptyFlags value:
* 1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value
* cells, then the column will be considered empty.
* 2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty
* string value cells, then the column will be considered empty.
* 3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL
* If the only cells in the collection are null value or empty string value cells, then the column
* will be considered empty.
*
* @param int $definitionOfEmptyFlags
* Possible Flag Values are:
* CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL
* CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL
* @param int $startRow The row number at which to start checking if cells are empty
* @param ?int $endRow Optionally, the row number at which to stop checking if cells are empty
*/
public function isEmpty(int $definitionOfEmptyFlags = 0, int $startRow = 1, ?int $endRow = null): bool
{
$nullValueCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL);
$emptyStringCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL);
$cellIterator = $this->getCellIterator($startRow, $endRow);
$cellIterator->setIterateOnlyExistingCells(true);
foreach ($cellIterator as $cell) {
$value = $cell->getValue();
if ($value === null && $nullValueCellIsEmpty === true) {
continue;
}
if ($value === '' && $emptyStringCellIsEmpty === true) {
continue;
}
return false;
}
return true;
}
/**
* Returns bound worksheet.
*/
public function getWorksheet(): Worksheet
{
return $this->worksheet;
}
}

View File

@@ -0,0 +1,198 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
/**
* @extends CellIterator<int>
*/
class ColumnCellIterator extends CellIterator
{
/**
* Current iterator position.
*/
private int $currentRow;
/**
* Column index.
*/
private int $columnIndex;
/**
* Start position.
*/
private int $startRow = 1;
/**
* End position.
*/
private int $endRow = 1;
/**
* Create a new row iterator.
*
* @param Worksheet $worksheet The worksheet to iterate over
* @param string $columnIndex The column that we want to iterate
* @param int $startRow The row number at which to start iterating
* @param ?int $endRow Optionally, the row number at which to stop iterating
*/
public function __construct(Worksheet $worksheet, string $columnIndex = 'A', int $startRow = 1, ?int $endRow = null, bool $iterateOnlyExistingCells = false)
{
// Set subject
$this->worksheet = $worksheet;
$this->cellCollection = $worksheet->getCellCollection();
$this->columnIndex = Coordinate::columnIndexFromString($columnIndex);
$this->resetEnd($endRow);
$this->resetStart($startRow);
$this->setIterateOnlyExistingCells($iterateOnlyExistingCells);
}
/**
* (Re)Set the start row and the current row pointer.
*
* @param int $startRow The row number at which to start iterating
*
* @return $this
*/
public function resetStart(int $startRow = 1): static
{
$this->startRow = $startRow;
$this->adjustForExistingOnlyRange();
$this->seek($startRow);
return $this;
}
/**
* (Re)Set the end row.
*
* @param ?int $endRow The row number at which to stop iterating
*
* @return $this
*/
public function resetEnd(?int $endRow = null): static
{
$this->endRow = $endRow ?: $this->worksheet->getHighestRow();
$this->adjustForExistingOnlyRange();
return $this;
}
/**
* Set the row pointer to the selected row.
*
* @param int $row The row number to set the current pointer at
*
* @return $this
*/
public function seek(int $row = 1): static
{
if (
$this->onlyExistingCells
&& (!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->columnIndex) . $row))
) {
throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist');
}
if (($row < $this->startRow) || ($row > $this->endRow)) {
throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})");
}
$this->currentRow = $row;
return $this;
}
/**
* Rewind the iterator to the starting row.
*/
public function rewind(): void
{
$this->currentRow = $this->startRow;
}
/**
* Return the current cell in this worksheet column.
*/
public function current(): ?Cell
{
$cellAddress = Coordinate::stringFromColumnIndex($this->columnIndex) . $this->currentRow;
return $this->cellCollection->has($cellAddress)
? $this->cellCollection->get($cellAddress)
: (
$this->ifNotExists === self::IF_NOT_EXISTS_CREATE_NEW
? $this->worksheet->createNewCell($cellAddress)
: null
);
}
/**
* Return the current iterator key.
*/
public function key(): int
{
return $this->currentRow;
}
/**
* Set the iterator to its next value.
*/
public function next(): void
{
$columnAddress = Coordinate::stringFromColumnIndex($this->columnIndex);
do {
++$this->currentRow;
} while (
($this->onlyExistingCells)
&& ($this->currentRow <= $this->endRow)
&& (!$this->cellCollection->has($columnAddress . $this->currentRow))
);
}
/**
* Set the iterator to its previous value.
*/
public function prev(): void
{
$columnAddress = Coordinate::stringFromColumnIndex($this->columnIndex);
do {
--$this->currentRow;
} while (
($this->onlyExistingCells)
&& ($this->currentRow >= $this->startRow)
&& (!$this->cellCollection->has($columnAddress . $this->currentRow))
);
}
/**
* Indicate if more rows exist in the worksheet range of rows that we're iterating.
*/
public function valid(): bool
{
return $this->currentRow <= $this->endRow && $this->currentRow >= $this->startRow;
}
/**
* Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary.
*/
protected function adjustForExistingOnlyRange(): void
{
if ($this->onlyExistingCells) {
$columnAddress = Coordinate::stringFromColumnIndex($this->columnIndex);
while (
(!$this->cellCollection->has($columnAddress . $this->startRow))
&& ($this->startRow <= $this->endRow)
) {
++$this->startRow;
}
while (
(!$this->cellCollection->has($columnAddress . $this->endRow))
&& ($this->endRow >= $this->startRow)
) {
--$this->endRow;
}
}
}
}

View File

@@ -0,0 +1,131 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension;
class ColumnDimension extends Dimension
{
/**
* Column index.
*/
private ?string $columnIndex;
/**
* Column width.
*
* When this is set to a negative value, the column width should be ignored by IWriter
*/
private float $width = -1;
/**
* Auto size?
*/
private bool $autoSize = false;
/**
* Create a new ColumnDimension.
*
* @param ?string $index Character column index
*/
public function __construct(?string $index = 'A')
{
// Initialise values
$this->columnIndex = $index;
// set dimension as unformatted by default
parent::__construct(0);
}
/**
* Get column index as string eg: 'A'.
*/
public function getColumnIndex(): ?string
{
return $this->columnIndex;
}
/**
* Set column index as string eg: 'A'.
*/
public function setColumnIndex(string $index): self
{
$this->columnIndex = $index;
return $this;
}
/**
* Get column index as numeric.
*/
public function getColumnNumeric(): int
{
return Coordinate::columnIndexFromString($this->columnIndex ?? '');
}
/**
* Set column index as numeric.
*/
public function setColumnNumeric(int $index): self
{
$this->columnIndex = Coordinate::stringFromColumnIndex($index);
return $this;
}
/**
* Get Width.
*
* Each unit of column width is equal to the width of one character in the default font size. A value of -1
* tells Excel to display this column in its default width.
* By default, this will be the return value; but this method also accepts an optional unit of measure argument
* and will convert the returned value to the specified UoM..
*/
public function getWidth(?string $unitOfMeasure = null): float
{
return ($unitOfMeasure === null || $this->width < 0)
? $this->width
: (new CssDimension((string) $this->width))->toUnit($unitOfMeasure);
}
/**
* Set Width.
*
* Each unit of column width is equal to the width of one character in the default font size. A value of -1
* tells Excel to display this column in its default width.
* By default, this will be the unit of measure for the passed value; but this method also accepts an
* optional unit of measure argument, and will convert the value from the specified UoM using an
* approximation method.
*
* @return $this
*/
public function setWidth(float $width, ?string $unitOfMeasure = null): static
{
$this->width = ($unitOfMeasure === null || $width < 0)
? $width
: (new CssDimension("{$width}{$unitOfMeasure}"))->width();
return $this;
}
/**
* Get Auto Size.
*/
public function getAutoSize(): bool
{
return $this->autoSize;
}
/**
* Set Auto Size.
*
* @return $this
*/
public function setAutoSize(bool $autosizeEnabled): static
{
$this->autoSize = $autosizeEnabled;
return $this;
}
}

View File

@@ -0,0 +1,165 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use Iterator as NativeIterator;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
/**
* @implements NativeIterator<string, Column>
*/
class ColumnIterator implements NativeIterator
{
/**
* Worksheet to iterate.
*/
private Worksheet $worksheet;
/**
* Current iterator position.
*/
private int $currentColumnIndex = 1;
/**
* Start position.
*/
private int $startColumnIndex = 1;
/**
* End position.
*/
private int $endColumnIndex = 1;
/**
* Create a new column iterator.
*
* @param Worksheet $worksheet The worksheet to iterate over
* @param string $startColumn The column address at which to start iterating
* @param ?string $endColumn Optionally, the column address at which to stop iterating
*/
public function __construct(Worksheet $worksheet, string $startColumn = 'A', ?string $endColumn = null)
{
// Set subject
$this->worksheet = $worksheet;
$this->resetEnd($endColumn);
$this->resetStart($startColumn);
}
/**
* Destructor.
*/
public function __destruct()
{
unset($this->worksheet);
}
/**
* (Re)Set the start column and the current column pointer.
*
* @param string $startColumn The column address at which to start iterating
*
* @return $this
*/
public function resetStart(string $startColumn = 'A'): static
{
$startColumnIndex = Coordinate::columnIndexFromString($startColumn);
if ($startColumnIndex > Coordinate::columnIndexFromString($this->worksheet->getHighestColumn())) {
throw new Exception(
"Start column ({$startColumn}) is beyond highest column ({$this->worksheet->getHighestColumn()})"
);
}
$this->startColumnIndex = $startColumnIndex;
if ($this->endColumnIndex < $this->startColumnIndex) {
$this->endColumnIndex = $this->startColumnIndex;
}
$this->seek($startColumn);
return $this;
}
/**
* (Re)Set the end column.
*
* @param ?string $endColumn The column address at which to stop iterating
*
* @return $this
*/
public function resetEnd(?string $endColumn = null): static
{
$endColumn = $endColumn ?: $this->worksheet->getHighestColumn();
$this->endColumnIndex = Coordinate::columnIndexFromString($endColumn);
return $this;
}
/**
* Set the column pointer to the selected column.
*
* @param string $column The column address to set the current pointer at
*
* @return $this
*/
public function seek(string $column = 'A'): static
{
$column = Coordinate::columnIndexFromString($column);
if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) {
throw new PhpSpreadsheetException(
"Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})"
);
}
$this->currentColumnIndex = $column;
return $this;
}
/**
* Rewind the iterator to the starting column.
*/
public function rewind(): void
{
$this->currentColumnIndex = $this->startColumnIndex;
}
/**
* Return the current column in this worksheet.
*/
public function current(): Column
{
return new Column($this->worksheet, Coordinate::stringFromColumnIndex($this->currentColumnIndex));
}
/**
* Return the current iterator key.
*/
public function key(): string
{
return Coordinate::stringFromColumnIndex($this->currentColumnIndex);
}
/**
* Set the iterator to its next value.
*/
public function next(): void
{
++$this->currentColumnIndex;
}
/**
* Set the iterator to its previous value.
*/
public function prev(): void
{
--$this->currentColumnIndex;
}
/**
* Indicate if more columns exist in the worksheet range of columns that we're iterating.
*/
public function valid(): bool
{
return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex;
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
abstract class Dimension
{
/**
* Visible?
*/
private bool $visible = true;
/**
* Outline level.
*/
private int $outlineLevel = 0;
/**
* Collapsed.
*/
private bool $collapsed = false;
/**
* Index to cellXf. Null value means row has no explicit cellXf format.
*/
private ?int $xfIndex;
/**
* Create a new Dimension.
*
* @param ?int $initialValue Numeric row index
*/
public function __construct(?int $initialValue = null)
{
// set dimension as unformatted by default
$this->xfIndex = $initialValue;
}
/**
* Get Visible.
*/
public function getVisible(): bool
{
return $this->visible;
}
/**
* Set Visible.
*
* @return $this
*/
public function setVisible(bool $visible)
{
$this->visible = $visible;
return $this;
}
/**
* Get Outline Level.
*/
public function getOutlineLevel(): int
{
return $this->outlineLevel;
}
/**
* Set Outline Level.
* Value must be between 0 and 7.
*
* @return $this
*/
public function setOutlineLevel(int $level)
{
if ($level < 0 || $level > 7) {
throw new PhpSpreadsheetException('Outline level must range between 0 and 7.');
}
$this->outlineLevel = $level;
return $this;
}
/**
* Get Collapsed.
*/
public function getCollapsed(): bool
{
return $this->collapsed;
}
/**
* Set Collapsed.
*
* @return $this
*/
public function setCollapsed(bool $collapsed)
{
$this->collapsed = $collapsed;
return $this;
}
/**
* Get index to cellXf.
*/
public function getXfIndex(): ?int
{
return $this->xfIndex;
}
/**
* Set index to cellXf.
*
* @return $this
*/
public function setXfIndex(int $XfIndex)
{
$this->xfIndex = $XfIndex;
return $this;
}
}

View File

@@ -0,0 +1,260 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use ZipArchive;
class Drawing extends BaseDrawing
{
const IMAGE_TYPES_CONVERTION_MAP = [
IMAGETYPE_GIF => IMAGETYPE_PNG,
IMAGETYPE_JPEG => IMAGETYPE_JPEG,
IMAGETYPE_PNG => IMAGETYPE_PNG,
IMAGETYPE_BMP => IMAGETYPE_PNG,
];
/**
* Path.
*/
private string $path;
/**
* Whether or not we are dealing with a URL.
*/
private bool $isUrl;
/**
* Create a new Drawing.
*/
public function __construct()
{
// Initialise values
$this->path = '';
$this->isUrl = false;
// Initialize parent
parent::__construct();
}
/**
* Get Filename.
*/
public function getFilename(): string
{
return basename($this->path);
}
/**
* Get indexed filename (using image index).
*/
public function getIndexedFilename(): string
{
return md5($this->path) . '.' . $this->getExtension();
}
/**
* Get Extension.
*/
public function getExtension(): string
{
$exploded = explode('.', basename($this->path));
return $exploded[count($exploded) - 1];
}
/**
* Get full filepath to store drawing in zip archive.
*/
public function getMediaFilename(): string
{
if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) {
throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.');
}
return sprintf('image%d%s', $this->getImageIndex(), $this->getImageFileExtensionForSave());
}
/**
* Get Path.
*/
public function getPath(): string
{
return $this->path;
}
/**
* Set Path.
*
* @param string $path File path
* @param bool $verifyFile Verify file
* @param ?ZipArchive $zip Zip archive instance
*
* @return $this
*/
public function setPath(string $path, bool $verifyFile = true, ?ZipArchive $zip = null): static
{
$this->isUrl = false;
if (preg_match('~^data:image/[a-z]+;base64,~', $path) === 1) {
$this->path = $path;
return $this;
}
$this->path = '';
// Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979
if (filter_var($path, FILTER_VALIDATE_URL) || (preg_match('/^([\\w\\s\\x00-\\x1f]+):/u', $path) && !preg_match('/^([\\w]+):/u', $path))) {
if (!preg_match('/^(http|https|file|ftp|s3):/', $path)) {
throw new PhpSpreadsheetException('Invalid protocol for linked drawing');
}
// Implicit that it is a URL, rather store info than running check above on value in other places.
$this->isUrl = true;
$ctx = null;
// https://github.com/php/php-src/issues/16023
// https://github.com/php/php-src/issues/17121
if (str_starts_with($path, 'https:') || str_starts_with($path, 'http:')) {
$ctxArray = [
'http' => [
'user_agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
'header' => [
//'Connection: keep-alive', // unacceptable performance
'Accept: image/*;q=0.9,*/*;q=0.8',
],
],
];
if (str_starts_with($path, 'https:')) {
$ctxArray['ssl'] = ['crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT];
}
$ctx = stream_context_create($ctxArray);
}
$imageContents = @file_get_contents($path, false, $ctx);
if ($imageContents !== false) {
$filePath = tempnam(sys_get_temp_dir(), 'Drawing');
if ($filePath) {
$put = @file_put_contents($filePath, $imageContents);
if ($put !== false) {
if ($this->isImage($filePath)) {
$this->path = $path;
$this->setSizesAndType($filePath);
}
unlink($filePath);
}
}
}
} elseif ($zip instanceof ZipArchive) {
$zipPath = explode('#', $path)[1];
$locate = @$zip->locateName($zipPath);
if ($locate !== false) {
if ($this->isImage($path)) {
$this->path = $path;
$this->setSizesAndType($path);
}
}
} else {
$exists = @file_exists($path);
if ($exists !== false && $this->isImage($path)) {
$this->path = $path;
$this->setSizesAndType($path);
}
}
if ($this->path === '' && $verifyFile) {
throw new PhpSpreadsheetException("File $path not found!");
}
if ($this->worksheet !== null) {
if ($this->path !== '') {
$this->worksheet->getCell($this->coordinates);
}
}
return $this;
}
private function isImage(string $path): bool
{
$mime = (string) @mime_content_type($path);
$retVal = false;
if (str_starts_with($mime, 'image/')) {
$retVal = true;
} elseif ($mime === 'application/octet-stream') {
$extension = pathinfo($path, PATHINFO_EXTENSION);
$retVal = in_array($extension, ['bin', 'emf'], true);
}
return $retVal;
}
/**
* Get isURL.
*/
public function getIsURL(): bool
{
return $this->isUrl;
}
/**
* Set isURL.
*
* @return $this
*
* @deprecated 3.7.0 not needed, property is set by setPath
*/
public function setIsURL(bool $isUrl): self
{
$this->isUrl = $isUrl;
return $this;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode(): string
{
return md5(
$this->path
. parent::getHashCode()
. __CLASS__
);
}
/**
* Get Image Type for Save.
*/
public function getImageTypeForSave(): int
{
if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) {
throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.');
}
return self::IMAGE_TYPES_CONVERTION_MAP[$this->type];
}
/**
* Get Image file extention for Save.
*/
public function getImageFileExtensionForSave(bool $includeDot = true): string
{
if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) {
throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.');
}
$result = image_type_to_extension(self::IMAGE_TYPES_CONVERTION_MAP[$this->type], $includeDot);
return "$result";
}
/**
* Get Image mime type.
*/
public function getImageMimeType(): string
{
if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) {
throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.');
}
return image_type_to_mime_type(self::IMAGE_TYPES_CONVERTION_MAP[$this->type]);
}
}

View File

@@ -0,0 +1,247 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
use PhpOffice\PhpSpreadsheet\IComparable;
use PhpOffice\PhpSpreadsheet\Style\Color;
class Shadow implements IComparable
{
// Shadow alignment
const SHADOW_BOTTOM = 'b';
const SHADOW_BOTTOM_LEFT = 'bl';
const SHADOW_BOTTOM_RIGHT = 'br';
const SHADOW_CENTER = 'ctr';
const SHADOW_LEFT = 'l';
const SHADOW_TOP = 't';
const SHADOW_TOP_LEFT = 'tl';
const SHADOW_TOP_RIGHT = 'tr';
/**
* Visible.
*/
private bool $visible;
/**
* Blur radius.
*
* Defaults to 6
*/
private int $blurRadius;
/**
* Shadow distance.
*
* Defaults to 2
*/
private int $distance;
/**
* Shadow direction (in degrees).
*/
private int $direction;
/**
* Shadow alignment.
*/
private string $alignment;
/**
* Color.
*/
private Color $color;
/**
* Alpha.
*/
private int $alpha;
/**
* Create a new Shadow.
*/
public function __construct()
{
// Initialise values
$this->visible = false;
$this->blurRadius = 6;
$this->distance = 2;
$this->direction = 0;
$this->alignment = self::SHADOW_BOTTOM_RIGHT;
$this->color = new Color(Color::COLOR_BLACK);
$this->alpha = 50;
}
/**
* Get Visible.
*/
public function getVisible(): bool
{
return $this->visible;
}
/**
* Set Visible.
*
* @return $this
*/
public function setVisible(bool $visible): static
{
$this->visible = $visible;
return $this;
}
/**
* Get Blur radius.
*/
public function getBlurRadius(): int
{
return $this->blurRadius;
}
/**
* Set Blur radius.
*
* @return $this
*/
public function setBlurRadius(int $blurRadius): static
{
$this->blurRadius = $blurRadius;
return $this;
}
/**
* Get Shadow distance.
*/
public function getDistance(): int
{
return $this->distance;
}
/**
* Set Shadow distance.
*
* @return $this
*/
public function setDistance(int $distance): static
{
$this->distance = $distance;
return $this;
}
/**
* Get Shadow direction (in degrees).
*/
public function getDirection(): int
{
return $this->direction;
}
/**
* Set Shadow direction (in degrees).
*
* @return $this
*/
public function setDirection(int $direction): static
{
$this->direction = $direction;
return $this;
}
/**
* Get Shadow alignment.
*/
public function getAlignment(): string
{
return $this->alignment;
}
/**
* Set Shadow alignment.
*
* @return $this
*/
public function setAlignment(string $alignment): static
{
$this->alignment = $alignment;
return $this;
}
/**
* Get Color.
*/
public function getColor(): Color
{
return $this->color;
}
/**
* Set Color.
*
* @return $this
*/
public function setColor(Color $color): static
{
$this->color = $color;
return $this;
}
/**
* Get Alpha.
*/
public function getAlpha(): int
{
return $this->alpha;
}
/**
* Set Alpha.
*
* @return $this
*/
public function setAlpha(int $alpha): static
{
$this->alpha = $alpha;
return $this;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode(): string
{
return md5(
($this->visible ? 't' : 'f')
. $this->blurRadius
. $this->distance
. $this->direction
. $this->alignment
. $this->color->getHashCode()
. $this->alpha
. __CLASS__
);
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
if (is_object($value)) {
$this->$key = clone $value;
} else {
$this->$key = $value;
}
}
}
}

View File

@@ -0,0 +1,426 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
/**
* <code>
* Header/Footer Formatting Syntax taken from Office Open XML Part 4 - Markup Language Reference, page 1970:.
*
* There are a number of formatting codes that can be written inline with the actual header / footer text, which
* affect the formatting in the header or footer.
*
* Example: This example shows the text "Center Bold Header" on the first line (center section), and the date on
* the second line (center section).
* &CCenter &"-,Bold"Bold&"-,Regular"Header_x000A_&D
*
* General Rules:
* There is no required order in which these codes must appear.
*
* The first occurrence of the following codes turns the formatting ON, the second occurrence turns it OFF again:
* - strikethrough
* - superscript
* - subscript
* Superscript and subscript cannot both be ON at same time. Whichever comes first wins and the other is ignored,
* while the first is ON.
* &L - code for "left section" (there are three header / footer locations, "left", "center", and "right"). When
* two or more occurrences of this section marker exist, the contents from all markers are concatenated, in the
* order of appearance, and placed into the left section.
* &P - code for "current page #"
* &N - code for "total pages"
* &font size - code for "text font size", where font size is a font size in points.
* &K - code for "text font color"
* RGB Color is specified as RRGGBB
* Theme Color is specifed as TTSNN where TT is the theme color Id, S is either "+" or "-" of the tint/shade
* value, NN is the tint/shade value.
* &S - code for "text strikethrough" on / off
* &X - code for "text super script" on / off
* &Y - code for "text subscript" on / off
* &C - code for "center section". When two or more occurrences of this section marker exist, the contents
* from all markers are concatenated, in the order of appearance, and placed into the center section.
*
* &D - code for "date"
* &T - code for "time"
* &G - code for "picture as background"
* &U - code for "text single underline"
* &E - code for "double underline"
* &R - code for "right section". When two or more occurrences of this section marker exist, the contents
* from all markers are concatenated, in the order of appearance, and placed into the right section.
* &Z - code for "this workbook's file path"
* &F - code for "this workbook's file name"
* &A - code for "sheet tab name"
* &+ - code for add to page #.
* &- - code for subtract from page #.
* &"font name,font type" - code for "text font name" and "text font type", where font name and font type
* are strings specifying the name and type of the font, separated by a comma. When a hyphen appears in font
* name, it means "none specified". Both of font name and font type can be localized values.
* &"-,Bold" - code for "bold font style"
* &B - also means "bold font style".
* &"-,Regular" - code for "regular font style"
* &"-,Italic" - code for "italic font style"
* &I - also means "italic font style"
* &"-,Bold Italic" code for "bold italic font style"
* &O - code for "outline style"
* &H - code for "shadow style"
* </code>
*/
class HeaderFooter
{
// Header/footer image location
const IMAGE_HEADER_LEFT = 'LH';
const IMAGE_HEADER_CENTER = 'CH';
const IMAGE_HEADER_RIGHT = 'RH';
const IMAGE_FOOTER_LEFT = 'LF';
const IMAGE_FOOTER_CENTER = 'CF';
const IMAGE_FOOTER_RIGHT = 'RF';
/**
* OddHeader.
*/
private string $oddHeader = '';
/**
* OddFooter.
*/
private string $oddFooter = '';
/**
* EvenHeader.
*/
private string $evenHeader = '';
/**
* EvenFooter.
*/
private string $evenFooter = '';
/**
* FirstHeader.
*/
private string $firstHeader = '';
/**
* FirstFooter.
*/
private string $firstFooter = '';
/**
* Different header for Odd/Even, defaults to false.
*/
private bool $differentOddEven = false;
/**
* Different header for first page, defaults to false.
*/
private bool $differentFirst = false;
/**
* Scale with document, defaults to true.
*/
private bool $scaleWithDocument = true;
/**
* Align with margins, defaults to true.
*/
private bool $alignWithMargins = true;
/**
* Header/footer images.
*
* @var HeaderFooterDrawing[]
*/
private array $headerFooterImages = [];
/**
* Create a new HeaderFooter.
*/
public function __construct()
{
}
/**
* Get OddHeader.
*/
public function getOddHeader(): string
{
return $this->oddHeader;
}
/**
* Set OddHeader.
*
* @return $this
*/
public function setOddHeader(string $oddHeader): static
{
$this->oddHeader = $oddHeader;
return $this;
}
/**
* Get OddFooter.
*/
public function getOddFooter(): string
{
return $this->oddFooter;
}
/**
* Set OddFooter.
*
* @return $this
*/
public function setOddFooter(string $oddFooter): static
{
$this->oddFooter = $oddFooter;
return $this;
}
/**
* Get EvenHeader.
*/
public function getEvenHeader(): string
{
return $this->evenHeader;
}
/**
* Set EvenHeader.
*
* @return $this
*/
public function setEvenHeader(string $eventHeader): static
{
$this->evenHeader = $eventHeader;
return $this;
}
/**
* Get EvenFooter.
*/
public function getEvenFooter(): string
{
return $this->evenFooter;
}
/**
* Set EvenFooter.
*
* @return $this
*/
public function setEvenFooter(string $evenFooter): static
{
$this->evenFooter = $evenFooter;
return $this;
}
/**
* Get FirstHeader.
*/
public function getFirstHeader(): string
{
return $this->firstHeader;
}
/**
* Set FirstHeader.
*
* @return $this
*/
public function setFirstHeader(string $firstHeader): static
{
$this->firstHeader = $firstHeader;
return $this;
}
/**
* Get FirstFooter.
*/
public function getFirstFooter(): string
{
return $this->firstFooter;
}
/**
* Set FirstFooter.
*
* @return $this
*/
public function setFirstFooter(string $firstFooter): static
{
$this->firstFooter = $firstFooter;
return $this;
}
/**
* Get DifferentOddEven.
*/
public function getDifferentOddEven(): bool
{
return $this->differentOddEven;
}
/**
* Set DifferentOddEven.
*
* @return $this
*/
public function setDifferentOddEven(bool $differentOddEvent): static
{
$this->differentOddEven = $differentOddEvent;
return $this;
}
/**
* Get DifferentFirst.
*/
public function getDifferentFirst(): bool
{
return $this->differentFirst;
}
/**
* Set DifferentFirst.
*
* @return $this
*/
public function setDifferentFirst(bool $differentFirst): static
{
$this->differentFirst = $differentFirst;
return $this;
}
/**
* Get ScaleWithDocument.
*/
public function getScaleWithDocument(): bool
{
return $this->scaleWithDocument;
}
/**
* Set ScaleWithDocument.
*
* @return $this
*/
public function setScaleWithDocument(bool $scaleWithDocument): static
{
$this->scaleWithDocument = $scaleWithDocument;
return $this;
}
/**
* Get AlignWithMargins.
*/
public function getAlignWithMargins(): bool
{
return $this->alignWithMargins;
}
/**
* Set AlignWithMargins.
*
* @return $this
*/
public function setAlignWithMargins(bool $alignWithMargins): static
{
$this->alignWithMargins = $alignWithMargins;
return $this;
}
/**
* Add header/footer image.
*
* @return $this
*/
public function addImage(HeaderFooterDrawing $image, string $location = self::IMAGE_HEADER_LEFT): static
{
$this->headerFooterImages[$location] = $image;
return $this;
}
/**
* Remove header/footer image.
*
* @return $this
*/
public function removeImage(string $location = self::IMAGE_HEADER_LEFT): static
{
if (isset($this->headerFooterImages[$location])) {
unset($this->headerFooterImages[$location]);
}
return $this;
}
/**
* Set header/footer images.
*
* @param HeaderFooterDrawing[] $images
*
* @return $this
*/
public function setImages(array $images): static
{
$this->headerFooterImages = $images;
return $this;
}
/**
* Get header/footer images.
*
* @return HeaderFooterDrawing[]
*/
public function getImages(): array
{
// Sort array
$images = [];
if (isset($this->headerFooterImages[self::IMAGE_HEADER_LEFT])) {
$images[self::IMAGE_HEADER_LEFT] = $this->headerFooterImages[self::IMAGE_HEADER_LEFT];
}
if (isset($this->headerFooterImages[self::IMAGE_HEADER_CENTER])) {
$images[self::IMAGE_HEADER_CENTER] = $this->headerFooterImages[self::IMAGE_HEADER_CENTER];
}
if (isset($this->headerFooterImages[self::IMAGE_HEADER_RIGHT])) {
$images[self::IMAGE_HEADER_RIGHT] = $this->headerFooterImages[self::IMAGE_HEADER_RIGHT];
}
if (isset($this->headerFooterImages[self::IMAGE_FOOTER_LEFT])) {
$images[self::IMAGE_FOOTER_LEFT] = $this->headerFooterImages[self::IMAGE_FOOTER_LEFT];
}
if (isset($this->headerFooterImages[self::IMAGE_FOOTER_CENTER])) {
$images[self::IMAGE_FOOTER_CENTER] = $this->headerFooterImages[self::IMAGE_FOOTER_CENTER];
}
if (isset($this->headerFooterImages[self::IMAGE_FOOTER_RIGHT])) {
$images[self::IMAGE_FOOTER_RIGHT] = $this->headerFooterImages[self::IMAGE_FOOTER_RIGHT];
}
$this->headerFooterImages = $images;
return $this->headerFooterImages;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
if (is_object($value)) {
$this->$key = clone $value;
} else {
$this->$key = $value;
}
}
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
class HeaderFooterDrawing extends Drawing
{
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode(): string
{
return md5(
$this->getPath()
. $this->name
. $this->offsetX
. $this->offsetY
. $this->width
. $this->height
. __CLASS__
);
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
/**
* @implements \Iterator<int, Worksheet>
*/
class Iterator implements \Iterator
{
/**
* Spreadsheet to iterate.
*/
private Spreadsheet $subject;
/**
* Current iterator position.
*/
private int $position = 0;
/**
* Create a new worksheet iterator.
*/
public function __construct(Spreadsheet $subject)
{
// Set subject
$this->subject = $subject;
}
/**
* Rewind iterator.
*/
public function rewind(): void
{
$this->position = 0;
}
/**
* Current Worksheet.
*/
public function current(): Worksheet
{
return $this->subject->getSheet($this->position);
}
/**
* Current key.
*/
public function key(): int
{
return $this->position;
}
/**
* Next value.
*/
public function next(): void
{
++$this->position;
}
/**
* Are there more Worksheet instances available?
*/
public function valid(): bool
{
return $this->position < $this->subject->getSheetCount() && $this->position >= 0;
}
}

View File

@@ -0,0 +1,333 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use GdImage;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Shared\File;
class MemoryDrawing extends BaseDrawing
{
// Rendering functions
const RENDERING_DEFAULT = 'imagepng';
const RENDERING_PNG = 'imagepng';
const RENDERING_GIF = 'imagegif';
const RENDERING_JPEG = 'imagejpeg';
// MIME types
const MIMETYPE_DEFAULT = 'image/png';
const MIMETYPE_PNG = 'image/png';
const MIMETYPE_GIF = 'image/gif';
const MIMETYPE_JPEG = 'image/jpeg';
const SUPPORTED_MIME_TYPES = [
self::MIMETYPE_GIF,
self::MIMETYPE_JPEG,
self::MIMETYPE_PNG,
];
/**
* Image resource.
*/
private null|GdImage $imageResource = null;
/**
* Rendering function.
*/
private string $renderingFunction;
/**
* Mime type.
*/
private string $mimeType;
/**
* Unique name.
*/
private string $uniqueName;
/**
* Create a new MemoryDrawing.
*/
public function __construct()
{
// Initialise values
$this->renderingFunction = self::RENDERING_DEFAULT;
$this->mimeType = self::MIMETYPE_DEFAULT;
$this->uniqueName = md5(mt_rand(0, 9999) . time() . mt_rand(0, 9999));
// Initialize parent
parent::__construct();
}
public function __destruct()
{
if ($this->imageResource) {
@imagedestroy($this->imageResource);
$this->imageResource = null;
}
$this->worksheet = null;
}
public function __clone()
{
parent::__clone();
$this->cloneResource();
}
private function cloneResource(): void
{
if (!$this->imageResource) {
return;
}
$width = (int) imagesx($this->imageResource);
$height = (int) imagesy($this->imageResource);
if (imageistruecolor($this->imageResource)) {
$clone = imagecreatetruecolor($width, $height);
if (!$clone) {
throw new Exception('Could not clone image resource');
}
imagealphablending($clone, false);
imagesavealpha($clone, true);
} else {
$clone = imagecreate($width, $height);
if (!$clone) {
throw new Exception('Could not clone image resource');
}
// If the image has transparency...
$transparent = imagecolortransparent($this->imageResource);
if ($transparent >= 0) {
// Starting with Php8.0, next function throws rather than return false
$rgb = imagecolorsforindex($this->imageResource, $transparent);
imagesavealpha($clone, true);
$color = imagecolorallocatealpha($clone, $rgb['red'], $rgb['green'], $rgb['blue'], $rgb['alpha']);
if ($color === false) {
throw new Exception('Could not get image alpha color');
}
imagefill($clone, 0, 0, $color);
}
}
//Create the Clone!!
imagecopy($clone, $this->imageResource, 0, 0, 0, 0, $width, $height);
$this->imageResource = $clone;
}
/**
* @param resource $imageStream Stream data to be converted to a Memory Drawing
*
* @throws Exception
*/
public static function fromStream($imageStream): self
{
$streamValue = stream_get_contents($imageStream);
if ($streamValue === false) {
throw new Exception('Unable to read data from stream');
}
return self::fromString($streamValue);
}
/**
* @param string $imageString String data to be converted to a Memory Drawing
*
* @throws Exception
*/
public static function fromString(string $imageString): self
{
$gdImage = @imagecreatefromstring($imageString);
if ($gdImage === false) {
throw new Exception('Value cannot be converted to an image');
}
$mimeType = self::identifyMimeType($imageString);
if (imageistruecolor($gdImage) || imagecolortransparent($gdImage) >= 0) {
imagesavealpha($gdImage, true);
}
$renderingFunction = self::identifyRenderingFunction($mimeType);
$drawing = new self();
$drawing->setImageResource($gdImage);
$drawing->setRenderingFunction($renderingFunction);
$drawing->setMimeType($mimeType);
return $drawing;
}
private static function identifyRenderingFunction(string $mimeType): string
{
return match ($mimeType) {
self::MIMETYPE_PNG => self::RENDERING_PNG,
self::MIMETYPE_JPEG => self::RENDERING_JPEG,
self::MIMETYPE_GIF => self::RENDERING_GIF,
default => self::RENDERING_DEFAULT,
};
}
/**
* @throws Exception
*/
private static function identifyMimeType(string $imageString): string
{
$temporaryFileName = File::temporaryFilename();
file_put_contents($temporaryFileName, $imageString);
$mimeType = self::identifyMimeTypeUsingExif($temporaryFileName);
if ($mimeType !== null) {
unlink($temporaryFileName);
return $mimeType;
}
$mimeType = self::identifyMimeTypeUsingGd($temporaryFileName);
if ($mimeType !== null) {
unlink($temporaryFileName);
return $mimeType;
}
unlink($temporaryFileName);
return self::MIMETYPE_DEFAULT;
}
private static function identifyMimeTypeUsingExif(string $temporaryFileName): ?string
{
if (function_exists('exif_imagetype')) {
$imageType = @exif_imagetype($temporaryFileName);
$mimeType = ($imageType) ? image_type_to_mime_type($imageType) : null;
return self::supportedMimeTypes($mimeType);
}
return null;
}
private static function identifyMimeTypeUsingGd(string $temporaryFileName): ?string
{
if (function_exists('getimagesize')) {
$imageSize = @getimagesize($temporaryFileName);
if (is_array($imageSize)) {
$mimeType = $imageSize['mime'];
return self::supportedMimeTypes($mimeType);
}
}
return null;
}
private static function supportedMimeTypes(?string $mimeType = null): ?string
{
if (in_array($mimeType, self::SUPPORTED_MIME_TYPES, true)) {
return $mimeType;
}
return null;
}
/**
* Get image resource.
*/
public function getImageResource(): ?GdImage
{
return $this->imageResource;
}
/**
* Set image resource.
*
* @return $this
*/
public function setImageResource(?GdImage $value): static
{
$this->imageResource = $value;
if ($this->imageResource !== null) {
// Get width/height
$this->width = (int) imagesx($this->imageResource);
$this->height = (int) imagesy($this->imageResource);
}
return $this;
}
/**
* Get rendering function.
*/
public function getRenderingFunction(): string
{
return $this->renderingFunction;
}
/**
* Set rendering function.
*
* @param string $value see self::RENDERING_*
*
* @return $this
*/
public function setRenderingFunction(string $value): static
{
$this->renderingFunction = $value;
return $this;
}
/**
* Get mime type.
*/
public function getMimeType(): string
{
return $this->mimeType;
}
/**
* Set mime type.
*
* @param string $value see self::MIMETYPE_*
*
* @return $this
*/
public function setMimeType(string $value): static
{
$this->mimeType = $value;
return $this;
}
/**
* Get indexed filename (using image index).
*/
public function getIndexedFilename(): string
{
$extension = strtolower($this->getMimeType());
$extension = explode('/', $extension);
$extension = $extension[1];
return $this->uniqueName . $this->getImageIndex() . '.' . $extension;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode(): string
{
return md5(
$this->renderingFunction
. $this->mimeType
. $this->uniqueName
. parent::getHashCode()
. __CLASS__
);
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\CellAddress;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
class PageBreak
{
private int $breakType;
private string $coordinate;
private int $maxColOrRow;
/**
* @param array{0: int, 1: int}|CellAddress|string $coordinate
*/
public function __construct(int $breakType, CellAddress|string|array $coordinate, int $maxColOrRow = -1)
{
$coordinate = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate));
$this->breakType = $breakType;
$this->coordinate = $coordinate;
$this->maxColOrRow = $maxColOrRow;
}
public function getBreakType(): int
{
return $this->breakType;
}
public function getCoordinate(): string
{
return $this->coordinate;
}
public function getMaxColOrRow(): int
{
return $this->maxColOrRow;
}
public function getColumnInt(): int
{
return Coordinate::indexesFromString($this->coordinate)[0];
}
public function getRow(): int
{
return Coordinate::indexesFromString($this->coordinate)[1];
}
public function getColumnString(): string
{
return Coordinate::indexesFromString($this->coordinate)[2];
}
}

View File

@@ -0,0 +1,193 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
class PageMargins
{
/**
* Left.
*/
private float $left = 0.7;
/**
* Right.
*/
private float $right = 0.7;
/**
* Top.
*/
private float $top = 0.75;
/**
* Bottom.
*/
private float $bottom = 0.75;
/**
* Header.
*/
private float $header = 0.3;
/**
* Footer.
*/
private float $footer = 0.3;
/**
* Create a new PageMargins.
*/
public function __construct()
{
}
/**
* Get Left.
*/
public function getLeft(): float
{
return $this->left;
}
/**
* Set Left.
*
* @return $this
*/
public function setLeft(float $left): static
{
$this->left = $left;
return $this;
}
/**
* Get Right.
*/
public function getRight(): float
{
return $this->right;
}
/**
* Set Right.
*
* @return $this
*/
public function setRight(float $right): static
{
$this->right = $right;
return $this;
}
/**
* Get Top.
*/
public function getTop(): float
{
return $this->top;
}
/**
* Set Top.
*
* @return $this
*/
public function setTop(float $top): static
{
$this->top = $top;
return $this;
}
/**
* Get Bottom.
*/
public function getBottom(): float
{
return $this->bottom;
}
/**
* Set Bottom.
*
* @return $this
*/
public function setBottom(float $bottom): static
{
$this->bottom = $bottom;
return $this;
}
/**
* Get Header.
*/
public function getHeader(): float
{
return $this->header;
}
/**
* Set Header.
*
* @return $this
*/
public function setHeader(float $header): static
{
$this->header = $header;
return $this;
}
/**
* Get Footer.
*/
public function getFooter(): float
{
return $this->footer;
}
/**
* Set Footer.
*
* @return $this
*/
public function setFooter(float $footer): static
{
$this->footer = $footer;
return $this;
}
public static function fromCentimeters(float $value): float
{
return $value / 2.54;
}
public static function toCentimeters(float $value): float
{
return $value * 2.54;
}
public static function fromMillimeters(float $value): float
{
return $value / 25.4;
}
public static function toMillimeters(float $value): float
{
return $value * 25.4;
}
public static function fromPoints(float $value): float
{
return $value / 72;
}
public static function toPoints(float $value): float
{
return $value * 72;
}
}

View File

@@ -0,0 +1,824 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
/**
* <code>
* Paper size taken from Office Open XML Part 4 - Markup Language Reference, page 1988:.
*
* 1 = Letter paper (8.5 in. by 11 in.)
* 2 = Letter small paper (8.5 in. by 11 in.)
* 3 = Tabloid paper (11 in. by 17 in.)
* 4 = Ledger paper (17 in. by 11 in.)
* 5 = Legal paper (8.5 in. by 14 in.)
* 6 = Statement paper (5.5 in. by 8.5 in.)
* 7 = Executive paper (7.25 in. by 10.5 in.)
* 8 = A3 paper (297 mm by 420 mm)
* 9 = A4 paper (210 mm by 297 mm)
* 10 = A4 small paper (210 mm by 297 mm)
* 11 = A5 paper (148 mm by 210 mm)
* 12 = B4 paper (250 mm by 353 mm)
* 13 = B5 paper (176 mm by 250 mm)
* 14 = Folio paper (8.5 in. by 13 in.)
* 15 = Quarto paper (215 mm by 275 mm)
* 16 = Standard paper (10 in. by 14 in.)
* 17 = Standard paper (11 in. by 17 in.)
* 18 = Note paper (8.5 in. by 11 in.)
* 19 = #9 envelope (3.875 in. by 8.875 in.)
* 20 = #10 envelope (4.125 in. by 9.5 in.)
* 21 = #11 envelope (4.5 in. by 10.375 in.)
* 22 = #12 envelope (4.75 in. by 11 in.)
* 23 = #14 envelope (5 in. by 11.5 in.)
* 24 = C paper (17 in. by 22 in.)
* 25 = D paper (22 in. by 34 in.)
* 26 = E paper (34 in. by 44 in.)
* 27 = DL envelope (110 mm by 220 mm)
* 28 = C5 envelope (162 mm by 229 mm)
* 29 = C3 envelope (324 mm by 458 mm)
* 30 = C4 envelope (229 mm by 324 mm)
* 31 = C6 envelope (114 mm by 162 mm)
* 32 = C65 envelope (114 mm by 229 mm)
* 33 = B4 envelope (250 mm by 353 mm)
* 34 = B5 envelope (176 mm by 250 mm)
* 35 = B6 envelope (176 mm by 125 mm)
* 36 = Italy envelope (110 mm by 230 mm)
* 37 = Monarch envelope (3.875 in. by 7.5 in.).
* 38 = 6 3/4 envelope (3.625 in. by 6.5 in.)
* 39 = US standard fanfold (14.875 in. by 11 in.)
* 40 = German standard fanfold (8.5 in. by 12 in.)
* 41 = German legal fanfold (8.5 in. by 13 in.)
* 42 = ISO B4 (250 mm by 353 mm)
* 43 = Japanese double postcard (200 mm by 148 mm)
* 44 = Standard paper (9 in. by 11 in.)
* 45 = Standard paper (10 in. by 11 in.)
* 46 = Standard paper (15 in. by 11 in.)
* 47 = Invite envelope (220 mm by 220 mm)
* 50 = Letter extra paper (9.275 in. by 12 in.)
* 51 = Legal extra paper (9.275 in. by 15 in.)
* 52 = Tabloid extra paper (11.69 in. by 18 in.)
* 53 = A4 extra paper (236 mm by 322 mm)
* 54 = Letter transverse paper (8.275 in. by 11 in.)
* 55 = A4 transverse paper (210 mm by 297 mm)
* 56 = Letter extra transverse paper (9.275 in. by 12 in.)
* 57 = SuperA/SuperA/A4 paper (227 mm by 356 mm)
* 58 = SuperB/SuperB/A3 paper (305 mm by 487 mm)
* 59 = Letter plus paper (8.5 in. by 12.69 in.)
* 60 = A4 plus paper (210 mm by 330 mm)
* 61 = A5 transverse paper (148 mm by 210 mm)
* 62 = JIS B5 transverse paper (182 mm by 257 mm)
* 63 = A3 extra paper (322 mm by 445 mm)
* 64 = A5 extra paper (174 mm by 235 mm)
* 65 = ISO B5 extra paper (201 mm by 276 mm)
* 66 = A2 paper (420 mm by 594 mm)
* 67 = A3 transverse paper (297 mm by 420 mm)
* 68 = A3 extra transverse paper (322 mm by 445 mm)
* </code>
*/
class PageSetup
{
// Paper size
const PAPERSIZE_LETTER = 1;
const PAPERSIZE_LETTER_SMALL = 2;
const PAPERSIZE_TABLOID = 3;
const PAPERSIZE_LEDGER = 4;
const PAPERSIZE_LEGAL = 5;
const PAPERSIZE_STATEMENT = 6;
const PAPERSIZE_EXECUTIVE = 7;
const PAPERSIZE_A3 = 8;
const PAPERSIZE_A4 = 9;
const PAPERSIZE_A4_SMALL = 10;
const PAPERSIZE_A5 = 11;
const PAPERSIZE_B4 = 12;
const PAPERSIZE_B5 = 13;
const PAPERSIZE_FOLIO = 14;
const PAPERSIZE_QUARTO = 15;
const PAPERSIZE_STANDARD_1 = 16;
const PAPERSIZE_STANDARD_2 = 17;
const PAPERSIZE_NOTE = 18;
const PAPERSIZE_NO9_ENVELOPE = 19;
const PAPERSIZE_NO10_ENVELOPE = 20;
const PAPERSIZE_NO11_ENVELOPE = 21;
const PAPERSIZE_NO12_ENVELOPE = 22;
const PAPERSIZE_NO14_ENVELOPE = 23;
const PAPERSIZE_C = 24;
const PAPERSIZE_D = 25;
const PAPERSIZE_E = 26;
const PAPERSIZE_DL_ENVELOPE = 27;
const PAPERSIZE_C5_ENVELOPE = 28;
const PAPERSIZE_C3_ENVELOPE = 29;
const PAPERSIZE_C4_ENVELOPE = 30;
const PAPERSIZE_C6_ENVELOPE = 31;
const PAPERSIZE_C65_ENVELOPE = 32;
const PAPERSIZE_B4_ENVELOPE = 33;
const PAPERSIZE_B5_ENVELOPE = 34;
const PAPERSIZE_B6_ENVELOPE = 35;
const PAPERSIZE_ITALY_ENVELOPE = 36;
const PAPERSIZE_MONARCH_ENVELOPE = 37;
const PAPERSIZE_6_3_4_ENVELOPE = 38;
const PAPERSIZE_US_STANDARD_FANFOLD = 39;
const PAPERSIZE_GERMAN_STANDARD_FANFOLD = 40;
const PAPERSIZE_GERMAN_LEGAL_FANFOLD = 41;
const PAPERSIZE_ISO_B4 = 42;
const PAPERSIZE_JAPANESE_DOUBLE_POSTCARD = 43;
const PAPERSIZE_STANDARD_PAPER_1 = 44;
const PAPERSIZE_STANDARD_PAPER_2 = 45;
const PAPERSIZE_STANDARD_PAPER_3 = 46;
const PAPERSIZE_INVITE_ENVELOPE = 47;
const PAPERSIZE_LETTER_EXTRA_PAPER = 48;
const PAPERSIZE_LEGAL_EXTRA_PAPER = 49;
const PAPERSIZE_TABLOID_EXTRA_PAPER = 50;
const PAPERSIZE_A4_EXTRA_PAPER = 51;
const PAPERSIZE_LETTER_TRANSVERSE_PAPER = 52;
const PAPERSIZE_A4_TRANSVERSE_PAPER = 53;
const PAPERSIZE_LETTER_EXTRA_TRANSVERSE_PAPER = 54;
const PAPERSIZE_SUPERA_SUPERA_A4_PAPER = 55;
const PAPERSIZE_SUPERB_SUPERB_A3_PAPER = 56;
const PAPERSIZE_LETTER_PLUS_PAPER = 57;
const PAPERSIZE_A4_PLUS_PAPER = 58;
const PAPERSIZE_A5_TRANSVERSE_PAPER = 59;
const PAPERSIZE_JIS_B5_TRANSVERSE_PAPER = 60;
const PAPERSIZE_A3_EXTRA_PAPER = 61;
const PAPERSIZE_A5_EXTRA_PAPER = 62;
const PAPERSIZE_ISO_B5_EXTRA_PAPER = 63;
const PAPERSIZE_A2_PAPER = 64;
const PAPERSIZE_A3_TRANSVERSE_PAPER = 65;
const PAPERSIZE_A3_EXTRA_TRANSVERSE_PAPER = 66;
// Page orientation
const ORIENTATION_DEFAULT = 'default';
const ORIENTATION_LANDSCAPE = 'landscape';
const ORIENTATION_PORTRAIT = 'portrait';
// Print Range Set Method
const SETPRINTRANGE_OVERWRITE = 'O';
const SETPRINTRANGE_INSERT = 'I';
const PAGEORDER_OVER_THEN_DOWN = 'overThenDown';
const PAGEORDER_DOWN_THEN_OVER = 'downThenOver';
/**
* Paper size default.
*/
private static int $paperSizeDefault = self::PAPERSIZE_LETTER;
/**
* Paper size.
*/
private ?int $paperSize = null;
/**
* Orientation default.
*/
private static string $orientationDefault = self::ORIENTATION_DEFAULT;
/**
* Orientation.
*/
private string $orientation;
/**
* Scale (Print Scale).
*
* Print scaling. Valid values range from 10 to 400
* This setting is overridden when fitToWidth and/or fitToHeight are in use
*/
private ?int $scale = 100;
/**
* Fit To Page
* Whether scale or fitToWith / fitToHeight applies.
*/
private bool $fitToPage = false;
/**
* Fit To Height
* Number of vertical pages to fit on.
*/
private ?int $fitToHeight = 1;
/**
* Fit To Width
* Number of horizontal pages to fit on.
*/
private ?int $fitToWidth = 1;
/**
* Columns to repeat at left.
*
* @var array Containing start column and end column, empty array if option unset
*/
private array $columnsToRepeatAtLeft = ['', ''];
/**
* Rows to repeat at top.
*
* @var array Containing start row number and end row number, empty array if option unset
*/
private array $rowsToRepeatAtTop = [0, 0];
/**
* Center page horizontally.
*/
private bool $horizontalCentered = false;
/**
* Center page vertically.
*/
private bool $verticalCentered = false;
/**
* Print area.
*/
private ?string $printArea = null;
/**
* First page number.
*/
private ?int $firstPageNumber = null;
private string $pageOrder = self::PAGEORDER_DOWN_THEN_OVER;
/**
* Create a new PageSetup.
*/
public function __construct()
{
$this->orientation = self::$orientationDefault;
}
/**
* Get Paper Size.
*/
public function getPaperSize(): int
{
return $this->paperSize ?? self::$paperSizeDefault;
}
/**
* Set Paper Size.
*
* @param int $paperSize see self::PAPERSIZE_*
*
* @return $this
*/
public function setPaperSize(int $paperSize): static
{
$this->paperSize = $paperSize;
return $this;
}
/**
* Get Paper Size default.
*/
public static function getPaperSizeDefault(): int
{
return self::$paperSizeDefault;
}
/**
* Set Paper Size Default.
*/
public static function setPaperSizeDefault(int $paperSize): void
{
self::$paperSizeDefault = $paperSize;
}
/**
* Get Orientation.
*/
public function getOrientation(): string
{
return $this->orientation;
}
/**
* Set Orientation.
*
* @param string $orientation see self::ORIENTATION_*
*
* @return $this
*/
public function setOrientation(string $orientation): static
{
if ($orientation === self::ORIENTATION_LANDSCAPE || $orientation === self::ORIENTATION_PORTRAIT || $orientation === self::ORIENTATION_DEFAULT) {
$this->orientation = $orientation;
}
return $this;
}
public static function getOrientationDefault(): string
{
return self::$orientationDefault;
}
public static function setOrientationDefault(string $orientation): void
{
if ($orientation === self::ORIENTATION_LANDSCAPE || $orientation === self::ORIENTATION_PORTRAIT || $orientation === self::ORIENTATION_DEFAULT) {
self::$orientationDefault = $orientation;
}
}
/**
* Get Scale.
*/
public function getScale(): ?int
{
return $this->scale;
}
/**
* Set Scale.
* Print scaling. Valid values range from 10 to 400
* This setting is overridden when fitToWidth and/or fitToHeight are in use.
*
* @param bool $update Update fitToPage so scaling applies rather than fitToHeight / fitToWidth
*
* @return $this
*/
public function setScale(?int $scale, bool $update = true): static
{
// Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface,
// but it is apparently still able to handle any scale >= 0, where 0 results in 100
if ($scale === null || $scale >= 0) {
$this->scale = $scale;
if ($update) {
$this->fitToPage = false;
}
} else {
throw new PhpSpreadsheetException('Scale must not be negative');
}
return $this;
}
/**
* Get Fit To Page.
*/
public function getFitToPage(): bool
{
return $this->fitToPage;
}
/**
* Set Fit To Page.
*
* @return $this
*/
public function setFitToPage(bool $fitToPage): static
{
$this->fitToPage = $fitToPage;
return $this;
}
/**
* Get Fit To Height.
*/
public function getFitToHeight(): ?int
{
return $this->fitToHeight;
}
/**
* Set Fit To Height.
*
* @param bool $update Update fitToPage so it applies rather than scaling
*
* @return $this
*/
public function setFitToHeight(?int $fitToHeight, bool $update = true): static
{
$this->fitToHeight = $fitToHeight;
if ($update) {
$this->fitToPage = true;
}
return $this;
}
/**
* Get Fit To Width.
*/
public function getFitToWidth(): ?int
{
return $this->fitToWidth;
}
/**
* Set Fit To Width.
*
* @param bool $update Update fitToPage so it applies rather than scaling
*
* @return $this
*/
public function setFitToWidth(?int $value, bool $update = true): static
{
$this->fitToWidth = $value;
if ($update) {
$this->fitToPage = true;
}
return $this;
}
/**
* Is Columns to repeat at left set?
*/
public function isColumnsToRepeatAtLeftSet(): bool
{
if (!empty($this->columnsToRepeatAtLeft)) {
if ($this->columnsToRepeatAtLeft[0] != '' && $this->columnsToRepeatAtLeft[1] != '') {
return true;
}
}
return false;
}
/**
* Get Columns to repeat at left.
*
* @return array Containing start column and end column, empty array if option unset
*/
public function getColumnsToRepeatAtLeft(): array
{
return $this->columnsToRepeatAtLeft;
}
/**
* Set Columns to repeat at left.
*
* @param array $columnsToRepeatAtLeft Containing start column and end column, empty array if option unset
*
* @return $this
*/
public function setColumnsToRepeatAtLeft(array $columnsToRepeatAtLeft): static
{
$this->columnsToRepeatAtLeft = $columnsToRepeatAtLeft;
return $this;
}
/**
* Set Columns to repeat at left by start and end.
*
* @param string $start eg: 'A'
* @param string $end eg: 'B'
*
* @return $this
*/
public function setColumnsToRepeatAtLeftByStartAndEnd(string $start, string $end): static
{
$this->columnsToRepeatAtLeft = [$start, $end];
return $this;
}
/**
* Is Rows to repeat at top set?
*/
public function isRowsToRepeatAtTopSet(): bool
{
if (!empty($this->rowsToRepeatAtTop)) {
if ($this->rowsToRepeatAtTop[0] != 0 && $this->rowsToRepeatAtTop[1] != 0) {
return true;
}
}
return false;
}
/**
* Get Rows to repeat at top.
*
* @return array Containing start column and end column, empty array if option unset
*/
public function getRowsToRepeatAtTop(): array
{
return $this->rowsToRepeatAtTop;
}
/**
* Set Rows to repeat at top.
*
* @param array $rowsToRepeatAtTop Containing start column and end column, empty array if option unset
*
* @return $this
*/
public function setRowsToRepeatAtTop(array $rowsToRepeatAtTop): static
{
$this->rowsToRepeatAtTop = $rowsToRepeatAtTop;
return $this;
}
/**
* Set Rows to repeat at top by start and end.
*
* @param int $start eg: 1
* @param int $end eg: 1
*
* @return $this
*/
public function setRowsToRepeatAtTopByStartAndEnd(int $start, int $end): static
{
$this->rowsToRepeatAtTop = [$start, $end];
return $this;
}
/**
* Get center page horizontally.
*/
public function getHorizontalCentered(): bool
{
return $this->horizontalCentered;
}
/**
* Set center page horizontally.
*
* @return $this
*/
public function setHorizontalCentered(bool $value): static
{
$this->horizontalCentered = $value;
return $this;
}
/**
* Get center page vertically.
*/
public function getVerticalCentered(): bool
{
return $this->verticalCentered;
}
/**
* Set center page vertically.
*
* @return $this
*/
public function setVerticalCentered(bool $value): static
{
$this->verticalCentered = $value;
return $this;
}
/**
* Get print area.
*
* @param int $index Identifier for a specific print area range if several ranges have been set
* Default behaviour, or a index value of 0, will return all ranges as a comma-separated string
* Otherwise, the specific range identified by the value of $index will be returned
* Print areas are numbered from 1
*/
public function getPrintArea(int $index = 0): string
{
if ($index == 0) {
return (string) $this->printArea;
}
$printAreas = explode(',', (string) $this->printArea);
if (isset($printAreas[$index - 1])) {
return $printAreas[$index - 1];
}
throw new PhpSpreadsheetException('Requested Print Area does not exist');
}
/**
* Is print area set?
*
* @param int $index Identifier for a specific print area range if several ranges have been set
* Default behaviour, or an index value of 0, will identify whether any print range is set
* Otherwise, existence of the range identified by the value of $index will be returned
* Print areas are numbered from 1
*/
public function isPrintAreaSet(int $index = 0): bool
{
if ($index == 0) {
return $this->printArea !== null;
}
$printAreas = explode(',', (string) $this->printArea);
return isset($printAreas[$index - 1]);
}
/**
* Clear a print area.
*
* @param int $index Identifier for a specific print area range if several ranges have been set
* Default behaviour, or an index value of 0, will clear all print ranges that are set
* Otherwise, the range identified by the value of $index will be removed from the series
* Print areas are numbered from 1
*
* @return $this
*/
public function clearPrintArea(int $index = 0): static
{
if ($index == 0) {
$this->printArea = null;
} else {
$printAreas = explode(',', (string) $this->printArea);
if (isset($printAreas[$index - 1])) {
unset($printAreas[$index - 1]);
$this->printArea = implode(',', $printAreas);
}
}
return $this;
}
/**
* Set print area. e.g. 'A1:D10' or 'A1:D10,G5:M20'.
*
* @param int $index Identifier for a specific print area range allowing several ranges to be set
* When the method is "O"verwrite, then a positive integer index will overwrite that indexed
* entry in the print areas list; a negative index value will identify which entry to
* overwrite working bacward through the print area to the list, with the last entry as -1.
* Specifying an index value of 0, will overwrite <b>all</b> existing print ranges.
* When the method is "I"nsert, then a positive index will insert after that indexed entry in
* the print areas list, while a negative index will insert before the indexed entry.
* Specifying an index value of 0, will always append the new print range at the end of the
* list.
* Print areas are numbered from 1
* @param string $method Determines the method used when setting multiple print areas
* Default behaviour, or the "O" method, overwrites existing print area
* The "I" method, inserts the new print area before any specified index, or at the end of the list
*
* @return $this
*/
public function setPrintArea(string $value, int $index = 0, string $method = self::SETPRINTRANGE_OVERWRITE): static
{
if (str_contains($value, '!')) {
throw new PhpSpreadsheetException('Cell coordinate must not specify a worksheet.');
} elseif (!str_contains($value, ':')) {
throw new PhpSpreadsheetException('Cell coordinate must be a range of cells.');
} elseif (str_contains($value, '$')) {
throw new PhpSpreadsheetException('Cell coordinate must not be absolute.');
}
$value = strtoupper($value);
if (!$this->printArea) {
$index = 0;
}
if ($method == self::SETPRINTRANGE_OVERWRITE) {
if ($index == 0) {
$this->printArea = $value;
} else {
$printAreas = explode(',', (string) $this->printArea);
if ($index < 0) {
$index = count($printAreas) - abs($index) + 1;
}
if (($index <= 0) || ($index > count($printAreas))) {
throw new PhpSpreadsheetException('Invalid index for setting print range.');
}
$printAreas[$index - 1] = $value;
$this->printArea = implode(',', $printAreas);
}
} elseif ($method == self::SETPRINTRANGE_INSERT) {
if ($index == 0) {
$this->printArea = $this->printArea ? ($this->printArea . ',' . $value) : $value;
} else {
$printAreas = explode(',', (string) $this->printArea);
if ($index < 0) {
$index = (int) abs($index) - 1;
}
if ($index > count($printAreas)) {
throw new PhpSpreadsheetException('Invalid index for setting print range.');
}
$printAreas = array_merge(array_slice($printAreas, 0, $index), [$value], array_slice($printAreas, $index));
$this->printArea = implode(',', $printAreas);
}
} else {
throw new PhpSpreadsheetException('Invalid method for setting print range.');
}
return $this;
}
/**
* Add a new print area (e.g. 'A1:D10' or 'A1:D10,G5:M20') to the list of print areas.
*
* @param int $index Identifier for a specific print area range allowing several ranges to be set
* A positive index will insert after that indexed entry in the print areas list, while a
* negative index will insert before the indexed entry.
* Specifying an index value of 0, will always append the new print range at the end of the
* list.
* Print areas are numbered from 1
*
* @return $this
*/
public function addPrintArea(string $value, int $index = -1): static
{
return $this->setPrintArea($value, $index, self::SETPRINTRANGE_INSERT);
}
/**
* Set print area.
*
* @param int $column1 Column 1
* @param int $row1 Row 1
* @param int $column2 Column 2
* @param int $row2 Row 2
* @param int $index Identifier for a specific print area range allowing several ranges to be set
* When the method is "O"verwrite, then a positive integer index will overwrite that indexed
* entry in the print areas list; a negative index value will identify which entry to
* overwrite working backward through the print area to the list, with the last entry as -1.
* Specifying an index value of 0, will overwrite <b>all</b> existing print ranges.
* When the method is "I"nsert, then a positive index will insert after that indexed entry in
* the print areas list, while a negative index will insert before the indexed entry.
* Specifying an index value of 0, will always append the new print range at the end of the
* list.
* Print areas are numbered from 1
* @param string $method Determines the method used when setting multiple print areas
* Default behaviour, or the "O" method, overwrites existing print area
* The "I" method, inserts the new print area before any specified index, or at the end of the list
*
* @return $this
*/
public function setPrintAreaByColumnAndRow(int $column1, int $row1, int $column2, int $row2, int $index = 0, string $method = self::SETPRINTRANGE_OVERWRITE): static
{
return $this->setPrintArea(
Coordinate::stringFromColumnIndex($column1) . $row1 . ':' . Coordinate::stringFromColumnIndex($column2) . $row2,
$index,
$method
);
}
/**
* Add a new print area to the list of print areas.
*
* @param int $column1 Start Column for the print area
* @param int $row1 Start Row for the print area
* @param int $column2 End Column for the print area
* @param int $row2 End Row for the print area
* @param int $index Identifier for a specific print area range allowing several ranges to be set
* A positive index will insert after that indexed entry in the print areas list, while a
* negative index will insert before the indexed entry.
* Specifying an index value of 0, will always append the new print range at the end of the
* list.
* Print areas are numbered from 1
*
* @return $this
*/
public function addPrintAreaByColumnAndRow(int $column1, int $row1, int $column2, int $row2, int $index = -1): static
{
return $this->setPrintArea(
Coordinate::stringFromColumnIndex($column1) . $row1 . ':' . Coordinate::stringFromColumnIndex($column2) . $row2,
$index,
self::SETPRINTRANGE_INSERT
);
}
/**
* Get first page number.
*/
public function getFirstPageNumber(): ?int
{
return $this->firstPageNumber;
}
/**
* Set first page number.
*
* @return $this
*/
public function setFirstPageNumber(?int $value): static
{
$this->firstPageNumber = $value;
return $this;
}
/**
* Reset first page number.
*
* @return $this
*/
public function resetFirstPageNumber(): static
{
return $this->setFirstPageNumber(null);
}
public function getPageOrder(): string
{
return $this->pageOrder;
}
public function setPageOrder(?string $pageOrder): self
{
if ($pageOrder === null || $pageOrder === self::PAGEORDER_DOWN_THEN_OVER || $pageOrder === self::PAGEORDER_OVER_THEN_DOWN) {
$this->pageOrder = $pageOrder ?? self::PAGEORDER_DOWN_THEN_OVER;
}
return $this;
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
class Pane
{
private string $sqref;
private string $activeCell;
private string $position;
public function __construct(string $position, string $sqref = '', string $activeCell = '')
{
$this->sqref = $sqref;
$this->activeCell = $activeCell;
$this->position = $position;
}
public function getPosition(): string
{
return $this->position;
}
public function getSqref(): string
{
return $this->sqref;
}
public function setSqref(string $sqref): self
{
$this->sqref = $sqref;
return $this;
}
public function getActiveCell(): string
{
return $this->activeCell;
}
public function setActiveCell(string $activeCell): self
{
$this->activeCell = $activeCell;
return $this;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
class ProtectedRange
{
private string $name = '';
private string $password = '';
private string $sqref;
private string $securityDescriptor = '';
/**
* No setters aside from constructor.
*/
public function __construct(string $sqref, string $password = '', string $name = '', string $securityDescriptor = '')
{
$this->sqref = $sqref;
$this->name = $name;
$this->password = $password;
$this->securityDescriptor = $securityDescriptor;
}
public function getSqref(): string
{
return $this->sqref;
}
public function getName(): string
{
return $this->name ?: ('p' . md5($this->sqref));
}
public function getPassword(): string
{
return $this->password;
}
public function getSecurityDescriptor(): string
{
return $this->securityDescriptor;
}
}

View File

@@ -0,0 +1,474 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Shared\PasswordHasher;
class Protection
{
const ALGORITHM_MD2 = 'MD2';
const ALGORITHM_MD4 = 'MD4';
const ALGORITHM_MD5 = 'MD5';
const ALGORITHM_SHA_1 = 'SHA-1';
const ALGORITHM_SHA_256 = 'SHA-256';
const ALGORITHM_SHA_384 = 'SHA-384';
const ALGORITHM_SHA_512 = 'SHA-512';
const ALGORITHM_RIPEMD_128 = 'RIPEMD-128';
const ALGORITHM_RIPEMD_160 = 'RIPEMD-160';
const ALGORITHM_WHIRLPOOL = 'WHIRLPOOL';
/**
* Autofilters are locked when sheet is protected, default true.
*/
private ?bool $autoFilter = null;
/**
* Deleting columns is locked when sheet is protected, default true.
*/
private ?bool $deleteColumns = null;
/**
* Deleting rows is locked when sheet is protected, default true.
*/
private ?bool $deleteRows = null;
/**
* Formatting cells is locked when sheet is protected, default true.
*/
private ?bool $formatCells = null;
/**
* Formatting columns is locked when sheet is protected, default true.
*/
private ?bool $formatColumns = null;
/**
* Formatting rows is locked when sheet is protected, default true.
*/
private ?bool $formatRows = null;
/**
* Inserting columns is locked when sheet is protected, default true.
*/
private ?bool $insertColumns = null;
/**
* Inserting hyperlinks is locked when sheet is protected, default true.
*/
private ?bool $insertHyperlinks = null;
/**
* Inserting rows is locked when sheet is protected, default true.
*/
private ?bool $insertRows = null;
/**
* Objects are locked when sheet is protected, default false.
*/
private ?bool $objects = null;
/**
* Pivot tables are locked when the sheet is protected, default true.
*/
private ?bool $pivotTables = null;
/**
* Scenarios are locked when sheet is protected, default false.
*/
private ?bool $scenarios = null;
/**
* Selection of locked cells is locked when sheet is protected, default false.
*/
private ?bool $selectLockedCells = null;
/**
* Selection of unlocked cells is locked when sheet is protected, default false.
*/
private ?bool $selectUnlockedCells = null;
/**
* Sheet is locked when sheet is protected, default false.
*/
private ?bool $sheet = null;
/**
* Sorting is locked when sheet is protected, default true.
*/
private ?bool $sort = null;
/**
* Hashed password.
*/
private string $password = '';
/**
* Algorithm name.
*/
private string $algorithm = '';
/**
* Salt value.
*/
private string $salt = '';
/**
* Spin count.
*/
private int $spinCount = 10000;
/**
* Create a new Protection.
*/
public function __construct()
{
}
/**
* Is some sort of protection enabled?
*/
public function isProtectionEnabled(): bool
{
return
$this->password !== ''
|| isset($this->sheet)
|| isset($this->objects)
|| isset($this->scenarios)
|| isset($this->formatCells)
|| isset($this->formatColumns)
|| isset($this->formatRows)
|| isset($this->insertColumns)
|| isset($this->insertRows)
|| isset($this->insertHyperlinks)
|| isset($this->deleteColumns)
|| isset($this->deleteRows)
|| isset($this->selectLockedCells)
|| isset($this->sort)
|| isset($this->autoFilter)
|| isset($this->pivotTables)
|| isset($this->selectUnlockedCells);
}
public function getSheet(): ?bool
{
return $this->sheet;
}
public function setSheet(?bool $sheet): self
{
$this->sheet = $sheet;
return $this;
}
public function getObjects(): ?bool
{
return $this->objects;
}
public function setObjects(?bool $objects): self
{
$this->objects = $objects;
return $this;
}
public function getScenarios(): ?bool
{
return $this->scenarios;
}
public function setScenarios(?bool $scenarios): self
{
$this->scenarios = $scenarios;
return $this;
}
public function getFormatCells(): ?bool
{
return $this->formatCells;
}
public function setFormatCells(?bool $formatCells): self
{
$this->formatCells = $formatCells;
return $this;
}
public function getFormatColumns(): ?bool
{
return $this->formatColumns;
}
public function setFormatColumns(?bool $formatColumns): self
{
$this->formatColumns = $formatColumns;
return $this;
}
public function getFormatRows(): ?bool
{
return $this->formatRows;
}
public function setFormatRows(?bool $formatRows): self
{
$this->formatRows = $formatRows;
return $this;
}
public function getInsertColumns(): ?bool
{
return $this->insertColumns;
}
public function setInsertColumns(?bool $insertColumns): self
{
$this->insertColumns = $insertColumns;
return $this;
}
public function getInsertRows(): ?bool
{
return $this->insertRows;
}
public function setInsertRows(?bool $insertRows): self
{
$this->insertRows = $insertRows;
return $this;
}
public function getInsertHyperlinks(): ?bool
{
return $this->insertHyperlinks;
}
public function setInsertHyperlinks(?bool $insertHyperLinks): self
{
$this->insertHyperlinks = $insertHyperLinks;
return $this;
}
public function getDeleteColumns(): ?bool
{
return $this->deleteColumns;
}
public function setDeleteColumns(?bool $deleteColumns): self
{
$this->deleteColumns = $deleteColumns;
return $this;
}
public function getDeleteRows(): ?bool
{
return $this->deleteRows;
}
public function setDeleteRows(?bool $deleteRows): self
{
$this->deleteRows = $deleteRows;
return $this;
}
public function getSelectLockedCells(): ?bool
{
return $this->selectLockedCells;
}
public function setSelectLockedCells(?bool $selectLockedCells): self
{
$this->selectLockedCells = $selectLockedCells;
return $this;
}
public function getSort(): ?bool
{
return $this->sort;
}
public function setSort(?bool $sort): self
{
$this->sort = $sort;
return $this;
}
public function getAutoFilter(): ?bool
{
return $this->autoFilter;
}
public function setAutoFilter(?bool $autoFilter): self
{
$this->autoFilter = $autoFilter;
return $this;
}
public function getPivotTables(): ?bool
{
return $this->pivotTables;
}
public function setPivotTables(?bool $pivotTables): self
{
$this->pivotTables = $pivotTables;
return $this;
}
public function getSelectUnlockedCells(): ?bool
{
return $this->selectUnlockedCells;
}
public function setSelectUnlockedCells(?bool $selectUnlockedCells): self
{
$this->selectUnlockedCells = $selectUnlockedCells;
return $this;
}
/**
* Get hashed password.
*/
public function getPassword(): string
{
return $this->password;
}
/**
* Set Password.
*
* @param bool $alreadyHashed If the password has already been hashed, set this to true
*
* @return $this
*/
public function setPassword(string $password, bool $alreadyHashed = false): static
{
if (!$alreadyHashed) {
$salt = $this->generateSalt();
$this->setSalt($salt);
$password = PasswordHasher::hashPassword($password, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount());
}
$this->password = $password;
return $this;
}
public function setHashValue(string $password): self
{
return $this->setPassword($password, true);
}
/**
* Create a pseudorandom string.
*/
private function generateSalt(): string
{
return base64_encode(random_bytes(16));
}
/**
* Get algorithm name.
*/
public function getAlgorithm(): string
{
return $this->algorithm;
}
/**
* Set algorithm name.
*/
public function setAlgorithm(string $algorithm): self
{
return $this->setAlgorithmName($algorithm);
}
/**
* Set algorithm name.
*/
public function setAlgorithmName(string $algorithm): self
{
$this->algorithm = $algorithm;
return $this;
}
public function getSalt(): string
{
return $this->salt;
}
public function setSalt(string $salt): self
{
return $this->setSaltValue($salt);
}
public function setSaltValue(string $salt): self
{
$this->salt = $salt;
return $this;
}
/**
* Get spin count.
*/
public function getSpinCount(): int
{
return $this->spinCount;
}
/**
* Set spin count.
*/
public function setSpinCount(int $spinCount): self
{
$this->spinCount = $spinCount;
return $this;
}
/**
* Verify that the given non-hashed password can "unlock" the protection.
*/
public function verify(string $password): bool
{
if ($this->password === '') {
return true;
}
$hash = PasswordHasher::hashPassword($password, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount());
return $this->getPassword() === $hash;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
if (is_object($value)) {
$this->$key = clone $value;
} else {
$this->$key = $value;
}
}
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
class Row
{
private Worksheet $worksheet;
/**
* Row index.
*/
private int $rowIndex;
/**
* Create a new row.
*/
public function __construct(Worksheet $worksheet, int $rowIndex = 1)
{
// Set parent and row index
$this->worksheet = $worksheet;
$this->rowIndex = $rowIndex;
}
/**
* Destructor.
*/
public function __destruct()
{
unset($this->worksheet);
}
/**
* Get row index.
*/
public function getRowIndex(): int
{
return $this->rowIndex;
}
/**
* Get cell iterator.
*
* @param string $startColumn The column address at which to start iterating
* @param ?string $endColumn Optionally, the column address at which to stop iterating
*/
public function getCellIterator(string $startColumn = 'A', ?string $endColumn = null, bool $iterateOnlyExistingCells = false): RowCellIterator
{
return new RowCellIterator($this->worksheet, $this->rowIndex, $startColumn, $endColumn, $iterateOnlyExistingCells);
}
/**
* Get column iterator. Synonym for getCellIterator().
*
* @param string $startColumn The column address at which to start iterating
* @param ?string $endColumn Optionally, the column address at which to stop iterating
*/
public function getColumnIterator(string $startColumn = 'A', ?string $endColumn = null, bool $iterateOnlyExistingCells = false): RowCellIterator
{
return $this->getCellIterator($startColumn, $endColumn, $iterateOnlyExistingCells);
}
/**
* Returns a boolean true if the row contains no cells. By default, this means that no cell records exist in the
* collection for this row. false will be returned otherwise.
* This rule can be modified by passing a $definitionOfEmptyFlags value:
* 1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value
* cells, then the row will be considered empty.
* 2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty
* string value cells, then the row will be considered empty.
* 3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL
* If the only cells in the collection are null value or empty string value cells, then the row
* will be considered empty.
*
* @param int $definitionOfEmptyFlags
* Possible Flag Values are:
* CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL
* CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL
* @param string $startColumn The column address at which to start checking if cells are empty
* @param ?string $endColumn Optionally, the column address at which to stop checking if cells are empty
*/
public function isEmpty(int $definitionOfEmptyFlags = 0, string $startColumn = 'A', ?string $endColumn = null): bool
{
$nullValueCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL);
$emptyStringCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL);
$cellIterator = $this->getCellIterator($startColumn, $endColumn);
$cellIterator->setIterateOnlyExistingCells(true);
foreach ($cellIterator as $cell) {
$value = $cell->getValue();
if ($value === null && $nullValueCellIsEmpty === true) {
continue;
}
if ($value === '' && $emptyStringCellIsEmpty === true) {
continue;
}
return false;
}
return true;
}
/**
* Returns bound worksheet.
*/
public function getWorksheet(): Worksheet
{
return $this->worksheet;
}
}

View File

@@ -0,0 +1,188 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
/**
* @extends CellIterator<string>
*/
class RowCellIterator extends CellIterator
{
/**
* Current iterator position.
*/
private int $currentColumnIndex;
/**
* Row index.
*/
private int $rowIndex;
/**
* Start position.
*/
private int $startColumnIndex = 1;
/**
* End position.
*/
private int $endColumnIndex = 1;
/**
* Create a new column iterator.
*
* @param Worksheet $worksheet The worksheet to iterate over
* @param int $rowIndex The row that we want to iterate
* @param string $startColumn The column address at which to start iterating
* @param ?string $endColumn Optionally, the column address at which to stop iterating
*/
public function __construct(Worksheet $worksheet, int $rowIndex = 1, string $startColumn = 'A', ?string $endColumn = null, bool $iterateOnlyExistingCells = false)
{
// Set subject and row index
$this->worksheet = $worksheet;
$this->cellCollection = $worksheet->getCellCollection();
$this->rowIndex = $rowIndex;
$this->resetEnd($endColumn);
$this->resetStart($startColumn);
$this->setIterateOnlyExistingCells($iterateOnlyExistingCells);
}
/**
* (Re)Set the start column and the current column pointer.
*
* @param string $startColumn The column address at which to start iterating
*
* @return $this
*/
public function resetStart(string $startColumn = 'A'): static
{
$this->startColumnIndex = Coordinate::columnIndexFromString($startColumn);
$this->adjustForExistingOnlyRange();
$this->seek(Coordinate::stringFromColumnIndex($this->startColumnIndex));
return $this;
}
/**
* (Re)Set the end column.
*
* @param ?string $endColumn The column address at which to stop iterating
*
* @return $this
*/
public function resetEnd(?string $endColumn = null): static
{
$endColumn = $endColumn ?: $this->worksheet->getHighestColumn();
$this->endColumnIndex = Coordinate::columnIndexFromString($endColumn);
$this->adjustForExistingOnlyRange();
return $this;
}
/**
* Set the column pointer to the selected column.
*
* @param string $column The column address to set the current pointer at
*
* @return $this
*/
public function seek(string $column = 'A'): static
{
$columnId = Coordinate::columnIndexFromString($column);
if ($this->onlyExistingCells && !($this->cellCollection->has($column . $this->rowIndex))) {
throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist');
}
if (($columnId < $this->startColumnIndex) || ($columnId > $this->endColumnIndex)) {
throw new PhpSpreadsheetException("Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})");
}
$this->currentColumnIndex = $columnId;
return $this;
}
/**
* Rewind the iterator to the starting column.
*/
public function rewind(): void
{
$this->currentColumnIndex = $this->startColumnIndex;
}
/**
* Return the current cell in this worksheet row.
*/
public function current(): ?Cell
{
$cellAddress = Coordinate::stringFromColumnIndex($this->currentColumnIndex) . $this->rowIndex;
return $this->cellCollection->has($cellAddress)
? $this->cellCollection->get($cellAddress)
: (
$this->ifNotExists === self::IF_NOT_EXISTS_CREATE_NEW
? $this->worksheet->createNewCell($cellAddress)
: null
);
}
/**
* Return the current iterator key.
*/
public function key(): string
{
return Coordinate::stringFromColumnIndex($this->currentColumnIndex);
}
/**
* Set the iterator to its next value.
*/
public function next(): void
{
do {
++$this->currentColumnIndex;
} while (($this->onlyExistingCells) && (!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->currentColumnIndex) . $this->rowIndex)) && ($this->currentColumnIndex <= $this->endColumnIndex));
}
/**
* Set the iterator to its previous value.
*/
public function prev(): void
{
do {
--$this->currentColumnIndex;
} while (($this->onlyExistingCells) && (!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->currentColumnIndex) . $this->rowIndex)) && ($this->currentColumnIndex >= $this->startColumnIndex));
}
/**
* Indicate if more columns exist in the worksheet range of columns that we're iterating.
*/
public function valid(): bool
{
return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex;
}
/**
* Return the current iterator position.
*/
public function getCurrentColumnIndex(): int
{
return $this->currentColumnIndex;
}
/**
* Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary.
*/
protected function adjustForExistingOnlyRange(): void
{
if ($this->onlyExistingCells) {
while ((!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->startColumnIndex) . $this->rowIndex)) && ($this->startColumnIndex <= $this->endColumnIndex)) {
++$this->startColumnIndex;
}
while ((!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->endColumnIndex) . $this->rowIndex)) && ($this->endColumnIndex >= $this->startColumnIndex)) {
--$this->endColumnIndex;
}
}
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension;
class RowDimension extends Dimension
{
/**
* Row index.
*/
private ?int $rowIndex;
/**
* Row height (in pt).
*
* When this is set to a negative value, the row height should be ignored by IWriter
*/
private float $height = -1;
/**
* ZeroHeight for Row?
*/
private bool $zeroHeight = false;
/**
* Create a new RowDimension.
*
* @param ?int $index Numeric row index
*/
public function __construct(?int $index = 0)
{
// Initialise values
$this->rowIndex = $index;
// set dimension as unformatted by default
parent::__construct(null);
}
/**
* Get Row Index.
*/
public function getRowIndex(): ?int
{
return $this->rowIndex;
}
/**
* Set Row Index.
*
* @return $this
*/
public function setRowIndex(int $index): static
{
$this->rowIndex = $index;
return $this;
}
/**
* Get Row Height.
* By default, this will be in points; but this method also accepts an optional unit of measure
* argument, and will convert the value from points to the specified UoM.
* A value of -1 tells Excel to display this column in its default height.
*/
public function getRowHeight(?string $unitOfMeasure = null): float
{
return ($unitOfMeasure === null || $this->height < 0)
? $this->height
: (new CssDimension($this->height . CssDimension::UOM_POINTS))->toUnit($unitOfMeasure);
}
/**
* Set Row Height.
*
* @param float $height in points. A value of -1 tells Excel to display this column in its default height.
* By default, this will be the passed argument value; but this method also accepts an optional unit of measure
* argument, and will convert the passed argument value to points from the specified UoM
*
* @return $this
*/
public function setRowHeight(float $height, ?string $unitOfMeasure = null): static
{
$this->height = ($unitOfMeasure === null || $height < 0)
? $height
: (new CssDimension("{$height}{$unitOfMeasure}"))->height();
return $this;
}
/**
* Get ZeroHeight.
*/
public function getZeroHeight(): bool
{
return $this->zeroHeight;
}
/**
* Set ZeroHeight.
*
* @return $this
*/
public function setZeroHeight(bool $zeroHeight): static
{
$this->zeroHeight = $zeroHeight;
return $this;
}
}

View File

@@ -0,0 +1,155 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use Iterator as NativeIterator;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
/**
* @implements NativeIterator<int, Row>
*/
class RowIterator implements NativeIterator
{
/**
* Worksheet to iterate.
*/
private Worksheet $subject;
/**
* Current iterator position.
*/
private int $position = 1;
/**
* Start position.
*/
private int $startRow = 1;
/**
* End position.
*/
private int $endRow = 1;
/**
* Create a new row iterator.
*
* @param Worksheet $subject The worksheet to iterate over
* @param int $startRow The row number at which to start iterating
* @param ?int $endRow Optionally, the row number at which to stop iterating
*/
public function __construct(Worksheet $subject, int $startRow = 1, ?int $endRow = null)
{
// Set subject
$this->subject = $subject;
$this->resetEnd($endRow);
$this->resetStart($startRow);
}
public function __destruct()
{
unset($this->subject);
}
/**
* (Re)Set the start row and the current row pointer.
*
* @param int $startRow The row number at which to start iterating
*
* @return $this
*/
public function resetStart(int $startRow = 1): static
{
if ($startRow > $this->subject->getHighestRow()) {
throw new PhpSpreadsheetException(
"Start row ({$startRow}) is beyond highest row ({$this->subject->getHighestRow()})"
);
}
$this->startRow = $startRow;
if ($this->endRow < $this->startRow) {
$this->endRow = $this->startRow;
}
$this->seek($startRow);
return $this;
}
/**
* (Re)Set the end row.
*
* @param ?int $endRow The row number at which to stop iterating
*
* @return $this
*/
public function resetEnd(?int $endRow = null): static
{
$this->endRow = $endRow ?: $this->subject->getHighestRow();
return $this;
}
/**
* Set the row pointer to the selected row.
*
* @param int $row The row number to set the current pointer at
*
* @return $this
*/
public function seek(int $row = 1): static
{
if (($row < $this->startRow) || ($row > $this->endRow)) {
throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})");
}
$this->position = $row;
return $this;
}
/**
* Rewind the iterator to the starting row.
*/
public function rewind(): void
{
$this->position = $this->startRow;
}
/**
* Return the current row in this worksheet.
*/
public function current(): Row
{
return new Row($this->subject, $this->position);
}
/**
* Return the current iterator key.
*/
public function key(): int
{
return $this->position;
}
/**
* Set the iterator to its next value.
*/
public function next(): void
{
++$this->position;
}
/**
* Set the iterator to its previous value.
*/
public function prev(): void
{
--$this->position;
}
/**
* Indicate if more rows exist in the worksheet range of rows that we're iterating.
*/
public function valid(): bool
{
return $this->position <= $this->endRow && $this->position >= $this->startRow;
}
}

View File

@@ -0,0 +1,199 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class SheetView
{
// Sheet View types
const SHEETVIEW_NORMAL = 'normal';
const SHEETVIEW_PAGE_LAYOUT = 'pageLayout';
const SHEETVIEW_PAGE_BREAK_PREVIEW = 'pageBreakPreview';
private const SHEET_VIEW_TYPES = [
self::SHEETVIEW_NORMAL,
self::SHEETVIEW_PAGE_LAYOUT,
self::SHEETVIEW_PAGE_BREAK_PREVIEW,
];
/**
* ZoomScale.
*
* Valid values range from 10 to 400.
*/
private ?int $zoomScale = 100;
/**
* ZoomScaleNormal.
*
* Valid values range from 10 to 400.
*/
private ?int $zoomScaleNormal = 100;
/**
* ZoomScalePageLayoutView.
*
* Valid values range from 10 to 400.
*/
private int $zoomScalePageLayoutView = 100;
/**
* ZoomScaleSheetLayoutView.
*
* Valid values range from 10 to 400.
*/
private int $zoomScaleSheetLayoutView = 100;
/**
* ShowZeros.
*
* If true, "null" values from a calculation will be shown as "0". This is the default Excel behaviour and can be changed
* with the advanced worksheet option "Show a zero in cells that have zero value"
*/
private bool $showZeros = true;
/**
* View.
*
* Valid values range from 10 to 400.
*/
private string $sheetviewType = self::SHEETVIEW_NORMAL;
/**
* Create a new SheetView.
*/
public function __construct()
{
}
/**
* Get ZoomScale.
*/
public function getZoomScale(): ?int
{
return $this->zoomScale;
}
/**
* Set ZoomScale.
* Valid values range from 10 to 400.
*
* @return $this
*/
public function setZoomScale(?int $zoomScale): static
{
// Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface,
// but it is apparently still able to handle any scale >= 1
if ($zoomScale === null || $zoomScale >= 1) {
$this->zoomScale = $zoomScale;
} else {
throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.');
}
return $this;
}
/**
* Get ZoomScaleNormal.
*/
public function getZoomScaleNormal(): ?int
{
return $this->zoomScaleNormal;
}
/**
* Set ZoomScale.
* Valid values range from 10 to 400.
*
* @return $this
*/
public function setZoomScaleNormal(?int $zoomScaleNormal): static
{
if ($zoomScaleNormal === null || $zoomScaleNormal >= 1) {
$this->zoomScaleNormal = $zoomScaleNormal;
} else {
throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.');
}
return $this;
}
public function getZoomScalePageLayoutView(): int
{
return $this->zoomScalePageLayoutView;
}
public function setZoomScalePageLayoutView(int $zoomScalePageLayoutView): static
{
if ($zoomScalePageLayoutView >= 1) {
$this->zoomScalePageLayoutView = $zoomScalePageLayoutView;
} else {
throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.');
}
return $this;
}
public function getZoomScaleSheetLayoutView(): int
{
return $this->zoomScaleSheetLayoutView;
}
public function setZoomScaleSheetLayoutView(int $zoomScaleSheetLayoutView): static
{
if ($zoomScaleSheetLayoutView >= 1) {
$this->zoomScaleSheetLayoutView = $zoomScaleSheetLayoutView;
} else {
throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.');
}
return $this;
}
/**
* Set ShowZeroes setting.
*/
public function setShowZeros(bool $showZeros): void
{
$this->showZeros = $showZeros;
}
public function getShowZeros(): bool
{
return $this->showZeros;
}
/**
* Get View.
*/
public function getView(): string
{
return $this->sheetviewType;
}
/**
* Set View.
*
* Valid values are
* 'normal' self::SHEETVIEW_NORMAL
* 'pageLayout' self::SHEETVIEW_PAGE_LAYOUT
* 'pageBreakPreview' self::SHEETVIEW_PAGE_BREAK_PREVIEW
*
* @return $this
*/
public function setView(?string $sheetViewType): static
{
// MS Excel 2007 allows setting the view to 'normal', 'pageLayout' or 'pageBreakPreview' via the user interface
if ($sheetViewType === null) {
$sheetViewType = self::SHEETVIEW_NORMAL;
}
if (in_array($sheetViewType, self::SHEET_VIEW_TYPES)) {
$this->sheetviewType = $sheetViewType;
} else {
throw new PhpSpreadsheetException('Invalid sheetview layout type.');
}
return $this;
}
}

View File

@@ -0,0 +1,579 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
use PhpOffice\PhpSpreadsheet\Cell\CellAddress;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Table\TableStyle;
use Stringable;
class Table implements Stringable
{
/**
* Table Name.
*/
private string $name;
/**
* Show Header Row.
*/
private bool $showHeaderRow = true;
/**
* Show Totals Row.
*/
private bool $showTotalsRow = false;
/**
* Table Range.
*/
private string $range = '';
/**
* Table Worksheet.
*/
private ?Worksheet $workSheet = null;
/**
* Table allow filter.
*/
private bool $allowFilter = true;
/**
* Table Column.
*
* @var Table\Column[]
*/
private array $columns = [];
/**
* Table Style.
*/
private TableStyle $style;
/**
* Table AutoFilter.
*/
private AutoFilter $autoFilter;
/**
* Create a new Table.
*
* @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range
* A simple string containing a Cell range like 'A1:E10' is permitted
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
* or an AddressRange object.
* @param string $name (e.g. Table1)
*/
public function __construct(AddressRange|string|array $range = '', string $name = '')
{
$this->style = new TableStyle();
$this->autoFilter = new AutoFilter($range);
$this->setRange($range);
$this->setName($name);
}
/**
* Code to execute when this table is unset().
*/
public function __destruct()
{
$this->workSheet = null;
}
/**
* Get Table name.
*/
public function getName(): string
{
return $this->name;
}
/**
* Set Table name.
*
* @throws PhpSpreadsheetException
*/
public function setName(string $name): self
{
$name = trim($name);
if (!empty($name)) {
if (strlen($name) === 1 && in_array($name, ['C', 'c', 'R', 'r'])) {
throw new PhpSpreadsheetException('The table name is invalid');
}
if (StringHelper::countCharacters($name) > 255) {
throw new PhpSpreadsheetException('The table name cannot be longer than 255 characters');
}
// Check for A1 or R1C1 cell reference notation
if (
preg_match(Coordinate::A1_COORDINATE_REGEX, $name)
|| preg_match('/^R\[?\-?[0-9]*\]?C\[?\-?[0-9]*\]?$/i', $name)
) {
throw new PhpSpreadsheetException('The table name can\'t be the same as a cell reference');
}
if (!preg_match('/^[\p{L}_\\\\]/iu', $name)) {
throw new PhpSpreadsheetException('The table name must begin a name with a letter, an underscore character (_), or a backslash (\)');
}
if (!preg_match('/^[\p{L}_\\\\][\p{L}\p{M}0-9\._]+$/iu', $name)) {
throw new PhpSpreadsheetException('The table name contains invalid characters');
}
$this->checkForDuplicateTableNames($name, $this->workSheet);
$this->updateStructuredReferences($name);
}
$this->name = $name;
return $this;
}
/**
* @throws PhpSpreadsheetException
*/
private function checkForDuplicateTableNames(string $name, ?Worksheet $worksheet): void
{
// Remember that table names are case-insensitive
$tableName = StringHelper::strToLower($name);
if ($worksheet !== null && StringHelper::strToLower($this->name) !== $name) {
$spreadsheet = $worksheet->getParentOrThrow();
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
foreach ($sheet->getTableCollection() as $table) {
if (StringHelper::strToLower($table->getName()) === $tableName && $table != $this) {
throw new PhpSpreadsheetException("Spreadsheet already contains a table named '{$this->name}'");
}
}
}
}
}
private function updateStructuredReferences(string $name): void
{
if (!$this->workSheet || !$this->name) {
return;
}
// Remember that table names are case-insensitive
if (StringHelper::strToLower($this->name) !== StringHelper::strToLower($name)) {
// We need to check all formula cells that might contain fully-qualified Structured References
// that refer to this table, and update those formulae to reference the new table name
$spreadsheet = $this->workSheet->getParentOrThrow();
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
$this->updateStructuredReferencesInCells($sheet, $name);
}
$this->updateStructuredReferencesInNamedFormulae($spreadsheet, $name);
}
}
private function updateStructuredReferencesInCells(Worksheet $worksheet, string $newName): void
{
$pattern = '/' . preg_quote($this->name, '/') . '\[/mui';
foreach ($worksheet->getCoordinates(false) as $coordinate) {
$cell = $worksheet->getCell($coordinate);
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
$formula = $cell->getValueString();
if (preg_match($pattern, $formula) === 1) {
$formula = preg_replace($pattern, "{$newName}[", $formula);
$cell->setValueExplicit($formula, DataType::TYPE_FORMULA);
}
}
}
}
private function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $newName): void
{
$pattern = '/' . preg_quote($this->name, '/') . '\[/mui';
foreach ($spreadsheet->getNamedFormulae() as $namedFormula) {
$formula = $namedFormula->getValue();
if (preg_match($pattern, $formula) === 1) {
$formula = preg_replace($pattern, "{$newName}[", $formula) ?? '';
$namedFormula->setValue($formula);
}
}
}
/**
* Get show Header Row.
*/
public function getShowHeaderRow(): bool
{
return $this->showHeaderRow;
}
/**
* Set show Header Row.
*/
public function setShowHeaderRow(bool $showHeaderRow): self
{
$this->showHeaderRow = $showHeaderRow;
return $this;
}
/**
* Get show Totals Row.
*/
public function getShowTotalsRow(): bool
{
return $this->showTotalsRow;
}
/**
* Set show Totals Row.
*/
public function setShowTotalsRow(bool $showTotalsRow): self
{
$this->showTotalsRow = $showTotalsRow;
return $this;
}
/**
* Get allow filter.
* If false, autofiltering is disabled for the table, if true it is enabled.
*/
public function getAllowFilter(): bool
{
return $this->allowFilter;
}
/**
* Set show Autofiltering.
* Disabling autofiltering has the same effect as hiding the filter button on all the columns in the table.
*/
public function setAllowFilter(bool $allowFilter): self
{
$this->allowFilter = $allowFilter;
return $this;
}
/**
* Get Table Range.
*/
public function getRange(): string
{
return $this->range;
}
/**
* Set Table Cell Range.
*
* @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $range
* A simple string containing a Cell range like 'A1:E10' is permitted
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
* or an AddressRange object.
*/
public function setRange(AddressRange|string|array $range = ''): self
{
// extract coordinate
if ($range !== '') {
[, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true);
}
if (empty($range)) {
// Discard all column rules
$this->columns = [];
$this->range = '';
return $this;
}
if (!str_contains($range, ':')) {
throw new PhpSpreadsheetException('Table must be set on a range of cells.');
}
[$width, $height] = Coordinate::rangeDimension($range);
if ($width < 1 || $height < 1) {
throw new PhpSpreadsheetException('The table range must be at least 1 column and row');
}
$this->range = $range;
$this->autoFilter->setRange($range);
// Discard any column rules that are no longer valid within this range
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
foreach ($this->columns as $key => $value) {
$colIndex = Coordinate::columnIndexFromString($key);
if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) {
unset($this->columns[$key]);
}
}
return $this;
}
/**
* Set Table Cell Range to max row.
*/
public function setRangeToMaxRow(): self
{
if ($this->workSheet !== null) {
$thisrange = $this->range;
$range = (string) preg_replace('/\\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange);
if ($range !== $thisrange) {
$this->setRange($range);
}
}
return $this;
}
/**
* Get Table's Worksheet.
*/
public function getWorksheet(): ?Worksheet
{
return $this->workSheet;
}
/**
* Set Table's Worksheet.
*/
public function setWorksheet(?Worksheet $worksheet = null): self
{
if ($this->name !== '' && $worksheet !== null) {
$spreadsheet = $worksheet->getParentOrThrow();
$tableName = StringHelper::strToUpper($this->name);
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
foreach ($sheet->getTableCollection() as $table) {
if (StringHelper::strToUpper($table->getName()) === $tableName) {
throw new PhpSpreadsheetException("Workbook already contains a table named '{$this->name}'");
}
}
}
}
$this->workSheet = $worksheet;
$this->autoFilter->setParent($worksheet);
return $this;
}
/**
* Get all Table Columns.
*
* @return Table\Column[]
*/
public function getColumns(): array
{
return $this->columns;
}
/**
* Validate that the specified column is in the Table range.
*
* @param string $column Column name (e.g. A)
*
* @return int The column offset within the table range
*/
public function isColumnInRange(string $column): int
{
if (empty($this->range)) {
throw new PhpSpreadsheetException('No table range is defined.');
}
$columnIndex = Coordinate::columnIndexFromString($column);
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) {
throw new PhpSpreadsheetException('Column is outside of current table range.');
}
return $columnIndex - $rangeStart[0];
}
/**
* Get a specified Table Column Offset within the defined Table range.
*
* @param string $column Column name (e.g. A)
*
* @return int The offset of the specified column within the table range
*/
public function getColumnOffset(string $column): int
{
return $this->isColumnInRange($column);
}
/**
* Get a specified Table Column.
*
* @param string $column Column name (e.g. A)
*/
public function getColumn(string $column): Table\Column
{
$this->isColumnInRange($column);
if (!isset($this->columns[$column])) {
$this->columns[$column] = new Table\Column($column, $this);
}
return $this->columns[$column];
}
/**
* Get a specified Table Column by it's offset.
*
* @param int $columnOffset Column offset within range (starting from 0)
*/
public function getColumnByOffset(int $columnOffset): Table\Column
{
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
$pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $columnOffset);
return $this->getColumn($pColumn);
}
/**
* Set Table.
*
* @param string|Table\Column $columnObjectOrString
* A simple string containing a Column ID like 'A' is permitted
*/
public function setColumn(string|Table\Column $columnObjectOrString): self
{
if ((is_string($columnObjectOrString)) && (!empty($columnObjectOrString))) {
$column = $columnObjectOrString;
} elseif (is_object($columnObjectOrString) && ($columnObjectOrString instanceof Table\Column)) {
$column = $columnObjectOrString->getColumnIndex();
} else {
throw new PhpSpreadsheetException('Column is not within the table range.');
}
$this->isColumnInRange($column);
if (is_string($columnObjectOrString)) {
$this->columns[$columnObjectOrString] = new Table\Column($columnObjectOrString, $this);
} else {
$columnObjectOrString->setTable($this);
$this->columns[$column] = $columnObjectOrString;
}
ksort($this->columns);
return $this;
}
/**
* Clear a specified Table Column.
*
* @param string $column Column name (e.g. A)
*/
public function clearColumn(string $column): self
{
$this->isColumnInRange($column);
if (isset($this->columns[$column])) {
unset($this->columns[$column]);
}
return $this;
}
/**
* Shift an Table Column Rule to a different column.
*
* Note: This method bypasses validation of the destination column to ensure it is within this Table range.
* Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value.
* Use with caution.
*
* @param string $fromColumn Column name (e.g. A)
* @param string $toColumn Column name (e.g. B)
*/
public function shiftColumn(string $fromColumn, string $toColumn): self
{
$fromColumn = strtoupper($fromColumn);
$toColumn = strtoupper($toColumn);
if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) {
$this->columns[$fromColumn]->setTable();
$this->columns[$fromColumn]->setColumnIndex($toColumn);
$this->columns[$toColumn] = $this->columns[$fromColumn];
$this->columns[$toColumn]->setTable($this);
unset($this->columns[$fromColumn]);
ksort($this->columns);
}
return $this;
}
/**
* Get table Style.
*/
public function getStyle(): TableStyle
{
return $this->style;
}
/**
* Set table Style.
*/
public function setStyle(TableStyle $style): self
{
$this->style = $style;
return $this;
}
/**
* Get AutoFilter.
*/
public function getAutoFilter(): AutoFilter
{
return $this->autoFilter;
}
/**
* Set AutoFilter.
*/
public function setAutoFilter(AutoFilter $autoFilter): self
{
$this->autoFilter = $autoFilter;
return $this;
}
/**
* Implement PHP __clone to create a deep clone, not just a shallow copy.
*/
public function __clone()
{
$vars = get_object_vars($this);
foreach ($vars as $key => $value) {
if (is_object($value)) {
if ($key === 'workSheet') {
// Detach from worksheet
$this->{$key} = null;
} else {
$this->{$key} = clone $value;
}
} elseif ((is_array($value)) && ($key === 'columns')) {
// The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\Table objects
$this->{$key} = [];
foreach ($value as $k => $v) {
$this->{$key}[$k] = clone $v;
// attach the new cloned Column to this new cloned Table object
$this->{$key}[$k]->setTable($this);
}
} else {
$this->{$key} = $value;
}
}
}
/**
* toString method replicates previous behavior by returning the range if object is
* referenced as a property of its worksheet.
*/
public function __toString(): string
{
return (string) $this->range;
}
}

View File

@@ -0,0 +1,240 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet\Table;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Column
{
/**
* Table Column Index.
*/
private string $columnIndex;
/**
* Show Filter Button.
*/
private bool $showFilterButton = true;
/**
* Total Row Label.
*/
private ?string $totalsRowLabel = null;
/**
* Total Row Function.
*/
private ?string $totalsRowFunction = null;
/**
* Total Row Formula.
*/
private ?string $totalsRowFormula = null;
/**
* Column Formula.
*/
private ?string $columnFormula = null;
/**
* Table.
*/
private ?Table $table;
/**
* Create a new Column.
*
* @param string $column Column (e.g. A)
* @param ?Table $table Table for this column
*/
public function __construct(string $column, ?Table $table = null)
{
$this->columnIndex = $column;
$this->table = $table;
}
/**
* Get Table column index as string eg: 'A'.
*/
public function getColumnIndex(): string
{
return $this->columnIndex;
}
/**
* Set Table column index as string eg: 'A'.
*
* @param string $column Column (e.g. A)
*/
public function setColumnIndex(string $column): self
{
// Uppercase coordinate
$column = strtoupper($column);
if ($this->table !== null) {
$this->table->isColumnInRange($column);
}
$this->columnIndex = $column;
return $this;
}
/**
* Get show Filter Button.
*/
public function getShowFilterButton(): bool
{
return $this->showFilterButton;
}
/**
* Set show Filter Button.
*/
public function setShowFilterButton(bool $showFilterButton): self
{
$this->showFilterButton = $showFilterButton;
return $this;
}
/**
* Get total Row Label.
*/
public function getTotalsRowLabel(): ?string
{
return $this->totalsRowLabel;
}
/**
* Set total Row Label.
*/
public function setTotalsRowLabel(string $totalsRowLabel): self
{
$this->totalsRowLabel = $totalsRowLabel;
return $this;
}
/**
* Get total Row Function.
*/
public function getTotalsRowFunction(): ?string
{
return $this->totalsRowFunction;
}
/**
* Set total Row Function.
*/
public function setTotalsRowFunction(string $totalsRowFunction): self
{
$this->totalsRowFunction = $totalsRowFunction;
return $this;
}
/**
* Get total Row Formula.
*/
public function getTotalsRowFormula(): ?string
{
return $this->totalsRowFormula;
}
/**
* Set total Row Formula.
*/
public function setTotalsRowFormula(string $totalsRowFormula): self
{
$this->totalsRowFormula = $totalsRowFormula;
return $this;
}
/**
* Get column Formula.
*/
public function getColumnFormula(): ?string
{
return $this->columnFormula;
}
/**
* Set column Formula.
*/
public function setColumnFormula(string $columnFormula): self
{
$this->columnFormula = $columnFormula;
return $this;
}
/**
* Get this Column's Table.
*/
public function getTable(): ?Table
{
return $this->table;
}
/**
* Set this Column's Table.
*/
public function setTable(?Table $table = null): self
{
$this->table = $table;
return $this;
}
public static function updateStructuredReferences(?Worksheet $workSheet, ?string $oldTitle, ?string $newTitle): void
{
if ($workSheet === null || $oldTitle === null || $oldTitle === '' || $newTitle === null) {
return;
}
// Remember that table headings are case-insensitive
if (StringHelper::strToLower($oldTitle) !== StringHelper::strToLower($newTitle)) {
// We need to check all formula cells that might contain Structured References that refer
// to this column, and update those formulae to reference the new column text
$spreadsheet = $workSheet->getParentOrThrow();
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
self::updateStructuredReferencesInCells($sheet, $oldTitle, $newTitle);
}
self::updateStructuredReferencesInNamedFormulae($spreadsheet, $oldTitle, $newTitle);
}
}
private static function updateStructuredReferencesInCells(Worksheet $worksheet, string $oldTitle, string $newTitle): void
{
$pattern = '/\[(@?)' . preg_quote($oldTitle, '/') . '\]/mui';
foreach ($worksheet->getCoordinates(false) as $coordinate) {
$cell = $worksheet->getCell($coordinate);
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
$formula = $cell->getValueString();
if (preg_match($pattern, $formula) === 1) {
$formula = preg_replace($pattern, "[$1{$newTitle}]", $formula);
$cell->setValueExplicit($formula, DataType::TYPE_FORMULA);
}
}
}
}
private static function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $oldTitle, string $newTitle): void
{
$pattern = '/\[(@?)' . preg_quote($oldTitle, '/') . '\]/mui';
foreach ($spreadsheet->getNamedFormulae() as $namedFormula) {
$formula = $namedFormula->getValue();
if (preg_match($pattern, $formula) === 1) {
$formula = preg_replace($pattern, "[$1{$newTitle}]", $formula) ?? '';
$namedFormula->setValue($formula);
}
}
}
}

View File

@@ -0,0 +1,218 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet\Table;
use PhpOffice\PhpSpreadsheet\Worksheet\Table;
class TableStyle
{
const TABLE_STYLE_NONE = '';
const TABLE_STYLE_LIGHT1 = 'TableStyleLight1';
const TABLE_STYLE_LIGHT2 = 'TableStyleLight2';
const TABLE_STYLE_LIGHT3 = 'TableStyleLight3';
const TABLE_STYLE_LIGHT4 = 'TableStyleLight4';
const TABLE_STYLE_LIGHT5 = 'TableStyleLight5';
const TABLE_STYLE_LIGHT6 = 'TableStyleLight6';
const TABLE_STYLE_LIGHT7 = 'TableStyleLight7';
const TABLE_STYLE_LIGHT8 = 'TableStyleLight8';
const TABLE_STYLE_LIGHT9 = 'TableStyleLight9';
const TABLE_STYLE_LIGHT10 = 'TableStyleLight10';
const TABLE_STYLE_LIGHT11 = 'TableStyleLight11';
const TABLE_STYLE_LIGHT12 = 'TableStyleLight12';
const TABLE_STYLE_LIGHT13 = 'TableStyleLight13';
const TABLE_STYLE_LIGHT14 = 'TableStyleLight14';
const TABLE_STYLE_LIGHT15 = 'TableStyleLight15';
const TABLE_STYLE_LIGHT16 = 'TableStyleLight16';
const TABLE_STYLE_LIGHT17 = 'TableStyleLight17';
const TABLE_STYLE_LIGHT18 = 'TableStyleLight18';
const TABLE_STYLE_LIGHT19 = 'TableStyleLight19';
const TABLE_STYLE_LIGHT20 = 'TableStyleLight20';
const TABLE_STYLE_LIGHT21 = 'TableStyleLight21';
const TABLE_STYLE_MEDIUM1 = 'TableStyleMedium1';
const TABLE_STYLE_MEDIUM2 = 'TableStyleMedium2';
const TABLE_STYLE_MEDIUM3 = 'TableStyleMedium3';
const TABLE_STYLE_MEDIUM4 = 'TableStyleMedium4';
const TABLE_STYLE_MEDIUM5 = 'TableStyleMedium5';
const TABLE_STYLE_MEDIUM6 = 'TableStyleMedium6';
const TABLE_STYLE_MEDIUM7 = 'TableStyleMedium7';
const TABLE_STYLE_MEDIUM8 = 'TableStyleMedium8';
const TABLE_STYLE_MEDIUM9 = 'TableStyleMedium9';
const TABLE_STYLE_MEDIUM10 = 'TableStyleMedium10';
const TABLE_STYLE_MEDIUM11 = 'TableStyleMedium11';
const TABLE_STYLE_MEDIUM12 = 'TableStyleMedium12';
const TABLE_STYLE_MEDIUM13 = 'TableStyleMedium13';
const TABLE_STYLE_MEDIUM14 = 'TableStyleMedium14';
const TABLE_STYLE_MEDIUM15 = 'TableStyleMedium15';
const TABLE_STYLE_MEDIUM16 = 'TableStyleMedium16';
const TABLE_STYLE_MEDIUM17 = 'TableStyleMedium17';
const TABLE_STYLE_MEDIUM18 = 'TableStyleMedium18';
const TABLE_STYLE_MEDIUM19 = 'TableStyleMedium19';
const TABLE_STYLE_MEDIUM20 = 'TableStyleMedium20';
const TABLE_STYLE_MEDIUM21 = 'TableStyleMedium21';
const TABLE_STYLE_MEDIUM22 = 'TableStyleMedium22';
const TABLE_STYLE_MEDIUM23 = 'TableStyleMedium23';
const TABLE_STYLE_MEDIUM24 = 'TableStyleMedium24';
const TABLE_STYLE_MEDIUM25 = 'TableStyleMedium25';
const TABLE_STYLE_MEDIUM26 = 'TableStyleMedium26';
const TABLE_STYLE_MEDIUM27 = 'TableStyleMedium27';
const TABLE_STYLE_MEDIUM28 = 'TableStyleMedium28';
const TABLE_STYLE_DARK1 = 'TableStyleDark1';
const TABLE_STYLE_DARK2 = 'TableStyleDark2';
const TABLE_STYLE_DARK3 = 'TableStyleDark3';
const TABLE_STYLE_DARK4 = 'TableStyleDark4';
const TABLE_STYLE_DARK5 = 'TableStyleDark5';
const TABLE_STYLE_DARK6 = 'TableStyleDark6';
const TABLE_STYLE_DARK7 = 'TableStyleDark7';
const TABLE_STYLE_DARK8 = 'TableStyleDark8';
const TABLE_STYLE_DARK9 = 'TableStyleDark9';
const TABLE_STYLE_DARK10 = 'TableStyleDark10';
const TABLE_STYLE_DARK11 = 'TableStyleDark11';
/**
* Theme.
*/
private string $theme;
/**
* Show First Column.
*/
private bool $showFirstColumn = false;
/**
* Show Last Column.
*/
private bool $showLastColumn = false;
/**
* Show Row Stripes.
*/
private bool $showRowStripes = false;
/**
* Show Column Stripes.
*/
private bool $showColumnStripes = false;
/**
* Table.
*/
private ?Table $table = null;
/**
* Create a new Table Style.
*
* @param string $theme (e.g. TableStyle::TABLE_STYLE_MEDIUM2)
*/
public function __construct(string $theme = self::TABLE_STYLE_MEDIUM2)
{
$this->theme = $theme;
}
/**
* Get theme.
*/
public function getTheme(): string
{
return $this->theme;
}
/**
* Set theme.
*/
public function setTheme(string $theme): self
{
$this->theme = $theme;
return $this;
}
/**
* Get show First Column.
*/
public function getShowFirstColumn(): bool
{
return $this->showFirstColumn;
}
/**
* Set show First Column.
*/
public function setShowFirstColumn(bool $showFirstColumn): self
{
$this->showFirstColumn = $showFirstColumn;
return $this;
}
/**
* Get show Last Column.
*/
public function getShowLastColumn(): bool
{
return $this->showLastColumn;
}
/**
* Set show Last Column.
*/
public function setShowLastColumn(bool $showLastColumn): self
{
$this->showLastColumn = $showLastColumn;
return $this;
}
/**
* Get show Row Stripes.
*/
public function getShowRowStripes(): bool
{
return $this->showRowStripes;
}
/**
* Set show Row Stripes.
*/
public function setShowRowStripes(bool $showRowStripes): self
{
$this->showRowStripes = $showRowStripes;
return $this;
}
/**
* Get show Column Stripes.
*/
public function getShowColumnStripes(): bool
{
return $this->showColumnStripes;
}
/**
* Set show Column Stripes.
*/
public function setShowColumnStripes(bool $showColumnStripes): self
{
$this->showColumnStripes = $showColumnStripes;
return $this;
}
/**
* Get this Style's Table.
*/
public function getTable(): ?Table
{
return $this->table;
}
/**
* Set this Style's Table.
*/
public function setTable(?Table $table = null): self
{
$this->table = $table;
return $this;
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
use PhpOffice\PhpSpreadsheet\Cell\CellAddress;
use PhpOffice\PhpSpreadsheet\Cell\CellRange;
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
class Validations
{
/**
* Validate a cell address.
*
* @param null|array{0: int, 1: int}|CellAddress|string $cellAddress Coordinate of the cell as a string, eg: 'C5';
* or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
*/
public static function validateCellAddress(null|CellAddress|string|array $cellAddress): string
{
if (is_string($cellAddress)) {
[$worksheet, $address] = Worksheet::extractSheetTitle($cellAddress, true);
// if (!empty($worksheet) && $worksheet !== $this->getTitle()) {
// throw new Exception('Reference is not for this worksheet');
// }
return empty($worksheet) ? strtoupper("$address") : $worksheet . '!' . strtoupper("$address");
}
if (is_array($cellAddress)) {
$cellAddress = CellAddress::fromColumnRowArray($cellAddress);
}
return (string) $cellAddress;
}
/**
* Validate a cell address or cell range.
*
* @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|CellAddress|int|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
* or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]),
* or as a CellAddress or AddressRange object.
*/
public static function validateCellOrCellRange(AddressRange|CellAddress|int|string|array $cellRange): string
{
if (is_string($cellRange) || is_numeric($cellRange)) {
// Convert a single column reference like 'A' to 'A:A',
// a single row reference like '1' to '1:1'
$cellRange = (string) preg_replace('/^([A-Z]+|\d+)$/', '${1}:${1}', (string) $cellRange);
} elseif (is_object($cellRange) && $cellRange instanceof CellAddress) {
$cellRange = new CellRange($cellRange, $cellRange);
}
return self::validateCellRange($cellRange);
}
private const SETMAXROW = '${1}1:${2}' . AddressRange::MAX_ROW;
private const SETMAXCOL = 'A${1}:' . AddressRange::MAX_COLUMN . '${2}';
/**
* Validate a cell range.
*
* @param AddressRange<CellAddress>|array{0: int, 1: int, 2: int, 3: int}|array{0: int, 1: int}|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
* or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]),
* or as an AddressRange object.
*/
public static function validateCellRange(AddressRange|string|array $cellRange): string
{
if (is_string($cellRange)) {
[$worksheet, $addressRange] = Worksheet::extractSheetTitle($cellRange, true);
// Convert Column ranges like 'A:C' to 'A1:C1048576'
// or Row ranges like '1:3' to 'A1:XFD3'
$addressRange = (string) preg_replace(
['/^([A-Z]+):([A-Z]+)$/i', '/^(\\d+):(\\d+)$/'],
[self::SETMAXROW, self::SETMAXCOL],
$addressRange ?? ''
);
return empty($worksheet) ? strtoupper($addressRange) : $worksheet . '!' . strtoupper($addressRange);
}
if (is_array($cellRange)) {
switch (count($cellRange)) {
case 4:
$from = [$cellRange[0], $cellRange[1]];
$to = [$cellRange[2], $cellRange[3]];
break;
case 2:
$from = [$cellRange[0], $cellRange[1]];
$to = [$cellRange[0], $cellRange[1]];
break;
default:
throw new SpreadsheetException('CellRange array length must be 2 or 4');
}
$cellRange = new CellRange(CellAddress::fromColumnRowArray($from), CellAddress::fromColumnRowArray($to));
}
return (string) $cellRange;
}
public static function definedNameToCoordinate(string $coordinate, Worksheet $worksheet): string
{
// Uppercase coordinate
$coordinate = strtoupper($coordinate);
// Eliminate leading equal sign
$testCoordinate = (string) preg_replace('/^=/', '', $coordinate);
$defined = $worksheet->getParentOrThrow()->getDefinedName($testCoordinate, $worksheet);
if ($defined !== null) {
if ($defined->getWorksheet() === $worksheet && !$defined->isFormula()) {
$coordinate = (string) preg_replace('/^=/', '', $defined->getValue());
}
}
return $coordinate;
}
}

File diff suppressed because it is too large Load Diff