updated-packages

This commit is contained in:
RafficMohammed
2023-01-08 00:13:22 +05:30
parent 3ff7df7487
commit da241bacb6
12659 changed files with 563377 additions and 510538 deletions

View File

@@ -0,0 +1,871 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\DateTime;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\Shared\Date;
class AutoFilter
{
/**
* Autofilter Worksheet.
*
* @var Worksheet
*/
private $workSheet;
/**
* Autofilter Range.
*
* @var string
*/
private $range = '';
/**
* Autofilter Column Ruleset.
*
* @var AutoFilter\Column[]
*/
private $columns = [];
/**
* Create a new AutoFilter.
*
* @param string $pRange Cell range (i.e. A1:E10)
* @param Worksheet $pSheet
*/
public function __construct($pRange = '', ?Worksheet $pSheet = null)
{
$this->range = $pRange;
$this->workSheet = $pSheet;
}
/**
* Get AutoFilter Parent Worksheet.
*
* @return Worksheet
*/
public function getParent()
{
return $this->workSheet;
}
/**
* Set AutoFilter Parent Worksheet.
*
* @param Worksheet $pSheet
*
* @return $this
*/
public function setParent(?Worksheet $pSheet = null)
{
$this->workSheet = $pSheet;
return $this;
}
/**
* Get AutoFilter Range.
*
* @return string
*/
public function getRange()
{
return $this->range;
}
/**
* Set AutoFilter Range.
*
* @param string $pRange Cell range (i.e. A1:E10)
*
* @return $this
*/
public function setRange($pRange)
{
// extract coordinate
[$worksheet, $pRange] = Worksheet::extractSheetTitle($pRange, true);
if (strpos($pRange, ':') !== false) {
$this->range = $pRange;
} elseif (empty($pRange)) {
$this->range = '';
} else {
throw new PhpSpreadsheetException('Autofilter must be set on a range of cells.');
}
if (empty($pRange)) {
// Discard all column rules
$this->columns = [];
} else {
// 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;
}
/**
* Get all AutoFilter Columns.
*
* @return AutoFilter\Column[]
*/
public function getColumns()
{
return $this->columns;
}
/**
* Validate that the specified column is in the AutoFilter range.
*
* @param string $column Column name (e.g. A)
*
* @return int The column offset within the autofilter range
*/
public function testColumnInRange($column)
{
if (empty($this->range)) {
throw new PhpSpreadsheetException('No autofilter 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 autofilter range.');
}
return $columnIndex - $rangeStart[0];
}
/**
* Get a specified AutoFilter Column Offset within the defined AutoFilter range.
*
* @param string $pColumn Column name (e.g. A)
*
* @return int The offset of the specified column within the autofilter range
*/
public function getColumnOffset($pColumn)
{
return $this->testColumnInRange($pColumn);
}
/**
* Get a specified AutoFilter Column.
*
* @param string $pColumn Column name (e.g. A)
*
* @return AutoFilter\Column
*/
public function getColumn($pColumn)
{
$this->testColumnInRange($pColumn);
if (!isset($this->columns[$pColumn])) {
$this->columns[$pColumn] = new AutoFilter\Column($pColumn, $this);
}
return $this->columns[$pColumn];
}
/**
* Get a specified AutoFilter Column by it's offset.
*
* @param int $pColumnOffset Column offset within range (starting from 0)
*
* @return AutoFilter\Column
*/
public function getColumnByOffset($pColumnOffset)
{
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
$pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $pColumnOffset);
return $this->getColumn($pColumn);
}
/**
* Set AutoFilter.
*
* @param AutoFilter\Column|string $pColumn
* A simple string containing a Column ID like 'A' is permitted
*
* @return $this
*/
public function setColumn($pColumn)
{
if ((is_string($pColumn)) && (!empty($pColumn))) {
$column = $pColumn;
} elseif (is_object($pColumn) && ($pColumn instanceof AutoFilter\Column)) {
$column = $pColumn->getColumnIndex();
} else {
throw new PhpSpreadsheetException('Column is not within the autofilter range.');
}
$this->testColumnInRange($column);
if (is_string($pColumn)) {
$this->columns[$pColumn] = new AutoFilter\Column($pColumn, $this);
} elseif (is_object($pColumn) && ($pColumn instanceof AutoFilter\Column)) {
$pColumn->setParent($this);
$this->columns[$column] = $pColumn;
}
ksort($this->columns);
return $this;
}
/**
* Clear a specified AutoFilter Column.
*
* @param string $pColumn Column name (e.g. A)
*
* @return $this
*/
public function clearColumn($pColumn)
{
$this->testColumnInRange($pColumn);
if (isset($this->columns[$pColumn])) {
unset($this->columns[$pColumn]);
}
return $this;
}
/**
* Shift an AutoFilter Column Rule to a different column.
*
* Note: This method bypasses validation of the destination column to ensure it is within this AutoFilter 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)
*
* @return $this
*/
public function shiftColumn($fromColumn, $toColumn)
{
$fromColumn = strtoupper($fromColumn);
$toColumn = strtoupper($toColumn);
if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) {
$this->columns[$fromColumn]->setParent();
$this->columns[$fromColumn]->setColumnIndex($toColumn);
$this->columns[$toColumn] = $this->columns[$fromColumn];
$this->columns[$toColumn]->setParent($this);
unset($this->columns[$fromColumn]);
ksort($this->columns);
}
return $this;
}
/**
* Test if cell value is in the defined set of values.
*
* @param mixed $cellValue
* @param mixed[] $dataSet
*
* @return bool
*/
private static function filterTestInSimpleDataSet($cellValue, $dataSet)
{
$dataSetValues = $dataSet['filterValues'];
$blanks = $dataSet['blanks'];
if (($cellValue == '') || ($cellValue === null)) {
return $blanks;
}
return in_array($cellValue, $dataSetValues);
}
/**
* Test if cell value is in the defined set of Excel date values.
*
* @param mixed $cellValue
* @param mixed[] $dataSet
*
* @return bool
*/
private static function filterTestInDateGroupSet($cellValue, $dataSet)
{
$dateSet = $dataSet['filterValues'];
$blanks = $dataSet['blanks'];
if (($cellValue == '') || ($cellValue === null)) {
return $blanks;
}
if (is_numeric($cellValue)) {
$dateValue = Date::excelToTimestamp($cellValue);
if ($cellValue < 1) {
// Just the time part
$dtVal = date('His', $dateValue);
$dateSet = $dateSet['time'];
} elseif ($cellValue == floor($cellValue)) {
// Just the date part
$dtVal = date('Ymd', $dateValue);
$dateSet = $dateSet['date'];
} else {
// date and time parts
$dtVal = date('YmdHis', $dateValue);
$dateSet = $dateSet['dateTime'];
}
foreach ($dateSet as $dateValue) {
// Use of substr to extract value at the appropriate group level
if (substr($dtVal, 0, strlen($dateValue)) == $dateValue) {
return true;
}
}
}
return false;
}
/**
* Test if cell value is within a set of values defined by a ruleset.
*
* @param mixed $cellValue
* @param mixed[] $ruleSet
*
* @return bool
*/
private static function filterTestInCustomDataSet($cellValue, $ruleSet)
{
$dataSet = $ruleSet['filterRules'];
$join = $ruleSet['join'];
$customRuleForBlanks = $ruleSet['customRuleForBlanks'] ?? false;
if (!$customRuleForBlanks) {
// Blank cells are always ignored, so return a FALSE
if (($cellValue == '') || ($cellValue === null)) {
return false;
}
}
$returnVal = ($join == AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND);
foreach ($dataSet as $rule) {
$retVal = false;
if (is_numeric($rule['value'])) {
// Numeric values are tested using the appropriate operator
switch ($rule['operator']) {
case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL:
$retVal = ($cellValue == $rule['value']);
break;
case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL:
$retVal = ($cellValue != $rule['value']);
break;
case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN:
$retVal = ($cellValue > $rule['value']);
break;
case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL:
$retVal = ($cellValue >= $rule['value']);
break;
case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN:
$retVal = ($cellValue < $rule['value']);
break;
case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL:
$retVal = ($cellValue <= $rule['value']);
break;
}
} elseif ($rule['value'] == '') {
switch ($rule['operator']) {
case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL:
$retVal = (($cellValue == '') || ($cellValue === null));
break;
case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL:
$retVal = (($cellValue != '') && ($cellValue !== null));
break;
default:
$retVal = true;
break;
}
} else {
// String values are always tested for equality, factoring in for wildcards (hence a regexp test)
$retVal = preg_match('/^' . $rule['value'] . '$/i', $cellValue);
}
// If there are multiple conditions, then we need to test both using the appropriate join operator
switch ($join) {
case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR:
$returnVal = $returnVal || $retVal;
// Break as soon as we have a TRUE match for OR joins,
// to avoid unnecessary additional code execution
if ($returnVal) {
return $returnVal;
}
break;
case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND:
$returnVal = $returnVal && $retVal;
break;
}
}
return $returnVal;
}
/**
* Test if cell date value is matches a set of values defined by a set of months.
*
* @param mixed $cellValue
* @param mixed[] $monthSet
*
* @return bool
*/
private static function filterTestInPeriodDateSet($cellValue, $monthSet)
{
// Blank cells are always ignored, so return a FALSE
if (($cellValue == '') || ($cellValue === null)) {
return false;
}
if (is_numeric($cellValue)) {
$dateValue = date('m', Date::excelToTimestamp($cellValue));
if (in_array($dateValue, $monthSet)) {
return true;
}
}
return false;
}
/**
* Search/Replace arrays to convert Excel wildcard syntax to a regexp syntax for preg_matching.
*
* @var array
*/
private static $fromReplace = ['\*', '\?', '~~', '~.*', '~.?'];
private static $toReplace = ['.*', '.', '~', '\*', '\?'];
/**
* Convert a dynamic rule daterange to a custom filter range expression for ease of calculation.
*
* @param string $dynamicRuleType
* @param AutoFilter\Column $filterColumn
*
* @return mixed[]
*/
private function dynamicFilterDateRange($dynamicRuleType, &$filterColumn)
{
$rDateType = Functions::getReturnDateType();
Functions::setReturnDateType(Functions::RETURNDATE_PHP_NUMERIC);
$val = $maxVal = null;
$ruleValues = [];
$baseDate = DateTime::DATENOW();
// Calculate start/end dates for the required date range based on current date
switch ($dynamicRuleType) {
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK:
$baseDate = strtotime('-7 days', $baseDate);
break;
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK:
$baseDate = strtotime('-7 days', $baseDate);
break;
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH:
$baseDate = strtotime('-1 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
break;
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH:
$baseDate = strtotime('+1 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
break;
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER:
$baseDate = strtotime('-3 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
break;
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER:
$baseDate = strtotime('+3 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
break;
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR:
$baseDate = strtotime('-1 year', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
break;
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR:
$baseDate = strtotime('+1 year', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
break;
}
switch ($dynamicRuleType) {
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TODAY:
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY:
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW:
$maxVal = (int) Date::PHPtoExcel(strtotime('+1 day', $baseDate));
$val = (int) Date::PHPToExcel($baseDate);
break;
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE:
$maxVal = (int) Date::PHPtoExcel(strtotime('+1 day', $baseDate));
$val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1, date('Y', $baseDate)));
break;
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR:
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR:
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR:
$maxVal = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 31, 12, date('Y', $baseDate)));
++$maxVal;
$val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1, date('Y', $baseDate)));
break;
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER:
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER:
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER:
$thisMonth = date('m', $baseDate);
$thisQuarter = floor(--$thisMonth / 3);
$maxVal = (int) Date::PHPtoExcel(gmmktime(0, 0, 0, date('t', $baseDate), (1 + $thisQuarter) * 3, date('Y', $baseDate)));
++$maxVal;
$val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1 + $thisQuarter * 3, date('Y', $baseDate)));
break;
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH:
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH:
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH:
$maxVal = (int) Date::PHPtoExcel(gmmktime(0, 0, 0, date('t', $baseDate), date('m', $baseDate), date('Y', $baseDate)));
++$maxVal;
$val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
break;
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK:
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK:
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK:
$dayOfWeek = date('w', $baseDate);
$val = (int) Date::PHPToExcel($baseDate) - $dayOfWeek;
$maxVal = $val + 7;
break;
}
switch ($dynamicRuleType) {
// Adjust Today dates for Yesterday and Tomorrow
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY:
--$maxVal;
--$val;
break;
case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW:
++$maxVal;
++$val;
break;
}
// Set the filter column rule attributes ready for writing
$filterColumn->setAttributes(['val' => $val, 'maxVal' => $maxVal]);
// Set the rules for identifying rows for hide/show
$ruleValues[] = ['operator' => AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, 'value' => $val];
$ruleValues[] = ['operator' => AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN, 'value' => $maxVal];
Functions::setReturnDateType($rDateType);
return ['method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND]];
}
private function calculateTopTenValue($columnID, $startRow, $endRow, $ruleType, $ruleValue)
{
$range = $columnID . $startRow . ':' . $columnID . $endRow;
$dataValues = Functions::flattenArray($this->workSheet->rangeToArray($range, null, true, false));
$dataValues = array_filter($dataValues);
if ($ruleType == AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) {
rsort($dataValues);
} else {
sort($dataValues);
}
return array_pop(array_slice($dataValues, 0, $ruleValue));
}
/**
* Apply the AutoFilter rules to the AutoFilter Range.
*
* @return $this
*/
public function showHideRows()
{
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
// The heading row should always be visible
$this->workSheet->getRowDimension($rangeStart[1])->setVisible(true);
$columnFilterTests = [];
foreach ($this->columns as $columnID => $filterColumn) {
$rules = $filterColumn->getRules();
switch ($filterColumn->getFilterType()) {
case AutoFilter\Column::AUTOFILTER_FILTERTYPE_FILTER:
$ruleType = null;
$ruleValues = [];
// Build a list of the filter value selections
foreach ($rules as $rule) {
$ruleType = $rule->getRuleType();
$ruleValues[] = $rule->getValue();
}
// Test if we want to include blanks in our filter criteria
$blanks = false;
$ruleDataSet = array_filter($ruleValues);
if (count($ruleValues) != count($ruleDataSet)) {
$blanks = true;
}
if ($ruleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_FILTER) {
// Filter on absolute values
$columnFilterTests[$columnID] = [
'method' => 'filterTestInSimpleDataSet',
'arguments' => ['filterValues' => $ruleDataSet, 'blanks' => $blanks],
];
} else {
// Filter on date group values
$arguments = [
'date' => [],
'time' => [],
'dateTime' => [],
];
foreach ($ruleDataSet as $ruleValue) {
$date = $time = '';
if (
(isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR])) &&
($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR] !== '')
) {
$date .= sprintf('%04d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR]);
}
if (
(isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH])) &&
($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH] != '')
) {
$date .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH]);
}
if (
(isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY])) &&
($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY] !== '')
) {
$date .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY]);
}
if (
(isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR])) &&
($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR] !== '')
) {
$time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR]);
}
if (
(isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE])) &&
($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE] !== '')
) {
$time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE]);
}
if (
(isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND])) &&
($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND] !== '')
) {
$time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND]);
}
$dateTime = $date . $time;
$arguments['date'][] = $date;
$arguments['time'][] = $time;
$arguments['dateTime'][] = $dateTime;
}
// Remove empty elements
$arguments['date'] = array_filter($arguments['date']);
$arguments['time'] = array_filter($arguments['time']);
$arguments['dateTime'] = array_filter($arguments['dateTime']);
$columnFilterTests[$columnID] = [
'method' => 'filterTestInDateGroupSet',
'arguments' => ['filterValues' => $arguments, 'blanks' => $blanks],
];
}
break;
case AutoFilter\Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER:
$customRuleForBlanks = false;
$ruleValues = [];
// Build a list of the filter value selections
foreach ($rules as $rule) {
$ruleValue = $rule->getValue();
if (!is_numeric($ruleValue)) {
// Convert to a regexp allowing for regexp reserved characters, wildcards and escaped wildcards
$ruleValue = preg_quote($ruleValue);
$ruleValue = str_replace(self::$fromReplace, self::$toReplace, $ruleValue);
if (trim($ruleValue) == '') {
$customRuleForBlanks = true;
$ruleValue = trim($ruleValue);
}
}
$ruleValues[] = ['operator' => $rule->getOperator(), 'value' => $ruleValue];
}
$join = $filterColumn->getJoin();
$columnFilterTests[$columnID] = [
'method' => 'filterTestInCustomDataSet',
'arguments' => ['filterRules' => $ruleValues, 'join' => $join, 'customRuleForBlanks' => $customRuleForBlanks],
];
break;
case AutoFilter\Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER:
$ruleValues = [];
foreach ($rules as $rule) {
// We should only ever have one Dynamic Filter Rule anyway
$dynamicRuleType = $rule->getGrouping();
if (
($dynamicRuleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) ||
($dynamicRuleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE)
) {
// Number (Average) based
// Calculate the average
$averageFormula = '=AVERAGE(' . $columnID . ($rangeStart[1] + 1) . ':' . $columnID . $rangeEnd[1] . ')';
$average = Calculation::getInstance()->calculateFormula($averageFormula, null, $this->workSheet->getCell('A1'));
// Set above/below rule based on greaterThan or LessTan
$operator = ($dynamicRuleType === AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE)
? AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN
: AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN;
$ruleValues[] = [
'operator' => $operator,
'value' => $average,
];
$columnFilterTests[$columnID] = [
'method' => 'filterTestInCustomDataSet',
'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR],
];
} else {
// Date based
if ($dynamicRuleType[0] == 'M' || $dynamicRuleType[0] == 'Q') {
$periodType = '';
$period = 0;
// Month or Quarter
sscanf($dynamicRuleType, '%[A-Z]%d', $periodType, $period);
if ($periodType == 'M') {
$ruleValues = [$period];
} else {
--$period;
$periodEnd = (1 + $period) * 3;
$periodStart = 1 + $period * 3;
$ruleValues = range($periodStart, $periodEnd);
}
$columnFilterTests[$columnID] = [
'method' => 'filterTestInPeriodDateSet',
'arguments' => $ruleValues,
];
$filterColumn->setAttributes([]);
} else {
// Date Range
$columnFilterTests[$columnID] = $this->dynamicFilterDateRange($dynamicRuleType, $filterColumn);
break;
}
}
}
break;
case AutoFilter\Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER:
$ruleValues = [];
$dataRowCount = $rangeEnd[1] - $rangeStart[1];
foreach ($rules as $rule) {
// We should only ever have one Dynamic Filter Rule anyway
$toptenRuleType = $rule->getGrouping();
$ruleValue = $rule->getValue();
$ruleOperator = $rule->getOperator();
}
if ($ruleOperator === AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) {
$ruleValue = floor($ruleValue * ($dataRowCount / 100));
}
if ($ruleValue < 1) {
$ruleValue = 1;
}
if ($ruleValue > 500) {
$ruleValue = 500;
}
$maxVal = $this->calculateTopTenValue($columnID, $rangeStart[1] + 1, $rangeEnd[1], $toptenRuleType, $ruleValue);
$operator = ($toptenRuleType == AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP)
? AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL
: AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL;
$ruleValues[] = ['operator' => $operator, 'value' => $maxVal];
$columnFilterTests[$columnID] = [
'method' => 'filterTestInCustomDataSet',
'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR],
];
$filterColumn->setAttributes(['maxVal' => $maxVal]);
break;
}
}
// Execute the column tests for each row in the autoFilter range to determine show/hide,
for ($row = $rangeStart[1] + 1; $row <= $rangeEnd[1]; ++$row) {
$result = true;
foreach ($columnFilterTests as $columnID => $columnFilterTest) {
$cellValue = $this->workSheet->getCell($columnID . $row)->getCalculatedValue();
// Execute the filter test
$result = $result &&
call_user_func_array(
[self::class, $columnFilterTest['method']],
[$cellValue, $columnFilterTest['arguments']]
);
// If filter test has resulted in FALSE, exit the loop straightaway rather than running any more tests
if (!$result) {
break;
}
}
// Set show/hide for the row based on the result of the autoFilter result
$this->workSheet->getRowDimension($row)->setVisible($result);
}
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\AutoFilter objects
$this->{$key} = [];
foreach ($value as $k => $v) {
$this->{$key}[$k] = clone $v;
// attach the new cloned Column to this new cloned Autofilter object
$this->{$key}[$k]->setParent($this);
}
} else {
$this->{$key} = $value;
}
}
}
/**
* toString method replicates previous behavior by returning the range if object is
* referenced as a property of its parent.
*/
public function __toString()
{
return (string) $this->range;
}
}

View File

@@ -0,0 +1,380 @@
<?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 $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 $ruleJoins = [
self::AUTOFILTER_COLUMN_JOIN_AND,
self::AUTOFILTER_COLUMN_JOIN_OR,
];
/**
* Autofilter.
*
* @var AutoFilter
*/
private $parent;
/**
* Autofilter Column Index.
*
* @var string
*/
private $columnIndex = '';
/**
* Autofilter Column Filter Type.
*
* @var string
*/
private $filterType = self::AUTOFILTER_FILTERTYPE_FILTER;
/**
* Autofilter Multiple Rules And/Or.
*
* @var string
*/
private $join = self::AUTOFILTER_COLUMN_JOIN_OR;
/**
* Autofilter Column Rules.
*
* @var array of Column\Rule
*/
private $ruleset = [];
/**
* Autofilter Column Dynamic Attributes.
*
* @var array of mixed
*/
private $attributes = [];
/**
* Create a new Column.
*
* @param string $pColumn Column (e.g. A)
* @param AutoFilter $pParent Autofilter for this column
*/
public function __construct($pColumn, ?AutoFilter $pParent = null)
{
$this->columnIndex = $pColumn;
$this->parent = $pParent;
}
/**
* Get AutoFilter column index as string eg: 'A'.
*
* @return string
*/
public function getColumnIndex()
{
return $this->columnIndex;
}
/**
* Set AutoFilter column index as string eg: 'A'.
*
* @param string $pColumn Column (e.g. A)
*
* @return $this
*/
public function setColumnIndex($pColumn)
{
// Uppercase coordinate
$pColumn = strtoupper($pColumn);
if ($this->parent !== null) {
$this->parent->testColumnInRange($pColumn);
}
$this->columnIndex = $pColumn;
return $this;
}
/**
* Get this Column's AutoFilter Parent.
*
* @return AutoFilter
*/
public function getParent()
{
return $this->parent;
}
/**
* Set this Column's AutoFilter Parent.
*
* @param AutoFilter $pParent
*
* @return $this
*/
public function setParent(?AutoFilter $pParent = null)
{
$this->parent = $pParent;
return $this;
}
/**
* Get AutoFilter Type.
*
* @return string
*/
public function getFilterType()
{
return $this->filterType;
}
/**
* Set AutoFilter Type.
*
* @param string $pFilterType
*
* @return $this
*/
public function setFilterType($pFilterType)
{
if (!in_array($pFilterType, self::$filterTypes)) {
throw new PhpSpreadsheetException('Invalid filter type for column AutoFilter.');
}
$this->filterType = $pFilterType;
return $this;
}
/**
* Get AutoFilter Multiple Rules And/Or Join.
*
* @return string
*/
public function getJoin()
{
return $this->join;
}
/**
* Set AutoFilter Multiple Rules And/Or.
*
* @param string $pJoin And/Or
*
* @return $this
*/
public function setJoin($pJoin)
{
// Lowercase And/Or
$pJoin = strtolower($pJoin);
if (!in_array($pJoin, self::$ruleJoins)) {
throw new PhpSpreadsheetException('Invalid rule connection for column AutoFilter.');
}
$this->join = $pJoin;
return $this;
}
/**
* Set AutoFilter Attributes.
*
* @param string[] $attributes
*
* @return $this
*/
public function setAttributes(array $attributes)
{
$this->attributes = $attributes;
return $this;
}
/**
* Set An AutoFilter Attribute.
*
* @param string $pName Attribute Name
* @param string $pValue Attribute Value
*
* @return $this
*/
public function setAttribute($pName, $pValue)
{
$this->attributes[$pName] = $pValue;
return $this;
}
/**
* Get AutoFilter Column Attributes.
*
* @return string[]
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* Get specific AutoFilter Column Attribute.
*
* @param string $pName Attribute Name
*
* @return string
*/
public function getAttribute($pName)
{
if (isset($this->attributes[$pName])) {
return $this->attributes[$pName];
}
return null;
}
/**
* Get all AutoFilter Column Rules.
*
* @return Column\Rule[]
*/
public function getRules()
{
return $this->ruleset;
}
/**
* Get a specified AutoFilter Column Rule.
*
* @param int $pIndex Rule index in the ruleset array
*
* @return Column\Rule
*/
public function getRule($pIndex)
{
if (!isset($this->ruleset[$pIndex])) {
$this->ruleset[$pIndex] = new Column\Rule($this);
}
return $this->ruleset[$pIndex];
}
/**
* Create a new AutoFilter Column Rule in the ruleset.
*
* @return Column\Rule
*/
public function createRule()
{
$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 $pRule)
{
$pRule->setParent($this);
$this->ruleset[] = $pRule;
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 $pIndex Rule index in the ruleset array
*
* @return $this
*/
public function deleteRule($pIndex)
{
if (isset($this->ruleset[$pIndex])) {
unset($this->ruleset[$pIndex]);
// 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()
{
$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);
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;
}
} elseif (is_object($value)) {
$this->$key = clone $value;
} else {
$this->$key = $value;
}
}
}
}

View File

@@ -0,0 +1,449 @@
<?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 static $ruleTypes = [
// 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 static $dateTimeGroups = [
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 static $dynamicTypes = [
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,
];
/*
* The only valid filter rule operators for filter and customFilter types are:
* <xsd:enumeration value="equal"/>
* <xsd:enumeration value="lessThan"/>
* <xsd:enumeration value="lessThanOrEqual"/>
* <xsd:enumeration value="notEqual"/>
* <xsd:enumeration value="greaterThanOrEqual"/>
* <xsd:enumeration value="greaterThan"/>
*/
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 static $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 static $topTenValue = [
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 static $topTenType = [
self::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP,
self::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM,
];
// 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
// const AUTOFILTER_COLUMN_RULE_TOPTEN = 'topTen'; // greaterThan calculated value
// const AUTOFILTER_COLUMN_RULE_TOPTENPERCENT = 'topTenPercent'; // greaterThan calculated value
// const AUTOFILTER_COLUMN_RULE_ABOVEAVERAGE = 'aboveAverage'; // Value is calculated as the average
// const AUTOFILTER_COLUMN_RULE_BELOWAVERAGE = 'belowAverage'; // Value is calculated as the average
// 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';
// const AUTOFILTER_COLUMN_RULE_YESTERDAY = 'yesterday';
// const AUTOFILTER_COLUMN_RULE_TODAY = 'today';
// const AUTOFILTER_COLUMN_RULE_TOMORROW = 'tomorrow';
// const AUTOFILTER_COLUMN_RULE_LASTWEEK = 'lastWeek';
// const AUTOFILTER_COLUMN_RULE_THISWEEK = 'thisWeek';
// const AUTOFILTER_COLUMN_RULE_NEXTWEEK = 'nextWeek';
// const AUTOFILTER_COLUMN_RULE_LASTMONTH = 'lastMonth';
// const AUTOFILTER_COLUMN_RULE_THISMONTH = 'thisMonth';
// const AUTOFILTER_COLUMN_RULE_NEXTMONTH = 'nextMonth';
// const AUTOFILTER_COLUMN_RULE_LASTQUARTER = 'lastQuarter';
// const AUTOFILTER_COLUMN_RULE_THISQUARTER = 'thisQuarter';
// const AUTOFILTER_COLUMN_RULE_NEXTQUARTER = 'nextQuarter';
// const AUTOFILTER_COLUMN_RULE_LASTYEAR = 'lastYear';
// const AUTOFILTER_COLUMN_RULE_THISYEAR = 'thisYear';
// const AUTOFILTER_COLUMN_RULE_NEXTYEAR = 'nextYear';
// const AUTOFILTER_COLUMN_RULE_YEARTODATE = 'yearToDate'; // <dynamicFilter val="40909" type="yearToDate" maxVal="41113"/>
// const AUTOFILTER_COLUMN_RULE_ALLDATESINMONTH = 'allDatesInMonth'; // <dynamicFilter type="M2"/> for Month/February
// const AUTOFILTER_COLUMN_RULE_ALLDATESINQUARTER = 'allDatesInQuarter'; // <dynamicFilter type="Q2"/> for Quarter 2
/**
* Autofilter Column.
*
* @var Column
*/
private $parent;
/**
* Autofilter Rule Type.
*
* @var string
*/
private $ruleType = self::AUTOFILTER_RULETYPE_FILTER;
/**
* Autofilter Rule Value.
*
* @var string
*/
private $value = '';
/**
* Autofilter Rule Operator.
*
* @var string
*/
private $operator = self::AUTOFILTER_COLUMN_RULE_EQUAL;
/**
* DateTimeGrouping Group Value.
*
* @var string
*/
private $grouping = '';
/**
* Create a new Rule.
*
* @param Column $pParent
*/
public function __construct(?Column $pParent = null)
{
$this->parent = $pParent;
}
/**
* Get AutoFilter Rule Type.
*
* @return string
*/
public function getRuleType()
{
return $this->ruleType;
}
/**
* Set AutoFilter Rule Type.
*
* @param string $pRuleType see self::AUTOFILTER_RULETYPE_*
*
* @return $this
*/
public function setRuleType($pRuleType)
{
if (!in_array($pRuleType, self::$ruleTypes)) {
throw new PhpSpreadsheetException('Invalid rule type for column AutoFilter Rule.');
}
$this->ruleType = $pRuleType;
return $this;
}
/**
* Get AutoFilter Rule Value.
*
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* Set AutoFilter Rule Value.
*
* @param string|string[] $pValue
*
* @return $this
*/
public function setValue($pValue)
{
if (is_array($pValue)) {
$grouping = -1;
foreach ($pValue as $key => $value) {
// Validate array entries
if (!in_array($key, self::$dateTimeGroups)) {
// Remove any invalid entries from the value array
unset($pValue[$key]);
} else {
// Work out what the dateTime grouping will be
$grouping = max($grouping, array_search($key, self::$dateTimeGroups));
}
}
if (count($pValue) == 0) {
throw new PhpSpreadsheetException('Invalid rule value for column AutoFilter Rule.');
}
// Set the dateTime grouping that we've anticipated
$this->setGrouping(self::$dateTimeGroups[$grouping]);
}
$this->value = $pValue;
return $this;
}
/**
* Get AutoFilter Rule Operator.
*
* @return string
*/
public function getOperator()
{
return $this->operator;
}
/**
* Set AutoFilter Rule Operator.
*
* @param string $pOperator see self::AUTOFILTER_COLUMN_RULE_*
*
* @return $this
*/
public function setOperator($pOperator)
{
if (empty($pOperator)) {
$pOperator = self::AUTOFILTER_COLUMN_RULE_EQUAL;
}
if (
(!in_array($pOperator, self::$operators)) &&
(!in_array($pOperator, self::$topTenValue))
) {
throw new PhpSpreadsheetException('Invalid operator for column AutoFilter Rule.');
}
$this->operator = $pOperator;
return $this;
}
/**
* Get AutoFilter Rule Grouping.
*
* @return string
*/
public function getGrouping()
{
return $this->grouping;
}
/**
* Set AutoFilter Rule Grouping.
*
* @param string $pGrouping
*
* @return $this
*/
public function setGrouping($pGrouping)
{
if (
($pGrouping !== null) &&
(!in_array($pGrouping, self::$dateTimeGroups)) &&
(!in_array($pGrouping, self::$dynamicTypes)) &&
(!in_array($pGrouping, self::$topTenType))
) {
throw new PhpSpreadsheetException('Invalid rule type for column AutoFilter Rule.');
}
$this->grouping = $pGrouping;
return $this;
}
/**
* Set AutoFilter Rule.
*
* @param string $pOperator see self::AUTOFILTER_COLUMN_RULE_*
* @param string|string[] $pValue
* @param string $pGrouping
*
* @return $this
*/
public function setRule($pOperator, $pValue, $pGrouping = null)
{
$this->setOperator($pOperator);
$this->setValue($pValue);
// 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 ($pGrouping !== null) {
$this->setGrouping($pGrouping);
}
return $this;
}
/**
* Get this Rule's AutoFilter Column Parent.
*
* @return Column
*/
public function getParent()
{
return $this->parent;
}
/**
* Set this Rule's AutoFilter Column Parent.
*
* @param Column $pParent
*
* @return $this
*/
public function setParent(?Column $pParent = null)
{
$this->parent = $pParent;
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') {
// Detach from autofilter column parent
$this->$key = null;
} else {
$this->$key = clone $value;
}
} else {
$this->$key = $value;
}
}
}
}

View File

@@ -0,0 +1,532 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
use PhpOffice\PhpSpreadsheet\IComparable;
class BaseDrawing implements IComparable
{
/**
* Image counter.
*
* @var int
*/
private static $imageCounter = 0;
/**
* Image index.
*
* @var int
*/
private $imageIndex = 0;
/**
* Name.
*
* @var string
*/
protected $name;
/**
* Description.
*
* @var string
*/
protected $description;
/**
* Worksheet.
*
* @var Worksheet
*/
protected $worksheet;
/**
* Coordinates.
*
* @var string
*/
protected $coordinates;
/**
* Offset X.
*
* @var int
*/
protected $offsetX;
/**
* Offset Y.
*
* @var int
*/
protected $offsetY;
/**
* Width.
*
* @var int
*/
protected $width;
/**
* Height.
*
* @var int
*/
protected $height;
/**
* Proportional resize.
*
* @var bool
*/
protected $resizeProportional;
/**
* Rotation.
*
* @var int
*/
protected $rotation;
/**
* Shadow.
*
* @var Drawing\Shadow
*/
protected $shadow;
/**
* Image hyperlink.
*
* @var null|Hyperlink
*/
private $hyperlink;
/**
* Create a new BaseDrawing.
*/
public function __construct()
{
// Initialise values
$this->name = '';
$this->description = '';
$this->worksheet = null;
$this->coordinates = 'A1';
$this->offsetX = 0;
$this->offsetY = 0;
$this->width = 0;
$this->height = 0;
$this->resizeProportional = true;
$this->rotation = 0;
$this->shadow = new Drawing\Shadow();
// Set image index
++self::$imageCounter;
$this->imageIndex = self::$imageCounter;
}
/**
* Get image index.
*
* @return int
*/
public function getImageIndex()
{
return $this->imageIndex;
}
/**
* Get Name.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set Name.
*
* @param string $pValue
*
* @return $this
*/
public function setName($pValue)
{
$this->name = $pValue;
return $this;
}
/**
* Get Description.
*
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set Description.
*
* @param string $description
*
* @return $this
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get Worksheet.
*
* @return Worksheet
*/
public function getWorksheet()
{
return $this->worksheet;
}
/**
* Set Worksheet.
*
* @param Worksheet $pValue
* @param bool $pOverrideOld If a Worksheet has already been assigned, overwrite it and remove image from old Worksheet?
*
* @return $this
*/
public function setWorksheet(?Worksheet $pValue = null, $pOverrideOld = false)
{
if ($this->worksheet === null) {
// Add drawing to \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
$this->worksheet = $pValue;
$this->worksheet->getCell($this->coordinates);
$this->worksheet->getDrawingCollection()->append($this);
} else {
if ($pOverrideOld) {
// Remove drawing from old \PhpOffice\PhpSpreadsheet\Worksheet\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 \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
$this->setWorksheet($pValue);
} else {
throw new PhpSpreadsheetException('A Worksheet has already been assigned. Drawings can only exist on one \\PhpOffice\\PhpSpreadsheet\\Worksheet.');
}
}
return $this;
}
/**
* Get Coordinates.
*
* @return string
*/
public function getCoordinates()
{
return $this->coordinates;
}
/**
* Set Coordinates.
*
* @param string $pValue eg: 'A1'
*
* @return $this
*/
public function setCoordinates($pValue)
{
$this->coordinates = $pValue;
return $this;
}
/**
* Get OffsetX.
*
* @return int
*/
public function getOffsetX()
{
return $this->offsetX;
}
/**
* Set OffsetX.
*
* @param int $pValue
*
* @return $this
*/
public function setOffsetX($pValue)
{
$this->offsetX = $pValue;
return $this;
}
/**
* Get OffsetY.
*
* @return int
*/
public function getOffsetY()
{
return $this->offsetY;
}
/**
* Set OffsetY.
*
* @param int $pValue
*
* @return $this
*/
public function setOffsetY($pValue)
{
$this->offsetY = $pValue;
return $this;
}
/**
* Get Width.
*
* @return int
*/
public function getWidth()
{
return $this->width;
}
/**
* Set Width.
*
* @param int $pValue
*
* @return $this
*/
public function setWidth($pValue)
{
// Resize proportional?
if ($this->resizeProportional && $pValue != 0) {
$ratio = $this->height / ($this->width != 0 ? $this->width : 1);
$this->height = round($ratio * $pValue);
}
// Set width
$this->width = $pValue;
return $this;
}
/**
* Get Height.
*
* @return int
*/
public function getHeight()
{
return $this->height;
}
/**
* Set Height.
*
* @param int $pValue
*
* @return $this
*/
public function setHeight($pValue)
{
// Resize proportional?
if ($this->resizeProportional && $pValue != 0) {
$ratio = $this->width / ($this->height != 0 ? $this->height : 1);
$this->width = round($ratio * $pValue);
}
// Set height
$this->height = $pValue;
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
*
* @param int $width
* @param int $height
*
* @return $this
*/
public function setWidthAndHeight($width, $height)
{
$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 = ceil($xratio * $this->height);
$this->width = $width;
} else {
$this->width = ceil($yratio * $this->width);
$this->height = $height;
}
} else {
$this->width = $width;
$this->height = $height;
}
return $this;
}
/**
* Get ResizeProportional.
*
* @return bool
*/
public function getResizeProportional()
{
return $this->resizeProportional;
}
/**
* Set ResizeProportional.
*
* @param bool $pValue
*
* @return $this
*/
public function setResizeProportional($pValue)
{
$this->resizeProportional = $pValue;
return $this;
}
/**
* Get Rotation.
*
* @return int
*/
public function getRotation()
{
return $this->rotation;
}
/**
* Set Rotation.
*
* @param int $pValue
*
* @return $this
*/
public function setRotation($pValue)
{
$this->rotation = $pValue;
return $this;
}
/**
* Get Shadow.
*
* @return Drawing\Shadow
*/
public function getShadow()
{
return $this->shadow;
}
/**
* Set Shadow.
*
* @param Drawing\Shadow $pValue
*
* @return $this
*/
public function setShadow(?Drawing\Shadow $pValue = null)
{
$this->shadow = $pValue;
return $this;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode()
{
return md5(
$this->name .
$this->description .
$this->worksheet->getHashCode() .
$this->coordinates .
$this->offsetX .
$this->offsetY .
$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 $pHyperlink = null): void
{
$this->hyperlink = $pHyperlink;
}
/**
* @return null|Hyperlink
*/
public function getHyperlink()
{
return $this->hyperlink;
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use Iterator;
abstract class CellIterator implements Iterator
{
/**
* Worksheet to iterate.
*
* @var Worksheet
*/
protected $worksheet;
/**
* Iterate only existing cells.
*
* @var bool
*/
protected $onlyExistingCells = false;
/**
* Destructor.
*/
public function __destruct()
{
$this->worksheet = null;
}
/**
* Get loop only existing cells.
*
* @return bool
*/
public function getIterateOnlyExistingCells()
{
return $this->onlyExistingCells;
}
/**
* Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary.
*/
abstract protected function adjustForExistingOnlyRange();
/**
* Set the iterator to loop only existing cells.
*
* @param bool $value
*/
public function setIterateOnlyExistingCells($value): void
{
$this->onlyExistingCells = (bool) $value;
$this->adjustForExistingOnlyRange();
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
class Column
{
/**
* \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet.
*
* @var Worksheet
*/
private $parent;
/**
* Column index.
*
* @var string
*/
private $columnIndex;
/**
* Create a new column.
*
* @param Worksheet $parent
* @param string $columnIndex
*/
public function __construct(?Worksheet $parent = null, $columnIndex = 'A')
{
// Set parent and column index
$this->parent = $parent;
$this->columnIndex = $columnIndex;
}
/**
* Destructor.
*/
public function __destruct()
{
$this->parent = null;
}
/**
* Get column index as string eg: 'A'.
*
* @return string
*/
public function getColumnIndex()
{
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
*
* @return ColumnCellIterator
*/
public function getCellIterator($startRow = 1, $endRow = null)
{
return new ColumnCellIterator($this->parent, $this->columnIndex, $startRow, $endRow);
}
}

View File

@@ -0,0 +1,192 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class ColumnCellIterator extends CellIterator
{
/**
* Current iterator position.
*
* @var int
*/
private $currentRow;
/**
* Column index.
*
* @var string
*/
private $columnIndex;
/**
* Start position.
*
* @var int
*/
private $startRow = 1;
/**
* End position.
*
* @var int
*/
private $endRow = 1;
/**
* Create a new row iterator.
*
* @param Worksheet $subject 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 $subject = null, $columnIndex = 'A', $startRow = 1, $endRow = null)
{
// Set subject
$this->worksheet = $subject;
$this->columnIndex = Coordinate::columnIndexFromString($columnIndex);
$this->resetEnd($endRow);
$this->resetStart($startRow);
}
/**
* (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($startRow = 1)
{
$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($endRow = null)
{
$this->endRow = ($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($row = 1)
{
if ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($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.
*
* @return \PhpOffice\PhpSpreadsheet\Cell\Cell
*/
public function current()
{
return $this->worksheet->getCellByColumnAndRow($this->columnIndex, $this->currentRow);
}
/**
* Return the current iterator key.
*
* @return int
*/
public function key()
{
return $this->currentRow;
}
/**
* Set the iterator to its next value.
*/
public function next(): void
{
do {
++$this->currentRow;
} while (
($this->onlyExistingCells) &&
(!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->currentRow)) &&
($this->currentRow <= $this->endRow)
);
}
/**
* Set the iterator to its previous value.
*/
public function prev(): void
{
do {
--$this->currentRow;
} while (
($this->onlyExistingCells) &&
(!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->currentRow)) &&
($this->currentRow >= $this->startRow)
);
}
/**
* Indicate if more rows exist in the worksheet range of rows that we're iterating.
*
* @return bool
*/
public function valid()
{
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) {
while (
(!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->startRow)) &&
($this->startRow <= $this->endRow)
) {
++$this->startRow;
}
while (
(!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->endRow)) &&
($this->endRow >= $this->startRow)
) {
--$this->endRow;
}
}
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
class ColumnDimension extends Dimension
{
/**
* Column index.
*
* @var string
*/
private $columnIndex;
/**
* Column width.
*
* When this is set to a negative value, the column width should be ignored by IWriter
*
* @var float
*/
private $width = -1;
/**
* Auto size?
*
* @var bool
*/
private $autoSize = false;
/**
* Create a new ColumnDimension.
*
* @param string $pIndex Character column index
*/
public function __construct($pIndex = 'A')
{
// Initialise values
$this->columnIndex = $pIndex;
// set dimension as unformatted by default
parent::__construct(0);
}
/**
* Get column index as string eg: 'A'.
*
* @return string
*/
public function getColumnIndex()
{
return $this->columnIndex;
}
/**
* Set column index as string eg: 'A'.
*
* @param string $pValue
*
* @return $this
*/
public function setColumnIndex($pValue)
{
$this->columnIndex = $pValue;
return $this;
}
/**
* Get Width.
*
* @return float
*/
public function getWidth()
{
return $this->width;
}
/**
* Set Width.
*
* @param float $pValue
*
* @return $this
*/
public function setWidth($pValue)
{
$this->width = $pValue;
return $this;
}
/**
* Get Auto Size.
*
* @return bool
*/
public function getAutoSize()
{
return $this->autoSize;
}
/**
* Set Auto Size.
*
* @param bool $pValue
*
* @return $this
*/
public function setAutoSize($pValue)
{
$this->autoSize = $pValue;
return $this;
}
}

View File

@@ -0,0 +1,172 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use Iterator;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Exception;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class ColumnIterator implements Iterator
{
/**
* Worksheet to iterate.
*
* @var Worksheet
*/
private $worksheet;
/**
* Current iterator position.
*
* @var int
*/
private $currentColumnIndex = 1;
/**
* Start position.
*
* @var int
*/
private $startColumnIndex = 1;
/**
* End position.
*
* @var int
*/
private $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, $startColumn = 'A', $endColumn = null)
{
// Set subject
$this->worksheet = $worksheet;
$this->resetEnd($endColumn);
$this->resetStart($startColumn);
}
/**
* Destructor.
*/
public function __destruct()
{
$this->worksheet = null;
}
/**
* (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($startColumn = 'A')
{
$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($endColumn = null)
{
$endColumn = $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($column = 'A')
{
$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.
*
* @return Column
*/
public function current()
{
return new Column($this->worksheet, Coordinate::stringFromColumnIndex($this->currentColumnIndex));
}
/**
* Return the current iterator key.
*
* @return string
*/
public function key()
{
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.
*
* @return bool
*/
public function valid()
{
return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex;
}
}

View File

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

View File

@@ -0,0 +1,114 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class Drawing extends BaseDrawing
{
/**
* Path.
*
* @var string
*/
private $path;
/**
* Create a new Drawing.
*/
public function __construct()
{
// Initialise values
$this->path = '';
// Initialize parent
parent::__construct();
}
/**
* Get Filename.
*
* @return string
*/
public function getFilename()
{
return basename($this->path);
}
/**
* Get indexed filename (using image index).
*
* @return string
*/
public function getIndexedFilename()
{
$fileName = $this->getFilename();
$fileName = str_replace(' ', '_', $fileName);
return str_replace('.' . $this->getExtension(), '', $fileName) . $this->getImageIndex() . '.' . $this->getExtension();
}
/**
* Get Extension.
*
* @return string
*/
public function getExtension()
{
$exploded = explode('.', basename($this->path));
return $exploded[count($exploded) - 1];
}
/**
* Get Path.
*
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* Set Path.
*
* @param string $pValue File path
* @param bool $pVerifyFile Verify file
*
* @return $this
*/
public function setPath($pValue, $pVerifyFile = true)
{
if ($pVerifyFile) {
if (file_exists($pValue)) {
$this->path = $pValue;
if ($this->width == 0 && $this->height == 0) {
// Get width/height
[$this->width, $this->height] = getimagesize($pValue);
}
} else {
throw new PhpSpreadsheetException("File $pValue not found!");
}
} else {
$this->path = $pValue;
}
return $this;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode()
{
return md5(
$this->path .
parent::getHashCode() .
__CLASS__
);
}
}

View File

@@ -0,0 +1,289 @@
<?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.
*
* @var bool
*/
private $visible;
/**
* Blur radius.
*
* Defaults to 6
*
* @var int
*/
private $blurRadius;
/**
* Shadow distance.
*
* Defaults to 2
*
* @var int
*/
private $distance;
/**
* Shadow direction (in degrees).
*
* @var int
*/
private $direction;
/**
* Shadow alignment.
*
* @var int
*/
private $alignment;
/**
* Color.
*
* @var Color
*/
private $color;
/**
* Alpha.
*
* @var int
*/
private $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.
*
* @return bool
*/
public function getVisible()
{
return $this->visible;
}
/**
* Set Visible.
*
* @param bool $pValue
*
* @return $this
*/
public function setVisible($pValue)
{
$this->visible = $pValue;
return $this;
}
/**
* Get Blur radius.
*
* @return int
*/
public function getBlurRadius()
{
return $this->blurRadius;
}
/**
* Set Blur radius.
*
* @param int $pValue
*
* @return $this
*/
public function setBlurRadius($pValue)
{
$this->blurRadius = $pValue;
return $this;
}
/**
* Get Shadow distance.
*
* @return int
*/
public function getDistance()
{
return $this->distance;
}
/**
* Set Shadow distance.
*
* @param int $pValue
*
* @return $this
*/
public function setDistance($pValue)
{
$this->distance = $pValue;
return $this;
}
/**
* Get Shadow direction (in degrees).
*
* @return int
*/
public function getDirection()
{
return $this->direction;
}
/**
* Set Shadow direction (in degrees).
*
* @param int $pValue
*
* @return $this
*/
public function setDirection($pValue)
{
$this->direction = $pValue;
return $this;
}
/**
* Get Shadow alignment.
*
* @return int
*/
public function getAlignment()
{
return $this->alignment;
}
/**
* Set Shadow alignment.
*
* @param int $pValue
*
* @return $this
*/
public function setAlignment($pValue)
{
$this->alignment = $pValue;
return $this;
}
/**
* Get Color.
*
* @return Color
*/
public function getColor()
{
return $this->color;
}
/**
* Set Color.
*
* @param Color $pValue
*
* @return $this
*/
public function setColor(?Color $pValue = null)
{
$this->color = $pValue;
return $this;
}
/**
* Get Alpha.
*
* @return int
*/
public function getAlpha()
{
return $this->alpha;
}
/**
* Set Alpha.
*
* @param int $pValue
*
* @return $this
*/
public function setAlpha($pValue)
{
$this->alpha = $pValue;
return $this;
}
/**
* Get hash code.
*
* @return string Hash code
*/
public function getHashCode()
{
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,490 @@
<?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.
*
* @var string
*/
private $oddHeader = '';
/**
* OddFooter.
*
* @var string
*/
private $oddFooter = '';
/**
* EvenHeader.
*
* @var string
*/
private $evenHeader = '';
/**
* EvenFooter.
*
* @var string
*/
private $evenFooter = '';
/**
* FirstHeader.
*
* @var string
*/
private $firstHeader = '';
/**
* FirstFooter.
*
* @var string
*/
private $firstFooter = '';
/**
* Different header for Odd/Even, defaults to false.
*
* @var bool
*/
private $differentOddEven = false;
/**
* Different header for first page, defaults to false.
*
* @var bool
*/
private $differentFirst = false;
/**
* Scale with document, defaults to true.
*
* @var bool
*/
private $scaleWithDocument = true;
/**
* Align with margins, defaults to true.
*
* @var bool
*/
private $alignWithMargins = true;
/**
* Header/footer images.
*
* @var HeaderFooterDrawing[]
*/
private $headerFooterImages = [];
/**
* Create a new HeaderFooter.
*/
public function __construct()
{
}
/**
* Get OddHeader.
*
* @return string
*/
public function getOddHeader()
{
return $this->oddHeader;
}
/**
* Set OddHeader.
*
* @param string $pValue
*
* @return $this
*/
public function setOddHeader($pValue)
{
$this->oddHeader = $pValue;
return $this;
}
/**
* Get OddFooter.
*
* @return string
*/
public function getOddFooter()
{
return $this->oddFooter;
}
/**
* Set OddFooter.
*
* @param string $pValue
*
* @return $this
*/
public function setOddFooter($pValue)
{
$this->oddFooter = $pValue;
return $this;
}
/**
* Get EvenHeader.
*
* @return string
*/
public function getEvenHeader()
{
return $this->evenHeader;
}
/**
* Set EvenHeader.
*
* @param string $pValue
*
* @return $this
*/
public function setEvenHeader($pValue)
{
$this->evenHeader = $pValue;
return $this;
}
/**
* Get EvenFooter.
*
* @return string
*/
public function getEvenFooter()
{
return $this->evenFooter;
}
/**
* Set EvenFooter.
*
* @param string $pValue
*
* @return $this
*/
public function setEvenFooter($pValue)
{
$this->evenFooter = $pValue;
return $this;
}
/**
* Get FirstHeader.
*
* @return string
*/
public function getFirstHeader()
{
return $this->firstHeader;
}
/**
* Set FirstHeader.
*
* @param string $pValue
*
* @return $this
*/
public function setFirstHeader($pValue)
{
$this->firstHeader = $pValue;
return $this;
}
/**
* Get FirstFooter.
*
* @return string
*/
public function getFirstFooter()
{
return $this->firstFooter;
}
/**
* Set FirstFooter.
*
* @param string $pValue
*
* @return $this
*/
public function setFirstFooter($pValue)
{
$this->firstFooter = $pValue;
return $this;
}
/**
* Get DifferentOddEven.
*
* @return bool
*/
public function getDifferentOddEven()
{
return $this->differentOddEven;
}
/**
* Set DifferentOddEven.
*
* @param bool $pValue
*
* @return $this
*/
public function setDifferentOddEven($pValue)
{
$this->differentOddEven = $pValue;
return $this;
}
/**
* Get DifferentFirst.
*
* @return bool
*/
public function getDifferentFirst()
{
return $this->differentFirst;
}
/**
* Set DifferentFirst.
*
* @param bool $pValue
*
* @return $this
*/
public function setDifferentFirst($pValue)
{
$this->differentFirst = $pValue;
return $this;
}
/**
* Get ScaleWithDocument.
*
* @return bool
*/
public function getScaleWithDocument()
{
return $this->scaleWithDocument;
}
/**
* Set ScaleWithDocument.
*
* @param bool $pValue
*
* @return $this
*/
public function setScaleWithDocument($pValue)
{
$this->scaleWithDocument = $pValue;
return $this;
}
/**
* Get AlignWithMargins.
*
* @return bool
*/
public function getAlignWithMargins()
{
return $this->alignWithMargins;
}
/**
* Set AlignWithMargins.
*
* @param bool $pValue
*
* @return $this
*/
public function setAlignWithMargins($pValue)
{
$this->alignWithMargins = $pValue;
return $this;
}
/**
* Add header/footer image.
*
* @param string $location
*
* @return $this
*/
public function addImage(HeaderFooterDrawing $image, $location = self::IMAGE_HEADER_LEFT)
{
$this->headerFooterImages[$location] = $image;
return $this;
}
/**
* Remove header/footer image.
*
* @param string $location
*
* @return $this
*/
public function removeImage($location = self::IMAGE_HEADER_LEFT)
{
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)
{
$this->headerFooterImages = $images;
return $this;
}
/**
* Get header/footer images.
*
* @return HeaderFooterDrawing[]
*/
public function getImages()
{
// 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()
{
return md5(
$this->getPath() .
$this->name .
$this->offsetX .
$this->offsetY .
$this->width .
$this->height .
__CLASS__
);
}
}

View File

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

View File

@@ -0,0 +1,171 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use GdImage;
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';
/**
* Image resource.
*
* @var GdImage|resource
*/
private $imageResource;
/**
* Rendering function.
*
* @var string
*/
private $renderingFunction;
/**
* Mime type.
*
* @var string
*/
private $mimeType;
/**
* Unique name.
*
* @var string
*/
private $uniqueName;
/**
* Create a new MemoryDrawing.
*/
public function __construct()
{
// Initialise values
$this->imageResource = null;
$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();
}
/**
* Get image resource.
*
* @return GdImage|resource
*/
public function getImageResource()
{
return $this->imageResource;
}
/**
* Set image resource.
*
* @param GdImage|resource $value
*
* @return $this
*/
public function setImageResource($value)
{
$this->imageResource = $value;
if ($this->imageResource !== null) {
// Get width/height
$this->width = imagesx($this->imageResource);
$this->height = imagesy($this->imageResource);
}
return $this;
}
/**
* Get rendering function.
*
* @return string
*/
public function getRenderingFunction()
{
return $this->renderingFunction;
}
/**
* Set rendering function.
*
* @param string $value see self::RENDERING_*
*
* @return $this
*/
public function setRenderingFunction($value)
{
$this->renderingFunction = $value;
return $this;
}
/**
* Get mime type.
*
* @return string
*/
public function getMimeType()
{
return $this->mimeType;
}
/**
* Set mime type.
*
* @param string $value see self::MIMETYPE_*
*
* @return $this
*/
public function setMimeType($value)
{
$this->mimeType = $value;
return $this;
}
/**
* Get indexed filename (using image index).
*
* @return string
*/
public function getIndexedFilename()
{
$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()
{
return md5(
$this->renderingFunction .
$this->mimeType .
$this->uniqueName .
parent::getHashCode() .
__CLASS__
);
}
}

View File

@@ -0,0 +1,244 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
class PageMargins
{
/**
* Left.
*
* @var float
*/
private $left = 0.7;
/**
* Right.
*
* @var float
*/
private $right = 0.7;
/**
* Top.
*
* @var float
*/
private $top = 0.75;
/**
* Bottom.
*
* @var float
*/
private $bottom = 0.75;
/**
* Header.
*
* @var float
*/
private $header = 0.3;
/**
* Footer.
*
* @var float
*/
private $footer = 0.3;
/**
* Create a new PageMargins.
*/
public function __construct()
{
}
/**
* Get Left.
*
* @return float
*/
public function getLeft()
{
return $this->left;
}
/**
* Set Left.
*
* @param float $pValue
*
* @return $this
*/
public function setLeft($pValue)
{
$this->left = $pValue;
return $this;
}
/**
* Get Right.
*
* @return float
*/
public function getRight()
{
return $this->right;
}
/**
* Set Right.
*
* @param float $pValue
*
* @return $this
*/
public function setRight($pValue)
{
$this->right = $pValue;
return $this;
}
/**
* Get Top.
*
* @return float
*/
public function getTop()
{
return $this->top;
}
/**
* Set Top.
*
* @param float $pValue
*
* @return $this
*/
public function setTop($pValue)
{
$this->top = $pValue;
return $this;
}
/**
* Get Bottom.
*
* @return float
*/
public function getBottom()
{
return $this->bottom;
}
/**
* Set Bottom.
*
* @param float $pValue
*
* @return $this
*/
public function setBottom($pValue)
{
$this->bottom = $pValue;
return $this;
}
/**
* Get Header.
*
* @return float
*/
public function getHeader()
{
return $this->header;
}
/**
* Set Header.
*
* @param float $pValue
*
* @return $this
*/
public function setHeader($pValue)
{
$this->header = $pValue;
return $this;
}
/**
* Get Footer.
*
* @return float
*/
public function getFooter()
{
return $this->footer;
}
/**
* Set Footer.
*
* @param float $pValue
*
* @return $this
*/
public function setFooter($pValue)
{
$this->footer = $pValue;
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)) {
$this->$key = clone $value;
} else {
$this->$key = $value;
}
}
}
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,857 @@
<?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.
*
* @var int
*/
private $paperSize = self::PAPERSIZE_LETTER;
/**
* Orientation.
*
* @var string
*/
private $orientation = self::ORIENTATION_DEFAULT;
/**
* Scale (Print Scale).
*
* Print scaling. Valid values range from 10 to 400
* This setting is overridden when fitToWidth and/or fitToHeight are in use
*
* @var null|int
*/
private $scale = 100;
/**
* Fit To Page
* Whether scale or fitToWith / fitToHeight applies.
*
* @var bool
*/
private $fitToPage = false;
/**
* Fit To Height
* Number of vertical pages to fit on.
*
* @var null|int
*/
private $fitToHeight = 1;
/**
* Fit To Width
* Number of horizontal pages to fit on.
*
* @var null|int
*/
private $fitToWidth = 1;
/**
* Columns to repeat at left.
*
* @var array Containing start column and end column, empty array if option unset
*/
private $columnsToRepeatAtLeft = ['', ''];
/**
* Rows to repeat at top.
*
* @var array Containing start row number and end row number, empty array if option unset
*/
private $rowsToRepeatAtTop = [0, 0];
/**
* Center page horizontally.
*
* @var bool
*/
private $horizontalCentered = false;
/**
* Center page vertically.
*
* @var bool
*/
private $verticalCentered = false;
/**
* Print area.
*
* @var string
*/
private $printArea;
/**
* First page number.
*
* @var int
*/
private $firstPageNumber;
private $pageOrder = self::PAGEORDER_DOWN_THEN_OVER;
/**
* Create a new PageSetup.
*/
public function __construct()
{
}
/**
* Get Paper Size.
*
* @return int
*/
public function getPaperSize()
{
return $this->paperSize;
}
/**
* Set Paper Size.
*
* @param int $pValue see self::PAPERSIZE_*
*
* @return $this
*/
public function setPaperSize($pValue)
{
$this->paperSize = $pValue;
return $this;
}
/**
* Get Orientation.
*
* @return string
*/
public function getOrientation()
{
return $this->orientation;
}
/**
* Set Orientation.
*
* @param string $pValue see self::ORIENTATION_*
*
* @return $this
*/
public function setOrientation($pValue)
{
$this->orientation = $pValue;
return $this;
}
/**
* Get Scale.
*
* @return null|int
*/
public function getScale()
{
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 null|int $pValue
* @param bool $pUpdate Update fitToPage so scaling applies rather than fitToHeight / fitToWidth
*
* @return $this
*/
public function setScale($pValue, $pUpdate = true)
{
// 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 (($pValue >= 0) || $pValue === null) {
$this->scale = $pValue;
if ($pUpdate) {
$this->fitToPage = false;
}
} else {
throw new PhpSpreadsheetException('Scale must not be negative');
}
return $this;
}
/**
* Get Fit To Page.
*
* @return bool
*/
public function getFitToPage()
{
return $this->fitToPage;
}
/**
* Set Fit To Page.
*
* @param bool $pValue
*
* @return $this
*/
public function setFitToPage($pValue)
{
$this->fitToPage = $pValue;
return $this;
}
/**
* Get Fit To Height.
*
* @return null|int
*/
public function getFitToHeight()
{
return $this->fitToHeight;
}
/**
* Set Fit To Height.
*
* @param null|int $pValue
* @param bool $pUpdate Update fitToPage so it applies rather than scaling
*
* @return $this
*/
public function setFitToHeight($pValue, $pUpdate = true)
{
$this->fitToHeight = $pValue;
if ($pUpdate) {
$this->fitToPage = true;
}
return $this;
}
/**
* Get Fit To Width.
*
* @return null|int
*/
public function getFitToWidth()
{
return $this->fitToWidth;
}
/**
* Set Fit To Width.
*
* @param null|int $pValue
* @param bool $pUpdate Update fitToPage so it applies rather than scaling
*
* @return $this
*/
public function setFitToWidth($pValue, $pUpdate = true)
{
$this->fitToWidth = $pValue;
if ($pUpdate) {
$this->fitToPage = true;
}
return $this;
}
/**
* Is Columns to repeat at left set?
*
* @return bool
*/
public function isColumnsToRepeatAtLeftSet()
{
if (is_array($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()
{
return $this->columnsToRepeatAtLeft;
}
/**
* Set Columns to repeat at left.
*
* @param array $pValue Containing start column and end column, empty array if option unset
*
* @return $this
*/
public function setColumnsToRepeatAtLeft(array $pValue)
{
$this->columnsToRepeatAtLeft = $pValue;
return $this;
}
/**
* Set Columns to repeat at left by start and end.
*
* @param string $pStart eg: 'A'
* @param string $pEnd eg: 'B'
*
* @return $this
*/
public function setColumnsToRepeatAtLeftByStartAndEnd($pStart, $pEnd)
{
$this->columnsToRepeatAtLeft = [$pStart, $pEnd];
return $this;
}
/**
* Is Rows to repeat at top set?
*
* @return bool
*/
public function isRowsToRepeatAtTopSet()
{
if (is_array($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()
{
return $this->rowsToRepeatAtTop;
}
/**
* Set Rows to repeat at top.
*
* @param array $pValue Containing start column and end column, empty array if option unset
*
* @return $this
*/
public function setRowsToRepeatAtTop(array $pValue)
{
$this->rowsToRepeatAtTop = $pValue;
return $this;
}
/**
* Set Rows to repeat at top by start and end.
*
* @param int $pStart eg: 1
* @param int $pEnd eg: 1
*
* @return $this
*/
public function setRowsToRepeatAtTopByStartAndEnd($pStart, $pEnd)
{
$this->rowsToRepeatAtTop = [$pStart, $pEnd];
return $this;
}
/**
* Get center page horizontally.
*
* @return bool
*/
public function getHorizontalCentered()
{
return $this->horizontalCentered;
}
/**
* Set center page horizontally.
*
* @param bool $value
*
* @return $this
*/
public function setHorizontalCentered($value)
{
$this->horizontalCentered = $value;
return $this;
}
/**
* Get center page vertically.
*
* @return bool
*/
public function getVerticalCentered()
{
return $this->verticalCentered;
}
/**
* Set center page vertically.
*
* @param bool $value
*
* @return $this
*/
public function setVerticalCentered($value)
{
$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
*
* @return string
*/
public function getPrintArea($index = 0)
{
if ($index == 0) {
return $this->printArea;
}
$printAreas = explode(',', $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
*
* @return bool
*/
public function isPrintAreaSet($index = 0)
{
if ($index == 0) {
return $this->printArea !== null;
}
$printAreas = explode(',', $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($index = 0)
{
if ($index == 0) {
$this->printArea = null;
} else {
$printAreas = explode(',', $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 string $value
* @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($value, $index = 0, $method = self::SETPRINTRANGE_OVERWRITE)
{
if (strpos($value, '!') !== false) {
throw new PhpSpreadsheetException('Cell coordinate must not specify a worksheet.');
} elseif (strpos($value, ':') === false) {
throw new PhpSpreadsheetException('Cell coordinate must be a range of cells.');
} elseif (strpos($value, '$') !== false) {
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(',', $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(',', $this->printArea);
if ($index < 0) {
$index = 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 string $value
* @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($value, $index = -1)
{
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($column1, $row1, $column2, $row2, $index = 0, $method = self::SETPRINTRANGE_OVERWRITE)
{
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($column1, $row1, $column2, $row2, $index = -1)
{
return $this->setPrintArea(
Coordinate::stringFromColumnIndex($column1) . $row1 . ':' . Coordinate::stringFromColumnIndex($column2) . $row2,
$index,
self::SETPRINTRANGE_INSERT
);
}
/**
* Get first page number.
*
* @return int
*/
public function getFirstPageNumber()
{
return $this->firstPageNumber;
}
/**
* Set first page number.
*
* @param int $value
*
* @return $this
*/
public function setFirstPageNumber($value)
{
$this->firstPageNumber = $value;
return $this;
}
/**
* Reset first page number.
*
* @return $this
*/
public function resetFirstPageNumber()
{
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;
}
/**
* 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,691 @@
<?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';
/**
* Sheet.
*
* @var bool
*/
private $sheet = false;
/**
* Objects.
*
* @var bool
*/
private $objects = false;
/**
* Scenarios.
*
* @var bool
*/
private $scenarios = false;
/**
* Format cells.
*
* @var bool
*/
private $formatCells = false;
/**
* Format columns.
*
* @var bool
*/
private $formatColumns = false;
/**
* Format rows.
*
* @var bool
*/
private $formatRows = false;
/**
* Insert columns.
*
* @var bool
*/
private $insertColumns = false;
/**
* Insert rows.
*
* @var bool
*/
private $insertRows = false;
/**
* Insert hyperlinks.
*
* @var bool
*/
private $insertHyperlinks = false;
/**
* Delete columns.
*
* @var bool
*/
private $deleteColumns = false;
/**
* Delete rows.
*
* @var bool
*/
private $deleteRows = false;
/**
* Select locked cells.
*
* @var bool
*/
private $selectLockedCells = false;
/**
* Sort.
*
* @var bool
*/
private $sort = false;
/**
* AutoFilter.
*
* @var bool
*/
private $autoFilter = false;
/**
* Pivot tables.
*
* @var bool
*/
private $pivotTables = false;
/**
* Select unlocked cells.
*
* @var bool
*/
private $selectUnlockedCells = false;
/**
* Hashed password.
*
* @var string
*/
private $password = '';
/**
* Algorithm name.
*
* @var string
*/
private $algorithm = '';
/**
* Salt value.
*
* @var string
*/
private $salt = '';
/**
* Spin count.
*
* @var int
*/
private $spinCount = 10000;
/**
* Create a new Protection.
*/
public function __construct()
{
}
/**
* Is some sort of protection enabled?
*
* @return bool
*/
public function isProtectionEnabled()
{
return $this->sheet ||
$this->objects ||
$this->scenarios ||
$this->formatCells ||
$this->formatColumns ||
$this->formatRows ||
$this->insertColumns ||
$this->insertRows ||
$this->insertHyperlinks ||
$this->deleteColumns ||
$this->deleteRows ||
$this->selectLockedCells ||
$this->sort ||
$this->autoFilter ||
$this->pivotTables ||
$this->selectUnlockedCells;
}
/**
* Get Sheet.
*
* @return bool
*/
public function getSheet()
{
return $this->sheet;
}
/**
* Set Sheet.
*
* @param bool $pValue
*
* @return $this
*/
public function setSheet($pValue)
{
$this->sheet = $pValue;
return $this;
}
/**
* Get Objects.
*
* @return bool
*/
public function getObjects()
{
return $this->objects;
}
/**
* Set Objects.
*
* @param bool $pValue
*
* @return $this
*/
public function setObjects($pValue)
{
$this->objects = $pValue;
return $this;
}
/**
* Get Scenarios.
*
* @return bool
*/
public function getScenarios()
{
return $this->scenarios;
}
/**
* Set Scenarios.
*
* @param bool $pValue
*
* @return $this
*/
public function setScenarios($pValue)
{
$this->scenarios = $pValue;
return $this;
}
/**
* Get FormatCells.
*
* @return bool
*/
public function getFormatCells()
{
return $this->formatCells;
}
/**
* Set FormatCells.
*
* @param bool $pValue
*
* @return $this
*/
public function setFormatCells($pValue)
{
$this->formatCells = $pValue;
return $this;
}
/**
* Get FormatColumns.
*
* @return bool
*/
public function getFormatColumns()
{
return $this->formatColumns;
}
/**
* Set FormatColumns.
*
* @param bool $pValue
*
* @return $this
*/
public function setFormatColumns($pValue)
{
$this->formatColumns = $pValue;
return $this;
}
/**
* Get FormatRows.
*
* @return bool
*/
public function getFormatRows()
{
return $this->formatRows;
}
/**
* Set FormatRows.
*
* @param bool $pValue
*
* @return $this
*/
public function setFormatRows($pValue)
{
$this->formatRows = $pValue;
return $this;
}
/**
* Get InsertColumns.
*
* @return bool
*/
public function getInsertColumns()
{
return $this->insertColumns;
}
/**
* Set InsertColumns.
*
* @param bool $pValue
*
* @return $this
*/
public function setInsertColumns($pValue)
{
$this->insertColumns = $pValue;
return $this;
}
/**
* Get InsertRows.
*
* @return bool
*/
public function getInsertRows()
{
return $this->insertRows;
}
/**
* Set InsertRows.
*
* @param bool $pValue
*
* @return $this
*/
public function setInsertRows($pValue)
{
$this->insertRows = $pValue;
return $this;
}
/**
* Get InsertHyperlinks.
*
* @return bool
*/
public function getInsertHyperlinks()
{
return $this->insertHyperlinks;
}
/**
* Set InsertHyperlinks.
*
* @param bool $pValue
*
* @return $this
*/
public function setInsertHyperlinks($pValue)
{
$this->insertHyperlinks = $pValue;
return $this;
}
/**
* Get DeleteColumns.
*
* @return bool
*/
public function getDeleteColumns()
{
return $this->deleteColumns;
}
/**
* Set DeleteColumns.
*
* @param bool $pValue
*
* @return $this
*/
public function setDeleteColumns($pValue)
{
$this->deleteColumns = $pValue;
return $this;
}
/**
* Get DeleteRows.
*
* @return bool
*/
public function getDeleteRows()
{
return $this->deleteRows;
}
/**
* Set DeleteRows.
*
* @param bool $pValue
*
* @return $this
*/
public function setDeleteRows($pValue)
{
$this->deleteRows = $pValue;
return $this;
}
/**
* Get SelectLockedCells.
*
* @return bool
*/
public function getSelectLockedCells()
{
return $this->selectLockedCells;
}
/**
* Set SelectLockedCells.
*
* @param bool $pValue
*
* @return $this
*/
public function setSelectLockedCells($pValue)
{
$this->selectLockedCells = $pValue;
return $this;
}
/**
* Get Sort.
*
* @return bool
*/
public function getSort()
{
return $this->sort;
}
/**
* Set Sort.
*
* @param bool $pValue
*
* @return $this
*/
public function setSort($pValue)
{
$this->sort = $pValue;
return $this;
}
/**
* Get AutoFilter.
*
* @return bool
*/
public function getAutoFilter()
{
return $this->autoFilter;
}
/**
* Set AutoFilter.
*
* @param bool $pValue
*
* @return $this
*/
public function setAutoFilter($pValue)
{
$this->autoFilter = $pValue;
return $this;
}
/**
* Get PivotTables.
*
* @return bool
*/
public function getPivotTables()
{
return $this->pivotTables;
}
/**
* Set PivotTables.
*
* @param bool $pValue
*
* @return $this
*/
public function setPivotTables($pValue)
{
$this->pivotTables = $pValue;
return $this;
}
/**
* Get SelectUnlockedCells.
*
* @return bool
*/
public function getSelectUnlockedCells()
{
return $this->selectUnlockedCells;
}
/**
* Set SelectUnlockedCells.
*
* @param bool $pValue
*
* @return $this
*/
public function setSelectUnlockedCells($pValue)
{
$this->selectUnlockedCells = $pValue;
return $this;
}
/**
* Get hashed password.
*
* @return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Set Password.
*
* @param string $pValue
* @param bool $pAlreadyHashed If the password has already been hashed, set this to true
*
* @return $this
*/
public function setPassword($pValue, $pAlreadyHashed = false)
{
if (!$pAlreadyHashed) {
$salt = $this->generateSalt();
$this->setSalt($salt);
$pValue = PasswordHasher::hashPassword($pValue, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount());
}
$this->password = $pValue;
return $this;
}
/**
* 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): void
{
$this->algorithm = $algorithm;
}
/**
* Get salt value.
*/
public function getSalt(): string
{
return $this->salt;
}
/**
* Set salt value.
*/
public function setSalt(string $salt): void
{
$this->salt = $salt;
}
/**
* Get spin count.
*/
public function getSpinCount(): int
{
return $this->spinCount;
}
/**
* Set spin count.
*/
public function setSpinCount(int $spinCount): void
{
$this->spinCount = $spinCount;
}
/**
* Verify that the given non-hashed password can "unlock" the protection.
*/
public function verify(string $password): bool
{
if (!$this->isProtectionEnabled()) {
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,74 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
class Row
{
/**
* \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet.
*
* @var Worksheet
*/
private $worksheet;
/**
* Row index.
*
* @var int
*/
private $rowIndex = 0;
/**
* Create a new row.
*
* @param Worksheet $worksheet
* @param int $rowIndex
*/
public function __construct(?Worksheet $worksheet = null, $rowIndex = 1)
{
// Set parent and row index
$this->worksheet = $worksheet;
$this->rowIndex = $rowIndex;
}
/**
* Destructor.
*/
public function __destruct()
{
$this->worksheet = null;
}
/**
* Get row index.
*
* @return int
*/
public function getRowIndex()
{
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
*
* @return RowCellIterator
*/
public function getCellIterator($startColumn = 'A', $endColumn = null)
{
return new RowCellIterator($this->worksheet, $this->rowIndex, $startColumn, $endColumn);
}
/**
* Returns bound worksheet.
*
* @return Worksheet
*/
public function getWorksheet()
{
return $this->worksheet;
}
}

View File

@@ -0,0 +1,191 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class RowCellIterator extends CellIterator
{
/**
* Current iterator position.
*
* @var int
*/
private $currentColumnIndex;
/**
* Row index.
*
* @var int
*/
private $rowIndex = 1;
/**
* Start position.
*
* @var int
*/
private $startColumnIndex = 1;
/**
* End position.
*
* @var int
*/
private $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 = null, $rowIndex = 1, $startColumn = 'A', $endColumn = null)
{
// Set subject and row index
$this->worksheet = $worksheet;
$this->rowIndex = $rowIndex;
$this->resetEnd($endColumn);
$this->resetStart($startColumn);
}
/**
* (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($startColumn = 'A')
{
$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($endColumn = null)
{
$endColumn = $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($column = 'A')
{
$columnx = $column;
$column = Coordinate::columnIndexFromString($column);
if ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($column, $this->rowIndex))) {
throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist');
}
if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) {
throw new PhpSpreadsheetException("Column $columnx 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 cell in this worksheet row.
*
* @return \PhpOffice\PhpSpreadsheet\Cell\Cell
*/
public function current()
{
return $this->worksheet->getCellByColumnAndRow($this->currentColumnIndex, $this->rowIndex);
}
/**
* Return the current iterator key.
*
* @return string
*/
public function key()
{
return Coordinate::stringFromColumnIndex($this->currentColumnIndex);
}
/**
* Set the iterator to its next value.
*/
public function next(): void
{
do {
++$this->currentColumnIndex;
} while (($this->onlyExistingCells) && (!$this->worksheet->cellExistsByColumnAndRow($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->worksheet->cellExistsByColumnAndRow($this->currentColumnIndex, $this->rowIndex)) && ($this->currentColumnIndex >= $this->startColumnIndex));
}
/**
* Indicate if more columns exist in the worksheet range of columns that we're iterating.
*
* @return bool
*/
public function valid()
{
return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex;
}
/**
* Return the current iterator position.
*
* @return int
*/
public function getCurrentColumnIndex()
{
return $this->currentColumnIndex;
}
/**
* Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary.
*/
protected function adjustForExistingOnlyRange(): void
{
if ($this->onlyExistingCells) {
while ((!$this->worksheet->cellExistsByColumnAndRow($this->startColumnIndex, $this->rowIndex)) && ($this->startColumnIndex <= $this->endColumnIndex)) {
++$this->startColumnIndex;
}
while ((!$this->worksheet->cellExistsByColumnAndRow($this->endColumnIndex, $this->rowIndex)) && ($this->endColumnIndex >= $this->startColumnIndex)) {
--$this->endColumnIndex;
}
}
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
class RowDimension extends Dimension
{
/**
* Row index.
*
* @var int
*/
private $rowIndex;
/**
* Row height (in pt).
*
* When this is set to a negative value, the row height should be ignored by IWriter
*
* @var float
*/
private $height = -1;
/**
* ZeroHeight for Row?
*
* @var bool
*/
private $zeroHeight = false;
/**
* Create a new RowDimension.
*
* @param int $pIndex Numeric row index
*/
public function __construct($pIndex = 0)
{
// Initialise values
$this->rowIndex = $pIndex;
// set dimension as unformatted by default
parent::__construct(null);
}
/**
* Get Row Index.
*
* @return int
*/
public function getRowIndex()
{
return $this->rowIndex;
}
/**
* Set Row Index.
*
* @param int $pValue
*
* @return $this
*/
public function setRowIndex($pValue)
{
$this->rowIndex = $pValue;
return $this;
}
/**
* Get Row Height.
*
* @return float
*/
public function getRowHeight()
{
return $this->height;
}
/**
* Set Row Height.
*
* @param float $pValue
*
* @return $this
*/
public function setRowHeight($pValue)
{
$this->height = $pValue;
return $this;
}
/**
* Get ZeroHeight.
*
* @return bool
*/
public function getZeroHeight()
{
return $this->zeroHeight;
}
/**
* Set ZeroHeight.
*
* @param bool $pValue
*
* @return $this
*/
public function setZeroHeight($pValue)
{
$this->zeroHeight = $pValue;
return $this;
}
}

View File

@@ -0,0 +1,167 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Worksheet;
use Iterator;
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
class RowIterator implements Iterator
{
/**
* Worksheet to iterate.
*
* @var Worksheet
*/
private $subject;
/**
* Current iterator position.
*
* @var int
*/
private $position = 1;
/**
* Start position.
*
* @var int
*/
private $startRow = 1;
/**
* End position.
*
* @var int
*/
private $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, $startRow = 1, $endRow = null)
{
// Set subject
$this->subject = $subject;
$this->resetEnd($endRow);
$this->resetStart($startRow);
}
/**
* Destructor.
*/
public function __destruct()
{
$this->subject = null;
}
/**
* (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($startRow = 1)
{
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($endRow = null)
{
$this->endRow = ($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($row = 1)
{
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.
*
* @return Row
*/
public function current()
{
return new Row($this->subject, $this->position);
}
/**
* Return the current iterator key.
*
* @return int
*/
public function key()
{
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.
*
* @return bool
*/
public function valid()
{
return $this->position <= $this->endRow && $this->position >= $this->startRow;
}
}

View File

@@ -0,0 +1,193 @@
<?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 static $sheetViewTypes = [
self::SHEETVIEW_NORMAL,
self::SHEETVIEW_PAGE_LAYOUT,
self::SHEETVIEW_PAGE_BREAK_PREVIEW,
];
/**
* ZoomScale.
*
* Valid values range from 10 to 400.
*
* @var int
*/
private $zoomScale = 100;
/**
* ZoomScaleNormal.
*
* Valid values range from 10 to 400.
*
* @var int
*/
private $zoomScaleNormal = 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"
*
* @var bool
*/
private $showZeros = true;
/**
* View.
*
* Valid values range from 10 to 400.
*
* @var string
*/
private $sheetviewType = self::SHEETVIEW_NORMAL;
/**
* Create a new SheetView.
*/
public function __construct()
{
}
/**
* Get ZoomScale.
*
* @return int
*/
public function getZoomScale()
{
return $this->zoomScale;
}
/**
* Set ZoomScale.
* Valid values range from 10 to 400.
*
* @param int $pValue
*
* @return $this
*/
public function setZoomScale($pValue)
{
// 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 (($pValue >= 1) || $pValue === null) {
$this->zoomScale = $pValue;
} else {
throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.');
}
return $this;
}
/**
* Get ZoomScaleNormal.
*
* @return int
*/
public function getZoomScaleNormal()
{
return $this->zoomScaleNormal;
}
/**
* Set ZoomScale.
* Valid values range from 10 to 400.
*
* @param int $pValue
*
* @return $this
*/
public function setZoomScaleNormal($pValue)
{
if (($pValue >= 1) || $pValue === null) {
$this->zoomScaleNormal = $pValue;
} else {
throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.');
}
return $this;
}
/**
* Set ShowZeroes setting.
*
* @param bool $pValue
*/
public function setShowZeros($pValue): void
{
$this->showZeros = $pValue;
}
/**
* @return bool
*/
public function getShowZeros()
{
return $this->showZeros;
}
/**
* Get View.
*
* @return string
*/
public function getView()
{
return $this->sheetviewType;
}
/**
* Set View.
*
* Valid values are
* 'normal' self::SHEETVIEW_NORMAL
* 'pageLayout' self::SHEETVIEW_PAGE_LAYOUT
* 'pageBreakPreview' self::SHEETVIEW_PAGE_BREAK_PREVIEW
*
* @param string $pValue
*
* @return $this
*/
public function setView($pValue)
{
// MS Excel 2007 allows setting the view to 'normal', 'pageLayout' or 'pageBreakPreview' via the user interface
if ($pValue === null) {
$pValue = self::SHEETVIEW_NORMAL;
}
if (in_array($pValue, self::$sheetViewTypes)) {
$this->sheetviewType = $pValue;
} else {
throw new PhpSpreadsheetException('Invalid sheetview layout type.');
}
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)) {
$this->$key = clone $value;
} else {
$this->$key = $value;
}
}
}
}

File diff suppressed because it is too large Load Diff