diff --git a/.travis.yml b/.travis.yml index 31ae804e..05e964a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ jobs: - php ocular.phar code-coverage:upload --format=php-clover tests/coverage-clover.xml - stage: API documentations - if: tag is present AND branch = master + if: branch = master php: 7.4 before_script: - curl -LO https://github.com/phpDocumentor/phpDocumentor/releases/download/v3.0.0-rc/phpDocumentor.phar diff --git a/CHANGELOG.md b/CHANGELOG.md index 22fd8c3d..0c139850 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com) and this project adheres to [Semantic Versioning](https://semver.org). -## [Unreleased] +## [1.13.0] - 2020-05-31 ### Added - Support writing to streams in all writers [#1292](https://github.com/PHPOffice/PhpSpreadsheet/issues/1292) - Support CSV files with data wrapping a lot of lines [#1468](https://github.com/PHPOffice/PhpSpreadsheet/pull/1468) +- Support protection of worksheet by a specific hash algorithm [#1485](https://github.com/PHPOffice/PhpSpreadsheet/pull/1485) ### Fixed @@ -21,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Several improvements in HTML writer [#1464](https://github.com/PHPOffice/PhpSpreadsheet/pull/1464) - Fix incorrect behaviour when saving XLSX file with drawings [#1462](https://github.com/PHPOffice/PhpSpreadsheet/pull/1462), - Fix Crash while trying setting a cell the value "123456\n" [#1476](https://github.com/PHPOffice/PhpSpreadsheet/pull/1481) +- Improved DATEDIF() function and reduced errors for Y and YM units [#1466](https://github.com/PHPOffice/PhpSpreadsheet/pull/1466) +- Stricter typing for mergeCells [#1494](https://github.com/PHPOffice/PhpSpreadsheet/pull/1494) ### Changed diff --git a/docs/faq.md b/docs/faq.md index 19f5f8fc..ac69e415 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -23,7 +23,7 @@ When you make use of any of the worksheet protection features (e.g. cell range protection, prohibiting deleting rows, ...), make sure you enable worksheet security. This can for example be done like this: -``` php +```php $spreadsheet->getActiveSheet()->getProtection()->setSheet(true); ``` diff --git a/docs/topics/accessing-cells.md b/docs/topics/accessing-cells.md index 4770d721..edb71514 100644 --- a/docs/topics/accessing-cells.md +++ b/docs/topics/accessing-cells.md @@ -8,7 +8,7 @@ topic lists some of the options to access a cell. Setting a cell value by coordinate can be done using the worksheet's `setCellValue()` method. -``` php +```php // Set cell A1 with a string value $spreadsheet->getActiveSheet()->setCellValue('A1', 'PhpSpreadsheet'); @@ -28,7 +28,7 @@ $spreadsheet->getActiveSheet()->setCellValue( Alternatively, you can retrieve the cell object, and then call the cell’s `setValue()` method: -``` php +```php $spreadsheet->getActiveSheet() ->getCell('B8') ->setValue('Some value'); @@ -56,7 +56,7 @@ the cell object will still retain its data values. What does this mean? Consider the following code: -``` +```php $spreadSheet = new Spreadsheet(); $workSheet = $spreadSheet->getActiveSheet(); @@ -74,7 +74,7 @@ $cellA1 = $workSheet->getCell('A1'); echo 'Value: ', $cellA1->getValue(), '; Address: ', $cellA1->getCoordinate(), PHP_EOL; echo 'Value: ', $cellC1->getValue(), '; Address: ', $cellC1->getCoordinate(), PHP_EOL; -``` +``` The call to `getCell('C1')` returns the cell at `C1` containing its value (`3`), together with its link to the collection (used to identify its @@ -153,7 +153,7 @@ was a formula. To do this, you need to "escape" the value by setting it as "quoted text". -``` +```php // Set cell A4 with a formula $spreadsheet->getActiveSheet()->setCellValue( 'A4', @@ -175,7 +175,7 @@ point value), and a number format mask is used to show how that value should be formatted; so if we want to store a date in a cell, we need to calculate the correct Excel timestamp, and set a number format mask. -``` php +```php // Get the current date/time and convert to an Excel date/time $dateTimeNow = time(); $excelDateValue = \PhpOffice\PhpSpreadsheet\Shared\Date::PHPToExcel( $dateTimeNow ); @@ -210,7 +210,7 @@ behaviour. Firstly, you can set the datatype explicitly as a string so that it is not converted to a number. -``` php +```php // Set cell A8 with a numeric value, but tell PhpSpreadsheet it should be treated as a string $spreadsheet->getActiveSheet()->setCellValueExplicit( 'A8', @@ -222,7 +222,7 @@ $spreadsheet->getActiveSheet()->setCellValueExplicit( Alternatively, you can use a number format mask to display the value with leading zeroes. -``` php +```php // Set cell A9 with a numeric value $spreadsheet->getActiveSheet()->setCellValue('A9', 1513789642); // Set a number format mask to display the value as 11 digits with leading zeroes @@ -236,7 +236,7 @@ $spreadsheet->getActiveSheet()->getStyle('A9') With number format masking, you can even break up the digits into groups to make the value more easily readable. -``` php +```php // Set cell A10 with a numeric value $spreadsheet->getActiveSheet()->setCellValue('A10', 1513789642); // Set a number format mask to display the value as 11 digits with leading zeroes @@ -259,7 +259,7 @@ writers (Xlsx and Xls). It is also possible to set a range of cell values in a single call by passing an array of values to the `fromArray()` method. -``` php +```php $arrayData = [ [NULL, 2010, 2011, 2012], ['Q1', 12, 15, 21], @@ -282,7 +282,7 @@ If you pass a 2-d array, then this will be treated as a series of rows and columns. A 1-d array will be treated as a single row, which is particularly useful if you're fetching an array of data from a database. -``` php +```php $rowArray = ['Value1', 'Value2', 'Value3', 'Value4']; $spreadsheet->getActiveSheet() ->fromArray( @@ -299,7 +299,7 @@ If you have a simple 1-d array, and want to write it as a column, then the following will convert it into an appropriately structured 2-d array that can be fed to the `fromArray()` method: -``` php +```php $rowArray = ['Value1', 'Value2', 'Value3', 'Value4']; $columnArray = array_chunk($rowArray, 1); $spreadsheet->getActiveSheet() @@ -319,7 +319,7 @@ To retrieve the value of a cell, the cell should first be retrieved from the worksheet using the `getCell()` method. A cell's value can be read using the `getValue()` method. -``` php +```php // Get the value from cell A1 $cellValue = $spreadsheet->getActiveSheet()->getCell('A1')->getValue(); ``` @@ -331,7 +331,7 @@ value rather than the formula itself, then use the cell's `getCalculatedValue()` method. This is further explained in [the calculation engine](./calculation-engine.md). -``` php +```php // Get the value from cell A4 $cellValue = $spreadsheet->getActiveSheet()->getCell('A4')->getCalculatedValue(); ``` @@ -340,7 +340,7 @@ Alternatively, if you want to see the value with any cell formatting applied (e.g. for a human-readable date or time value), then you can use the cell's `getFormattedValue()` method. -``` php +```php // Get the value from cell A6 $cellValue = $spreadsheet->getActiveSheet()->getCell('A6')->getFormattedValue(); ``` @@ -350,7 +350,7 @@ $cellValue = $spreadsheet->getActiveSheet()->getCell('A6')->getFormattedValue(); Setting a cell value by coordinate can be done using the worksheet's `setCellValueByColumnAndRow()` method. -``` php +```php // Set cell A5 with a string value $spreadsheet->getActiveSheet()->setCellValueByColumnAndRow(1, 5, 'PhpSpreadsheet'); ``` @@ -363,7 +363,7 @@ To retrieve the value of a cell, the cell should first be retrieved from the worksheet using the `getCellByColumnAndRow()` method. A cell’s value can be read again using the following line of code: -``` php +```php // Get the value from cell B5 $cellValue = $spreadsheet->getActiveSheet()->getCellByColumnAndRow(2, 5)->getValue(); ``` @@ -371,7 +371,7 @@ $cellValue = $spreadsheet->getActiveSheet()->getCellByColumnAndRow(2, 5)->getVal If you need the calculated value of a cell, use the following code. This is further explained in [the calculation engine](./calculation-engine.md). -``` php +```php // Get the value from cell A4 $cellValue = $spreadsheet->getActiveSheet()->getCellByColumnAndRow(1, 4)->getCalculatedValue(); ``` @@ -382,7 +382,7 @@ It is also possible to retrieve a range of cell values to an array in a single call using the `toArray()`, `rangeToArray()` or `namedRangeToArray()` methods. -``` php +```php $dataArray = $spreadsheet->getActiveSheet() ->rangeToArray( 'C3:E5', // The worksheet range that we want to retrieve @@ -409,7 +409,7 @@ cells within a row. Below is an example where we read all the values in a worksheet and display them in a table. -``` php +```php $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx'); $reader->setReadDataOnly(TRUE); $spreadsheet = $reader->load("test.xlsx"); @@ -456,7 +456,7 @@ loops. Below is an example where we read all the values in a worksheet and display them in a table. -``` php +```php $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx'); $reader->setReadDataOnly(TRUE); $spreadsheet = $reader->load("test.xlsx"); @@ -482,7 +482,7 @@ echo '' . PHP_EOL; Alternatively, you can take advantage of PHP's "Perl-style" character incrementors to loop through the cells by coordinate: -``` php +```php $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx'); $reader->setReadDataOnly(TRUE); $spreadsheet = $reader->load("test.xlsx"); @@ -528,7 +528,7 @@ dates entered as strings to the correct format, also setting the cell's style information. The following example demonstrates how to set the value binder in PhpSpreadsheet: -``` php +```php /** PhpSpreadsheet */ require_once 'src/Boostrap.php'; diff --git a/docs/topics/architecture.md b/docs/topics/architecture.md index 0295d672..1c544ef7 100644 --- a/docs/topics/architecture.md +++ b/docs/topics/architecture.md @@ -43,7 +43,7 @@ PhpSpreadsheet supports fluent interfaces in most locations. This means that you can easily "chain" calls to specific methods without requiring a new PHP statement. For example, take the following code: -``` php +```php $spreadsheet->getProperties()->setCreator("Maarten Balliauw"); $spreadsheet->getProperties()->setLastModifiedBy("Maarten Balliauw"); $spreadsheet->getProperties()->setTitle("Office 2007 XLSX Test Document"); @@ -55,7 +55,7 @@ $spreadsheet->getProperties()->setCategory("Test result file"); This can be rewritten as: -``` php +```php $spreadsheet->getProperties() ->setCreator("Maarten Balliauw") ->setLastModifiedBy("Maarten Balliauw") diff --git a/docs/topics/autofilters.md b/docs/topics/autofilters.md index 66321ee9..d5a07f8b 100644 --- a/docs/topics/autofilters.md +++ b/docs/topics/autofilters.md @@ -42,7 +42,7 @@ column, such as "Equals a red cell color" or "Larger than 150". To set an autoFilter on a range of cells. -``` php +```php $spreadsheet->getActiveSheet()->setAutoFilter('A1:E20'); ``` @@ -56,7 +56,7 @@ developer to avoid such errors. If you want to set the whole worksheet as an autofilter region -``` php +```php $spreadsheet->getActiveSheet()->setAutoFilter( $spreadsheet->getActiveSheet() ->calculateWorksheetDimension() @@ -74,7 +74,7 @@ will extend this to other formats. To apply a filter expression to an autoFilter range, you first need to identify which column you're going to be applying this filter to. -``` php +```php $autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter(); $columnFilter = $autoFilter->getColumn('C'); ``` @@ -114,7 +114,7 @@ To create a filter expression, we need to start by identifying the filter type. In this case, we're just going to specify that this filter is a standard filter. -``` php +```php $columnFilter->setFilterType( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::AUTOFILTER_FILTERTYPE_FILTER ); @@ -127,7 +127,7 @@ When creating a simple filter in PhpSpreadsheet, you only need to specify the values for "checked" columns: you do this by creating a filter rule for each value. -``` php +```php $columnFilter->createRule() ->setRule( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL, @@ -152,7 +152,7 @@ standard filters are always treated as being joined by an OR condition. If you want to create a filter to select blank cells, you would use: -``` php +```php $columnFilter->createRule() ->setRule( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL, @@ -170,7 +170,7 @@ within a year, or individual days within each month. DateGroup filters are still applied as a Standard Filter type. -``` php +```php $columnFilter->setFilterType( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::AUTOFILTER_FILTERTYPE_FILTER ); @@ -181,7 +181,7 @@ for "checked" columns as an associative array of year. month, day, hour minute and second. To select a year and month, you need to create a DateGroup rule identifying the selected year and month: -``` php +```php $columnFilter->createRule() ->setRule( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL, @@ -229,7 +229,7 @@ either an AND or an OR. We start by specifying a Filter type, this time a CUSTOMFILTER. -``` php +```php $columnFilter->setFilterType( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER ); @@ -240,7 +240,7 @@ And then define our rules. The following shows a simple wildcard filter to show all column entries beginning with the letter `U`. -``` php +```php $columnFilter->createRule() ->setRule( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL, @@ -264,7 +264,7 @@ is the \~ itself. To create a "between" condition, we need to define two rules: -``` php +```php $columnFilter->createRule() ->setRule( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, @@ -289,7 +289,7 @@ This defined two rules, filtering numbers that are `>= -20` OR `<= 20`, so we also need to modify the join condition to reflect AND rather than OR. -``` php +```php $columnFilter->setAndOr( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::AUTOFILTER_COLUMN_ANDOR_AND ); @@ -320,7 +320,7 @@ column at a time. Again, we start by specifying a Filter type, this time a DYNAMICFILTER. -``` php +```php $columnFilter->setFilterType( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER ); @@ -330,7 +330,7 @@ When defining the rule for a dynamic filter, we don't define a value (we can simply set that to NULL) but we do specify the dynamic filter category. -``` php +```php $columnFilter->createRule() ->setRule( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL, @@ -420,7 +420,7 @@ column at a time. We start by specifying a Filter type, this time a DYNAMICFILTER. -``` php +```php $columnFilter->setFilterType( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER ); @@ -428,7 +428,7 @@ $columnFilter->setFilterType( Then we create the rule: -``` php +```php $columnFilter->createRule() ->setRule( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT, @@ -444,7 +444,7 @@ This will filter the Top 5 percent of values in the column. To specify the lowest (bottom 2 values), we would specify a rule of: -``` php +```php $columnFilter->createRule() ->setRule( \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE, @@ -490,7 +490,7 @@ If you wish to execute your filter from within a script, you need to do this manually. You can do this using the autofilters `showHideRows()` method. -``` php +```php $autoFilter = $spreadsheet->getActiveSheet()->getAutoFilter(); $autoFilter->showHideRows(); ``` @@ -505,7 +505,7 @@ ever row, whether it matches the filter criteria or not. To selectively access only the filtered rows, you need to test each row’s visibility settings. -``` php +```php foreach ($spreadsheet->getActiveSheet()->getRowIterator() as $row) { if ($spreadsheet->getActiveSheet() ->getRowDimension($row->getRowIndex())->getVisible()) { diff --git a/docs/topics/calculation-engine.md b/docs/topics/calculation-engine.md index 779d73e1..d15dbb16 100644 --- a/docs/topics/calculation-engine.md +++ b/docs/topics/calculation-engine.md @@ -13,7 +13,7 @@ evaluates to the sum of values in A1, A2, ..., A10. To calculate a formula, you can call the cell containing the formula’s method `getCalculatedValue()`, for example: -``` php +```php $spreadsheet->getActiveSheet()->getCell('E11')->getCalculatedValue(); ``` @@ -32,7 +32,7 @@ You see that the formula contained in cell E11 is "SUM(E4:E9)". Now, when I write the following line of code, two new product lines are added: -``` php +```php $spreadsheet->getActiveSheet()->insertNewRowBefore(7, 2); ``` @@ -55,7 +55,7 @@ However, there may be times when you don't want this, perhaps you've changed the underlying data and need to re-evaluate the same formula with that new data. -``` +```php Calculation::getInstance($spreadsheet)->disableCalculationCache(); ``` @@ -63,7 +63,7 @@ Will disable calculation caching, and flush the current calculation cache. If you want only to flush the cache, then you can call -``` +```php Calculation::getInstance($spreadsheet)->clearCalculationCache(); ``` @@ -118,7 +118,7 @@ date values by calling the `\PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType()` method: -``` php +```php \PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType($returnDateType); ``` @@ -134,7 +134,7 @@ if an invalid value is passed in for the return date type). The `\PhpOffice\PhpSpreadsheet\Calculation\Functions::getReturnDateType()` method can be used to determine the current value of this setting: -``` php +```php $returnDateType = \PhpOffice\PhpSpreadsheet\Calculation\Functions::getReturnDateType(); ``` @@ -172,7 +172,7 @@ It is possible for scripts to change the calendar used for calculating Excel date values by calling the `\PhpOffice\PhpSpreadsheet\Shared\Date::setExcelCalendar()` method: -``` php +```php \PhpOffice\PhpSpreadsheet\Shared\Date::setExcelCalendar($baseDate); ``` @@ -187,7 +187,7 @@ if an invalid value is passed in). The `\PhpOffice\PhpSpreadsheet\Shared\Date::getExcelCalendar()` method can be used to determine the current value of this setting: -``` php +```php $baseDate = \PhpOffice\PhpSpreadsheet\Shared\Date::getExcelCalendar(); ``` @@ -353,7 +353,7 @@ This is the statistical mean. ##### Examples -``` php +```php $database = [ [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], [ 'Apple', 18, 20, 14, 105.00 ], @@ -421,7 +421,7 @@ in which you specify a condition for the column. ##### Examples -``` php +```php $database = [ [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], [ 'Apple', 18, 20, 14, 105.00 ], @@ -492,7 +492,7 @@ in which you specify a condition for the column. ##### Examples -``` php +```php $database = [ [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], [ 'Apple', 18, 20, 14, 105.00 ], @@ -563,7 +563,7 @@ in which you specify a condition for the column. #### Examples -``` php +```php $database = [ [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], [ 'Apple', 18, 20, 14, 105.00 ], @@ -631,7 +631,7 @@ in which you specify a condition for the column. ##### Examples -``` php +```php $database = [ [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], [ 'Apple', 18, 20, 14, 105.00 ], @@ -699,7 +699,7 @@ in which you specify a condition for the column. ##### Examples -``` php +```php $database = [ [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], [ 'Apple', 18, 20, 14, 105.00 ], @@ -767,7 +767,7 @@ in which you specify a condition for the column. ##### Examples -``` php +```php $database = [ [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], [ 'Apple', 18, 20, 14, 105.00 ], @@ -836,7 +836,7 @@ in which you specify a condition for the column. ##### Examples -``` php +```php $database = [ [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], [ 'Apple', 18, 20, 14, 105.00 ], @@ -905,7 +905,7 @@ in which you specify a condition for the column. ##### Examples -``` php +```php $database = [ [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], [ 'Apple', 18, 20, 14, 105.00 ], @@ -973,7 +973,7 @@ in which you specify a condition for the column. ##### Examples -``` php +```php $database = [ [ 'Tree', 'Height', 'Age', 'Yield', 'Profit' ], [ 'Apple', 18, 20, 14, 105.00 ], @@ -1074,7 +1074,7 @@ or an Excel timestamp value (real), depending on the value of ##### Examples -``` php +```php $worksheet->setCellValue('A1', 'Year') ->setCellValue('A2', 'Month') ->setCellValue('A3', 'Day'); @@ -1089,7 +1089,7 @@ $retVal = $worksheet->getCell('D1')->getCalculatedValue(); // $retVal = 1230681600 ``` -``` php +```php // We're going to be calling the same cell calculation multiple times, // and expecting different return values, so disable calculation cacheing \PhpOffice\PhpSpreadsheet\Calculation\Calculation::getInstance()->setCalculationCacheEnabled(FALSE); @@ -1170,7 +1170,7 @@ the third parameter. ##### Examples -``` php +```php $worksheet->setCellValue('A1', 'Year') ->setCellValue('A2', 'Month') ->setCellValue('A3', 'Day'); @@ -1208,7 +1208,7 @@ $retVal = $worksheet->getCell('D6')->getCalculatedValue(); // $retVal = 30 ``` -``` php +```php $date1 = 1193317015; // PHP timestamp for 25-Oct-2007 $date2 = 1449579415; // PHP timestamp for 8-Dec-2015 @@ -1279,7 +1279,7 @@ or an Excel timestamp value (real), depending on the value of ##### Examples -``` php +```php $worksheet->setCellValue('A1', 'Date String'); ->setCellValue('A2', '31-Dec-2008') ->setCellValue('A3', '31/12/2008') @@ -1301,7 +1301,7 @@ $retVal = $worksheet->getCell('B4')->getCalculatedValue(); // $retVal = 39813.0 for all cases ``` -``` php +```php // We're going to be calling the same cell calculation multiple times, // and expecting different return values, so disable calculation cacheing \PhpOffice\PhpSpreadsheet\Calculation\Calculation::getInstance()->setCalculationCacheEnabled(FALSE); @@ -1371,7 +1371,7 @@ This is an integer ranging from 1 to 31. ##### Examples -``` php +```php $worksheet->setCellValue('A1', 'Date String') ->setCellValue('A2', '31-Dec-2008') ->setCellValue('A3', '14-Feb-2008'); @@ -1386,7 +1386,7 @@ $retVal = $worksheet->getCell('B3')->getCalculatedValue(); // $retVal = 14 ``` -``` php +```php $retVal = call_user_func_array( ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'DAYOFMONTH'], ['25-Dec-2008'] @@ -1444,7 +1444,7 @@ day year. ##### Examples -``` php +```php $worksheet->setCellValue('B1', 'Start Date') ->setCellValue('C1', 'End Date') ->setCellValue('A2', 'Year') @@ -1469,7 +1469,7 @@ $retVal = $worksheet->getCell('E4')->getCalculatedValue(); // $retVal = 1557 ``` -``` php +```php $date1 = 37655.0; // Excel timestamp for 25-Oct-2007 $date2 = 39233.0; // Excel timestamp for 8-Dec-2015 @@ -1529,7 +1529,7 @@ or an Excel timestamp value (real), depending on the value of ##### Examples -``` php +```php $worksheet->setCellValue('A1', 'Date String') ->setCellValue('A2', '1-Jan-2008') ->setCellValue('A3', '29-Feb-2008'); @@ -1548,7 +1548,7 @@ $retVal = $worksheet->getCell('B3')->getCalculatedValue(); // $retVal = 39141.0 (28-Feb-2007) ``` -``` php +```php \PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType( \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL ); @@ -1602,7 +1602,7 @@ or an Excel timestamp value (real), depending on the value of ##### Examples -``` php +```php $worksheet->setCellValue('A1', 'Date String') ->setCellValue('A2', '1-Jan-2000') ->setCellValue('A3', '14-Feb-2009'); @@ -1619,7 +1619,7 @@ $retVal = $worksheet->getCell('B3')->getCalculatedValue(); // $retVal = 39507.0 (29-Feb-2008) ``` -``` php +```php \PhpOffice\PhpSpreadsheet\Calculation\Functions::setReturnDateType( \PhpOffice\PhpSpreadsheet\Calculation\Functions::RETURNDATE_EXCEL ); @@ -1661,7 +1661,7 @@ This is an integer ranging from 0 to 23. ##### Examples -``` php +```php $worksheet->setCellValue('A1', 'Time String') ->setCellValue('A2', '31-Dec-2008 17:30') ->setCellValue('A3', '14-Feb-2008 4:20 AM') @@ -1681,7 +1681,7 @@ $retVal = $worksheet->getCell('B4')->getCalculatedValue(); // $retVal = 16 ``` -``` php +```php $retVal = call_user_func_array( ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'HOUROFDAY'], ['09:30'] @@ -1719,7 +1719,7 @@ This is an integer ranging from 0 to 59. ##### Examples -``` php +```php $worksheet->setCellValue('A1', 'Time String') ->setCellValue('A2', '31-Dec-2008 17:30') ->setCellValue('A3', '14-Feb-2008 4:20 AM') @@ -1739,7 +1739,7 @@ $retVal = $worksheet->getCell('B4')->getCalculatedValue(); // $retVal = 45 ``` -``` php +```php $retVal = call_user_func_array( ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'MINUTE'], ['09:30'] @@ -1777,7 +1777,7 @@ This is an integer ranging from 1 to 12. ##### Examples -``` php +```php $worksheet->setCellValue('A1', 'Date String'); $worksheet->setCellValue('A2', '31-Dec-2008'); $worksheet->setCellValue('A3', '14-Feb-2008'); @@ -1792,7 +1792,7 @@ $retVal = $worksheet->getCell('B3')->getCalculatedValue(); // $retVal = 2 ``` -``` php +```php $retVal = call_user_func_array( ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'MONTHOFYEAR'], ['14-July-2008'] @@ -1847,10 +1847,10 @@ The number of working days between startDate and endDate. ##### Examples -``` php +```php ``` -``` php +```php ``` ##### Notes @@ -1880,10 +1880,10 @@ or an Excel timestamp value (real), depending on the value of ##### Examples -``` php +```php ``` -``` php +```php ``` ##### Notes @@ -1917,7 +1917,7 @@ This is an integer ranging from 0 to 59. ##### Examples -``` php +```php $worksheet->setCellValue('A1', 'Time String') ->setCellValue('A2', '31-Dec-2008 17:30:20') ->setCellValue('A3', '14-Feb-2008 4:20 AM') @@ -1937,7 +1937,7 @@ $retVal = $worksheet->getCell('B4')->getCalculatedValue(); // $retVal = 59 ``` -``` php +```php $retVal = call_user_func_array( ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'SECOND'], ['09:30:17'] @@ -2002,7 +2002,7 @@ value of method. ##### Examples -``` php +```php $worksheet->setCellValue('A1', 'Date String') ->setCellValue('A2', '31-Dec-2008') ->setCellValue('A3', '14-Feb-2008'); @@ -2021,7 +2021,7 @@ $retVal = $worksheet->getCell('B4')->getCalculatedValue(); // $retVal = 2 ``` -``` php +```php $retVal = call_user_func_array( ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'WEEKDAY'], ['14-July-2008'] @@ -2066,7 +2066,7 @@ This is an integer year value. ##### Examples -``` php +```php $worksheet->setCellValue('A1', 'Date String') ->setCellValue('A2', '17-Jul-1982') ->setCellValue('A3', '16-Apr-2009'); @@ -2081,7 +2081,7 @@ $retVal = $worksheet->getCell('B3')->getCalculatedValue(); // $retVal = 2009 ``` -``` php +```php $retVal = call_user_func_array( ['\PhpOffice\PhpSpreadsheet\Calculation\Functions', 'YEAR'], ['14-July-2001'] diff --git a/docs/topics/creating-spreadsheet.md b/docs/topics/creating-spreadsheet.md index dceafe4b..3a82623e 100644 --- a/docs/topics/creating-spreadsheet.md +++ b/docs/topics/creating-spreadsheet.md @@ -20,7 +20,7 @@ Details of the different spreadsheet formats supported, and the options available to read them into a Spreadsheet object are described fully in the [Reading Files](./reading-files.md) document. -``` php +```php $inputFileName = './sampleData/example1.xls'; /** Load $inputFileName to a Spreadsheet object **/ @@ -32,7 +32,7 @@ $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($inputFileName); If you want to create a new workbook, rather than load one from file, then you simply need to instantiate it as a new Spreadsheet object. -``` php +```php /** Create a new Spreadsheet Object **/ $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); ``` @@ -53,7 +53,7 @@ then you also need to "break" these cyclic references before doing so. PhpSpreadsheet provides the `disconnectWorksheets()` method for this purpose. -``` php +```php $spreadsheet->disconnectWorksheets(); unset($spreadsheet); ``` diff --git a/docs/topics/memory_saving.md b/docs/topics/memory_saving.md index 4c9a848f..157bb704 100644 --- a/docs/topics/memory_saving.md +++ b/docs/topics/memory_saving.md @@ -16,7 +16,7 @@ cache usages. To enable cell caching, you must provide your own implementation of cache like so: -``` php +```php $cache = new MyCustomPsr16Implementation(); \PhpOffice\PhpSpreadsheet\Settings::setCache($cache); diff --git a/docs/topics/migration-from-PHPExcel.md b/docs/topics/migration-from-PHPExcel.md index cc469768..6dc14f1f 100644 --- a/docs/topics/migration-from-PHPExcel.md +++ b/docs/topics/migration-from-PHPExcel.md @@ -12,7 +12,7 @@ need to be done. automatically your codebase. Assuming your files to be migrated lives in `src/`, you can run the migration like so: -``` sh +```sh composer require rector/rector --dev vendor/bin/rector process src --set phpexcel-to-phpspreadsheet composer remove rector/rector diff --git a/docs/topics/reading-and-writing-to-file.md b/docs/topics/reading-and-writing-to-file.md index 13b62e01..2e0a9d5f 100644 --- a/docs/topics/reading-and-writing-to-file.md +++ b/docs/topics/reading-and-writing-to-file.md @@ -33,7 +33,7 @@ You can create a `\PhpOffice\PhpSpreadsheet\Reader\IReader` instance using `\PhpOffice\PhpSpreadsheet\IOFactory` in automatic file type resolving mode using the following code sample: -``` php +```php $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load("05featuredemo.xlsx"); ``` @@ -45,7 +45,7 @@ If you need to set some properties on the reader, (e.g. to only read data, see more about this later), then you may instead want to use this variant: -``` php +```php $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile("05featuredemo.xlsx"); $reader->setReadDataOnly(true); $reader->load("05featuredemo.xlsx"); @@ -55,7 +55,7 @@ You can create a `\PhpOffice\PhpSpreadsheet\Reader\IReader` instance using `\PhpOffice\PhpSpreadsheet\IOFactory` in explicit mode using the following code sample: -``` php +```php $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader("Xlsx"); $spreadsheet = $reader->load("05featuredemo.xlsx"); ``` @@ -68,7 +68,7 @@ mode. You can create a `\PhpOffice\PhpSpreadsheet\Writer\IWriter` instance using `\PhpOffice\PhpSpreadsheet\IOFactory`: -``` php +```php $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, "Xlsx"); $writer->save("05featuredemo.xlsx"); ``` @@ -84,7 +84,7 @@ outputting the in-memory spreadsheet to a .xlsx file. You can read an .xlsx file using the following code: -``` php +```php $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); $spreadsheet = $reader->load("05featuredemo.xlsx"); ``` @@ -94,7 +94,7 @@ $spreadsheet = $reader->load("05featuredemo.xlsx"); You can set the option setReadDataOnly on the reader, to instruct the reader to ignore styling, data validation, … and just read cell data: -``` php +```php $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); $reader->setReadDataOnly(true); $spreadsheet = $reader->load("05featuredemo.xlsx"); @@ -105,7 +105,7 @@ $spreadsheet = $reader->load("05featuredemo.xlsx"); You can set the option setLoadSheetsOnly on the reader, to instruct the reader to only load the sheets with a given name: -``` php +```php $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx(); $reader->setLoadSheetsOnly(["Sheet 1", "My special sheet"]); $spreadsheet = $reader->load("05featuredemo.xlsx"); @@ -122,7 +122,7 @@ read using the `\PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter`. The following code will only read row 1 and rows 20 – 30 of any sheet in the Excel file: -``` php +```php class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter { public function readCell($column, $row, $worksheetName = '') { @@ -145,7 +145,7 @@ $spreadsheet = $reader->load("06largescale.xlsx"); You can write an .xlsx file using the following code: -``` php +```php $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet); $writer->save("05featuredemo.xlsx"); ``` @@ -156,7 +156,7 @@ By default, this writer pre-calculates all formulas in the spreadsheet. This can be slow on large spreadsheets, and maybe even unwanted. You can however disable formula pre-calculation: -``` php +```php $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet); $writer->setPreCalculateFormulas(false); $writer->save("05featuredemo.xlsx"); @@ -201,7 +201,7 @@ PHP. You can read an .xls file using the following code: -``` php +```php $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls(); $spreadsheet = $reader->load("05featuredemo.xls"); ``` @@ -211,7 +211,7 @@ $spreadsheet = $reader->load("05featuredemo.xls"); You can set the option setReadDataOnly on the reader, to instruct the reader to ignore styling, data validation, … and just read cell data: -``` php +```php $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls(); $reader->setReadDataOnly(true); $spreadsheet = $reader->load("05featuredemo.xls"); @@ -222,7 +222,7 @@ $spreadsheet = $reader->load("05featuredemo.xls"); You can set the option setLoadSheetsOnly on the reader, to instruct the reader to only load the sheets with a given name: -``` php +```php $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls(); $reader->setLoadSheetsOnly(["Sheet 1", "My special sheet"]); $spreadsheet = $reader->load("05featuredemo.xls"); @@ -239,7 +239,7 @@ read using the `\PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter`. The following code will only read row 1 and rows 20 to 30 of any sheet in the Excel file: -``` php +```php class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter { public function readCell($column, $row, $worksheetName = '') { @@ -262,7 +262,7 @@ $spreadsheet = $reader->load("06largescale.xls"); You can write an .xls file using the following code: -``` php +```php $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xls($spreadsheet); $writer->save("05featuredemo.xls"); ``` @@ -282,7 +282,7 @@ spreadsheets via PHP. You can read an Excel 2003 .xml file using the following code: -``` php +```php $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xml(); $spreadsheet = $reader->load("05featuredemo.xml"); ``` @@ -298,7 +298,7 @@ read using the `\PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter`. The following code will only read row 1 and rows 20 to 30 of any sheet in the Excel file: -``` php +```php class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter { public function readCell($column, $row, $worksheetName = '') { @@ -333,7 +333,7 @@ regarding to styling cells and handling large spreadsheets via PHP. You can read an .slk file using the following code: -``` php +```php $reader = new \PhpOffice\PhpSpreadsheet\Reader\Slk(); $spreadsheet = $reader->load("05featuredemo.slk"); ``` @@ -349,7 +349,7 @@ read using the `\PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter`. The following code will only read row 1 and rows 20 to 30 of any sheet in the SYLK file: -``` php +```php class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter { public function readCell($column, $row, $worksheetName = '') { @@ -378,7 +378,7 @@ Open Office or Libre Office Calc files. You can read an .ods file using the following code: -``` php +```php $reader = new \PhpOffice\PhpSpreadsheet\Reader\Ods(); $spreadsheet = $reader->load("05featuredemo.ods"); ``` @@ -394,7 +394,7 @@ read using the `\PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter`. The following code will only read row 1 and rows 20 to 30 of any sheet in the Calc file: -``` php +```php class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter { public function readCell($column, $row, $worksheetName = '') { @@ -427,7 +427,7 @@ regarding to styling cells, number formatting, ... You can read a .csv file using the following code: -``` php +```php $reader = new \PhpOffice\PhpSpreadsheet\Reader\Csv(); $spreadsheet = $reader->load("sample.csv"); ``` @@ -449,7 +449,7 @@ were created in Microsoft Office Excel the correct input encoding may rather be Windows-1252 (CP1252). Always make sure that the input encoding is set appropriately. -``` php +```php $reader = new \PhpOffice\PhpSpreadsheet\Reader\Csv(); $reader->setInputEncoding('CP1252'); $reader->setDelimiter(';'); @@ -464,7 +464,7 @@ $spreadsheet = $reader->load("sample.csv"); CSV files can only contain one worksheet. Therefore, you can specify which sheet to read from CSV: -``` php +```php $reader->setSheetIndex(0); ``` @@ -475,7 +475,7 @@ data into an existing `Spreadsheet` object. The following code loads a CSV file into an existing `$spreadsheet` containing some sheets, and imports onto the 6th sheet: -``` php +```php $reader = new \PhpOffice\PhpSpreadsheet\Reader\Csv(); $reader->setDelimiter(';'); $reader->setEnclosure(''); @@ -490,7 +490,7 @@ $reader->loadIntoExisting("05featuredemo.csv", $spreadsheet); You can write a .csv file using the following code: -``` php +```php $writer = new \PhpOffice\PhpSpreadsheet\Writer\Csv($spreadsheet); $writer->save("05featuredemo.csv"); ``` @@ -502,7 +502,7 @@ as a separator. You can instruct `\PhpOffice\PhpSpreadsheet\Writer\Csv` some options before writing a CSV file: -``` php +```php $writer = new \PhpOffice\PhpSpreadsheet\Writer\Csv($spreadsheet); $writer->setDelimiter(';'); $writer->setEnclosure(''); @@ -517,7 +517,7 @@ $writer->save("05featuredemo.csv"); CSV files can only contain one worksheet. Therefore, you can specify which sheet to write to CSV: -``` php +```php $writer->setSheetIndex(0); ``` @@ -527,7 +527,7 @@ By default, this writer pre-calculates all formulas in the spreadsheet. This can be slow on large spreadsheets, and maybe even unwanted. You can however disable formula pre-calculation: -``` php +```php $writer = new \PhpOffice\PhpSpreadsheet\Writer\Csv($spreadsheet); $writer->setPreCalculateFormulas(false); $writer->save("05featuredemo.csv"); @@ -542,7 +542,7 @@ it should explicitly include a BOM file header; if it doesn't, Excel will not interpret those characters correctly. This can be enabled by using the following code: -``` php +```php $writer = new \PhpOffice\PhpSpreadsheet\Writer\Csv($spreadsheet); $writer->setUseBOM(true); $writer->save("05featuredemo.csv"); @@ -560,14 +560,14 @@ to set the characters explicitly as shown below. English users will want to use this before doing the export: -``` php +```php \PhpOffice\PhpSpreadsheet\Shared\StringHelper::setDecimalSeparator('.'); \PhpOffice\PhpSpreadsheet\Shared\StringHelper::setThousandsSeparator(','); ``` German users will want to use the opposite values. -``` php +```php \PhpOffice\PhpSpreadsheet\Shared\StringHelper::setDecimalSeparator(','); \PhpOffice\PhpSpreadsheet\Shared\StringHelper::setThousandsSeparator('.'); ``` @@ -592,7 +592,7 @@ regarding to styling cells, number formatting, ... You can read an .html or .htm file using the following code: -``` php +```php $reader = new \PhpOffice\PhpSpreadsheet\Reader\Html(); $spreadsheet = $reader->load("05featuredemo.html"); @@ -610,7 +610,7 @@ first worksheet by default. You can write a .htm file using the following code: -``` php +```php $writer = new \PhpOffice\PhpSpreadsheet\Writer\Html($spreadsheet); $writer->save("05featuredemo.htm"); @@ -621,7 +621,7 @@ $writer->save("05featuredemo.htm"); HTML files can contain one or more worksheets. If you want to write all sheets into a single HTML file, use the following code: -``` php +```php $writer->writeAllSheets(); ``` @@ -630,7 +630,7 @@ $writer->writeAllSheets(); HTML files can contain one or more worksheets. Therefore, you can specify which sheet to write to HTML: -``` php +```php $writer->setSheetIndex(0); ``` @@ -639,19 +639,19 @@ $writer->setSheetIndex(0); There might be situations where you want to explicitly set the included images root. For example, instead of: - ``` html + ```html ``` You might want to see: -``` html +```html ``` You can use the following code to achieve this result: -``` php +```php $writer->setImagesRoot('http://www.example.com'); ``` @@ -661,7 +661,7 @@ By default, this writer pre-calculates all formulas in the spreadsheet. This can be slow on large spreadsheets, and maybe even unwanted. You can however disable formula pre-calculation: -``` php +```php $writer = new \PhpOffice\PhpSpreadsheet\Writer\Html($spreadsheet); $writer->setPreCalculateFormulas(false); @@ -686,7 +686,7 @@ Supported methods: Here's an example which retrieves all parts independently and merges them into a resulting HTML page: -``` php +```php $writer = new \PhpOffice\PhpSpreadsheet\Writer\Html($spreadsheet); $hdr = $writer->generateHTMLHeader(); $sty = $writer->generateStyles(false); // do not write @@ -757,7 +757,7 @@ own circumstances. You can instantiate a writer with its specific name, like so: -``` php +```php $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Mpdf'); ``` @@ -765,7 +765,7 @@ Or you can register which writer you are using with a more generic name, so you don't need to remember which library you chose, only that you want to write PDF files: -``` php +```php $class = \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf::class; \PhpOffice\PhpSpreadsheet\IOFactory::registerWriter('Pdf', $class); $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Pdf'); @@ -773,7 +773,7 @@ $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Pdf') Or you can instantiate directly the writer of your choice like so: -``` php +```php $writer = \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf($spreadsheet); ``` @@ -782,7 +782,7 @@ $writer = \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf($spreadsheet); If you need a custom implementation, or custom configuration, of a supported PDF library. You can extends the PDF library, and the PDF writer like so: -``` php +```php class My_Custom_TCPDF extends TCPDF { // ... @@ -808,7 +808,7 @@ class My_Custom_TCPDF_Writer extends \PhpOffice\PhpSpreadsheet\Writer\Pdf\Tcpdf Once you have identified the Renderer that you wish to use for PDF generation, you can write a .pdf file using the following code: -``` php +```php $writer = new \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf($spreadsheet); $writer->save("05featuredemo.pdf"); ``` @@ -821,7 +821,7 @@ first worksheet by default. PDF files can contain one or more worksheets. If you want to write all sheets into a single PDF file, use the following code: -``` php +```php $writer->writeAllSheets(); ``` @@ -830,7 +830,7 @@ $writer->writeAllSheets(); PDF files can contain one or more worksheets. Therefore, you can specify which sheet to write to PDF: -``` php +```php $writer->setSheetIndex(0); ``` @@ -840,7 +840,7 @@ By default, this writer pre-calculates all formulas in the spreadsheet. This can be slow on large spreadsheets, and maybe even unwanted. You can however disable formula pre-calculation: -``` php +```php $writer = new \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf($spreadsheet); $writer->setPreCalculateFormulas(false); @@ -868,7 +868,7 @@ page setup properties, headers etc. Here is an example how to open a template file, fill in a couple of fields and save it again: -``` php +```php $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load('template.xlsx'); $worksheet = $spreadsheet->getActiveSheet(); diff --git a/docs/topics/reading-files.md b/docs/topics/reading-files.md index 779082dc..1451f2ab 100644 --- a/docs/topics/reading-files.md +++ b/docs/topics/reading-files.md @@ -22,7 +22,7 @@ The simplest way to load a workbook file is to let PhpSpreadsheet's IO Factory identify the file type and load it, calling the static `load()` method of the `\PhpOffice\PhpSpreadsheet\IOFactory` class. -``` php +```php $inputFileName = './sampleData/example1.xls'; /** Load $inputFileName to a Spreadsheet Object **/ @@ -59,7 +59,7 @@ supported filetype by name. However, you may get unpredictable results if the file isn't of the right type (e.g. it is a CSV with an extension of .xls), although this type of exception should normally be trapped. -``` php +```php $inputFileName = './sampleData/example1.xls'; /** Create a new Xls Reader **/ @@ -81,7 +81,7 @@ Alternatively, you can use the IO Factory's `createReader()` method to instantiate the reader object for you, simply telling it the file type of the reader that you want instantiating. -``` php +```php $inputFileType = 'Xls'; // $inputFileType = 'Xlsx'; // $inputFileType = 'Xml'; @@ -104,7 +104,7 @@ If you're uncertain of the filetype, you can use the `IOFactory::identify()` method to identify the reader that you need, before using the `createReader()` method to instantiate the reader object. -``` php +```php $inputFileName = './sampleData/example1.xls'; /** Identify the type of $inputFileName **/ @@ -131,7 +131,7 @@ need any of the cell formatting information, then you can set the reader to read only the data values and any formulae from each cell using the `setReadDataOnly()` method. -``` php +```php $inputFileType = 'Xls'; $inputFileName = './sampleData/example1.xls'; @@ -176,7 +176,7 @@ in reading. To read a single sheet, you can pass that sheet name as a parameter to the `setLoadSheetsOnly()` method. -``` php +```php $inputFileType = 'Xls'; $inputFileName = './sampleData/example1.xls'; $sheetname = 'Data Sheet #2'; @@ -195,7 +195,7 @@ for a working example of this code. If you want to read more than just a single sheet, you can pass a list of sheet names as an array parameter to the `setLoadSheetsOnly()` method. -``` php +```php $inputFileType = 'Xls'; $inputFileName = './sampleData/example1.xls'; $sheetnames = ['Data Sheet #1','Data Sheet #3']; @@ -214,7 +214,7 @@ for a working example of this code. To reset this option to the default, you can call the `setLoadAllSheets()` method. -``` php +```php $inputFileType = 'Xls'; $inputFileName = './sampleData/example1.xls'; @@ -248,7 +248,7 @@ should be read by the loader. A read filter must implement the whether a workbook cell identified by those arguments should be read or not. -``` php +```php $inputFileType = 'Xls'; $inputFileName = './sampleData/example1.xls'; $sheetname = 'Data Sheet #3'; @@ -286,7 +286,7 @@ a very specific circumstance (when you only want cells in the range A1:E7 from your worksheet. A generic Read Filter would probably be more useful: -``` php +```php /** Define a Read Filter class implementing \PhpOffice\PhpSpreadsheet\Reader\IReadFilter */ class MyReadFilter implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter { @@ -324,7 +324,7 @@ to read and process a large workbook in "chunks": an example of this usage might be when transferring data from an Excel worksheet to a database. -``` php +```php $inputFileType = 'Xls'; $inputFileName = './sampleData/example2.xls'; @@ -393,7 +393,7 @@ the `setSheetIndex()` method of the `$reader`, then use the `loadIntoExisting()` method rather than the `load()` method to actually read the file into that worksheet. -``` php +```php $inputFileType = 'Csv'; $inputFileNames = [ './sampleData/example1.csv', @@ -452,7 +452,7 @@ Class that we defined in [the above section](#reading-only-specific-columns-and- and the `setSheetIndex()` method of the `$reader`, we can split the CSV file across several individual worksheets. -``` php +```php $inputFileType = 'Csv'; $inputFileName = './sampleData/example2.csv'; @@ -523,7 +523,7 @@ cannot auto-detect, it will default to the comma. If this does not fit your use-case, you can manually specify a separator by using the `setDelimiter()` method. -``` php +```php $inputFileType = 'Csv'; $inputFileName = './sampleData/example1.tsv'; @@ -585,7 +585,7 @@ it encountered a hyperlink, or HTML markup within a CSV file. So using a Value Binder allows a great deal more flexibility in the loader logic when reading unformatted text files. -``` php +```php /** Tell PhpSpreadsheet that we want to use the Advanced Value Binder **/ \PhpOffice\PhpSpreadsheet\Cell\Cell::setValueBinder( new \PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder() ); @@ -619,7 +619,7 @@ manner. The PhpSpreadsheet Readers throw a `\PhpOffice\PhpSpreadsheet\Reader\Exception`. -``` php +```php $inputFileName = './sampleData/example-1.xls'; try { @@ -646,7 +646,7 @@ whole file. The `listWorksheetNames()` method returns a simple array listing each worksheet name within the workbook: -``` php +```php $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); $worksheetNames = $reader->listWorksheetNames($inputFileName); @@ -667,7 +667,7 @@ for a working example of this code. The `listWorksheetInfo()` method returns a nested array, with each entry listing the name and dimensions for a worksheet: -``` php +```php $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType); $worksheetData = $reader->listWorksheetInfo($inputFileName); diff --git a/docs/topics/recipes.md b/docs/topics/recipes.md index b0956b6e..f85576a2 100644 --- a/docs/topics/recipes.md +++ b/docs/topics/recipes.md @@ -20,7 +20,7 @@ metadata to search for a specific document in its document lists. Setting spreadsheet metadata is done as follows: -``` php +```php $spreadsheet->getProperties() ->setCreator("Maarten Balliauw") ->setLastModifiedBy("Maarten Balliauw") @@ -38,13 +38,13 @@ $spreadsheet->getProperties() The following line of code sets the active sheet index to the first sheet: -``` php +```php $spreadsheet->setActiveSheetIndex(0); ``` You can also set the active sheet by its name/title -``` php +```php $spreadsheet->setActiveSheetIndexByName('DataSheet') ``` @@ -68,7 +68,7 @@ UST. Writing a date value in a cell consists of 2 lines of code. Select the method that suits you the best. Here are some examples: -``` php +```php // MySQL-like timestamp '2008-12-31' or date string \PhpOffice\PhpSpreadsheet\Cell\Cell::setValueBinder( new \PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder() ); @@ -136,14 +136,14 @@ The following line of code writes the formula formula must start with `=` to make PhpSpreadsheet recognise this as a formula. -``` php +```php $spreadsheet->getActiveSheet()->setCellValue('B8','=IF(C4>500,"profit","loss")'); ``` If you want to write a string beginning with an `=` character to a cell, then you should use the `setCellValueExplicit()` method. -``` php +```php $spreadsheet->getActiveSheet() ->setCellValueExplicit( 'B8', @@ -154,14 +154,14 @@ $spreadsheet->getActiveSheet() A cell's formula can be read again using the following line of code: -``` php +```php $formula = $spreadsheet->getActiveSheet()->getCell('B8')->getValue(); ``` If you need the calculated value of a cell, use the following code. This is further explained in [the calculation engine](./calculation-engine.md). -``` php +```php $value = $spreadsheet->getActiveSheet()->getCell('B8')->getCalculatedValue(); ``` @@ -171,7 +171,7 @@ Some localisation elements have been included in PhpSpreadsheet. You can set a locale by changing the settings. To set the locale to Russian you would use: -``` php +```php $locale = 'ru'; $validLocale = \PhpOffice\PhpSpreadsheet\Settings::setLocale($locale); if (!$validLocale) { @@ -185,7 +185,7 @@ will return an error, and English settings will be used throughout. Once you have set a locale, you can translate a formula from its internal English coding. -``` php +```php $formula = $spreadsheet->getActiveSheet()->getCell('B8')->getValue(); $translatedFormula = \PhpOffice\PhpSpreadsheet\Calculation\Calculation::getInstance()->_translateFormulaToLocale($formula); ``` @@ -194,7 +194,7 @@ You can also create a formula using the function names and argument separators appropriate to the defined locale; then translate it to English before setting the cell value: -``` php +```php $formula = '=ДНЕЙ360(ДАТА(2010;2;5);ДАТА(2010;12;31);ИСТИНА)'; $internalFormula = \PhpOffice\PhpSpreadsheet\Calculation\Calculation::getInstance()->translateFormulaToEnglish($formula); $spreadsheet->getActiveSheet()->setCellValue('B8',$internalFormula); @@ -232,7 +232,7 @@ the cell. Here is how to achieve this in PhpSpreadsheet: -``` php +```php $spreadsheet->getActiveSheet()->getCell('A1')->setValue("hello\nworld"); $spreadsheet->getActiveSheet()->getStyle('A1')->getAlignment()->setWrapText(true); ``` @@ -247,7 +247,7 @@ AdvancedValuebinder.php automatically turns on "wrap text" for the cell when it sees a newline character in a string that you are inserting in a cell. Just like Microsoft Office Excel. Try this: -``` php +```php \PhpOffice\PhpSpreadsheet\Cell\Cell::setValueBinder( new \PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder() ); $spreadsheet->getActiveSheet()->getCell('A1')->setValue("hello\nworld"); @@ -261,7 +261,7 @@ You can set a cell's datatype explicitly by using the cell's setValueExplicit method, or the setCellValueExplicit method of a worksheet. Here's an example: -``` php +```php $spreadsheet->getActiveSheet()->getCell('A1') ->setValueExplicit( '25', @@ -273,7 +273,7 @@ $spreadsheet->getActiveSheet()->getCell('A1') You can make a cell a clickable URL by setting its hyperlink property: -``` php +```php $spreadsheet->getActiveSheet()->setCellValue('E26', 'www.phpexcel.net'); $spreadsheet->getActiveSheet()->getCell('E26')->getHyperlink()->setUrl('https://www.example.com'); ``` @@ -281,7 +281,7 @@ $spreadsheet->getActiveSheet()->getCell('E26')->getHyperlink()->setUrl('https:// If you want to make a hyperlink to another worksheet/cell, use the following code: -``` php +```php $spreadsheet->getActiveSheet()->setCellValue('E26', 'www.phpexcel.net'); $spreadsheet->getActiveSheet()->getCell('E26')->getHyperlink()->setUrl("sheet://'Sheetname'!A1"); ``` @@ -293,7 +293,7 @@ $spreadsheet->getActiveSheet()->getCell('E26')->getHyperlink()->setUrl("sheet:// Setting a worksheet's page orientation and size can be done using the following lines of code: -``` php +```php $spreadsheet->getActiveSheet()->getPageSetup() ->setOrientation(\PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::ORIENTATION_LANDSCAPE); $spreadsheet->getActiveSheet()->getPageSetup() @@ -324,7 +324,7 @@ setFitToHeight(...) | 1 | setFitToPage(TRUE) | value 0 mean Here is how to fit to 1 page wide by infinite pages tall: -``` php +```php $spreadsheet->getActiveSheet()->getPageSetup()->setFitToWidth(1); $spreadsheet->getActiveSheet()->getPageSetup()->setFitToHeight(0); ``` @@ -340,7 +340,7 @@ the initial values. To set page margins for a worksheet, use this code: -``` php +```php $spreadsheet->getActiveSheet()->getPageMargins()->setTop(1); $spreadsheet->getActiveSheet()->getPageMargins()->setRight(0.75); $spreadsheet->getActiveSheet()->getPageMargins()->setLeft(0.75); @@ -356,7 +356,7 @@ Note that the margin values are specified in inches. To center a page horizontally/vertically, you can use the following code: -``` php +```php $spreadsheet->getActiveSheet()->getPageSetup()->setHorizontalCentered(true); $spreadsheet->getActiveSheet()->getPageSetup()->setVerticalCentered(false); ``` @@ -366,7 +366,7 @@ $spreadsheet->getActiveSheet()->getPageSetup()->setVerticalCentered(false); Setting a worksheet's print header and footer can be done using the following lines of code: -``` php +```php $spreadsheet->getActiveSheet()->getHeaderFooter() ->setOddHeader('&C&HPlease treat this document as confidential!'); $spreadsheet->getActiveSheet()->getHeaderFooter() @@ -460,13 +460,13 @@ $spreadsheet->getActiveSheet()->getHeaderFooter()->addImage($drawing, \PhpOffice To set a print break, use the following code, which sets a row break on row 10. -``` php +```php $spreadsheet->getActiveSheet()->setBreak('A10', \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_ROW); ``` The following line of code sets a print break on column D: -``` php +```php $spreadsheet->getActiveSheet()->setBreak('D10', \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_COLUMN); ``` @@ -484,7 +484,7 @@ PhpSpreadsheet can repeat specific rows/cells at top/left of a page. The following code is an example of how to repeat row 1 to 5 on each printed page of a specific worksheet: -``` php +```php $spreadsheet->getActiveSheet()->getPageSetup()->setRowsToRepeatAtTopByStartAndEnd(1, 5); ``` @@ -492,13 +492,13 @@ $spreadsheet->getActiveSheet()->getPageSetup()->setRowsToRepeatAtTopByStartAndEn To specify a worksheet's printing area, use the following code: -``` php +```php $spreadsheet->getActiveSheet()->getPageSetup()->setPrintArea('A1:E5'); ``` There can also be multiple printing areas in a single worksheet: -``` php +```php $spreadsheet->getActiveSheet()->getPageSetup()->setPrintArea('A1:E5,G4:M20'); ``` @@ -511,7 +511,7 @@ For example, one can set the foreground colour of a cell to red, aligned to the right, and the border to black and thick border style. Let's do that on cell B2: -``` php +```php $spreadsheet->getActiveSheet()->getStyle('B2') ->getFont()->getColor()->setARGB(\PhpOffice\PhpSpreadsheet\Style\Color::COLOR_RED); $spreadsheet->getActiveSheet()->getStyle('B2') @@ -533,7 +533,7 @@ $spreadsheet->getActiveSheet()->getStyle('B2') `getStyle()` also accepts a cell range as a parameter. For example, you can set a red background color on a range of cells: -``` php +```php $spreadsheet->getActiveSheet()->getStyle('B3:B7')->getFill() ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID) ->getStartColor()->setARGB('FFFF0000'); @@ -548,7 +548,7 @@ There is also an alternative manner to set styles. The following code sets a cell's style to font bold, alignment right, top border thin and a gradient fill: -``` php +```php $styleArray = [ 'font' => [ 'bold' => true, @@ -578,7 +578,7 @@ $spreadsheet->getActiveSheet()->getStyle('A3')->applyFromArray($styleArray); Or with a range of cells: -``` php +```php $spreadsheet->getActiveSheet()->getStyle('B3:B7')->applyFromArray($styleArray); ``` @@ -602,7 +602,7 @@ number format code unless you need a custom number format. In PhpSpreadsheet, you can also apply various predefined number formats. Example: -``` php +```php $spreadsheet->getActiveSheet()->getStyle('A1')->getNumberFormat() ->setFormatCode(\PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_COMMA_SEPARATED1); ``` @@ -614,7 +614,7 @@ up as 1.587,20) You can achieve exactly the same as the above by using this: -``` php +```php $spreadsheet->getActiveSheet()->getStyle('A1')->getNumberFormat() ->setFormatCode('#,##0.00'); ``` @@ -623,7 +623,7 @@ In Microsoft Office Excel, as well as in PhpSpreadsheet, you will have to interact with raw number format codes whenever you need some special custom number format. Example: -``` php +```php $spreadsheet->getActiveSheet()->getStyle('A1')->getNumberFormat() ->setFormatCode('[Blue][>=3000]$#,##0;[Red][<0]$#,##0;$#,##0'); ``` @@ -631,7 +631,7 @@ $spreadsheet->getActiveSheet()->getStyle('A1')->getNumberFormat() Another example is when you want numbers zero-padded with leading zeros to a fixed length: -``` php +```php $spreadsheet->getActiveSheet()->getCell('A1')->setValue(19); $spreadsheet->getActiveSheet()->getStyle('A1')->getNumberFormat() ->setFormatCode('0000'); // will show as 0019 in Excel @@ -646,7 +646,7 @@ The readers shipped with PhpSpreadsheet come to the rescue. Load your template workbook using e.g. Xlsx reader to reveal the number format code. Example how read a number format code for cell A1: -``` php +```php $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx'); $spreadsheet = $reader->load('template.xlsx'); var_dump($spreadsheet->getActiveSheet()->getStyle('A1')->getNumberFormat()->getFormatCode()); @@ -661,14 +661,14 @@ code in *xl/styles.xml*. Let's set vertical alignment to the top for cells A1:D4 -``` php +```php $spreadsheet->getActiveSheet()->getStyle('A1:D4') ->getAlignment()->setVertical(\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_TOP); ``` Here is how to achieve wrap text: -``` php +```php $spreadsheet->getActiveSheet()->getStyle('A1:D4') ->getAlignment()->setWrapText(true); ``` @@ -678,7 +678,7 @@ $spreadsheet->getActiveSheet()->getStyle('A1:D4') It is possible to set the default style of a workbook. Let's set the default font to Arial size 8: -``` php +```php $spreadsheet->getDefaultStyle()->getFont()->setName('Arial'); $spreadsheet->getDefaultStyle()->getFont()->setSize(8); ``` @@ -689,7 +689,7 @@ In PhpSpreadsheet it is easy to apply various borders on a rectangular selection. Here is how to apply a thick red border outline around cells B2:G8. -``` php +```php $styleArray = [ 'borders' => [ 'outline' => [ @@ -839,7 +839,7 @@ is below zero, and to green if its value is zero or more. One can set a conditional style ruleset to a cell using the following code: -``` php +```php $conditional1 = new \PhpOffice\PhpSpreadsheet\Style\Conditional(); $conditional1->setConditionType(\PhpOffice\PhpSpreadsheet\Style\Conditional::CONDITION_CELLIS); $conditional1->setOperatorType(\PhpOffice\PhpSpreadsheet\Style\Conditional::OPERATOR_LESSTHAN); @@ -864,7 +864,7 @@ $spreadsheet->getActiveSheet()->getStyle('B2')->setConditionalStyles($conditiona If you want to copy the ruleset to other cells, you can duplicate the style object: -``` php +```php $spreadsheet->getActiveSheet() ->duplicateStyle( $spreadsheet->getActiveSheet()->getStyle('B2'), @@ -877,7 +877,7 @@ $spreadsheet->getActiveSheet() To add a comment to a cell, use the following code. The example below adds a comment to cell E11: -``` php +```php $spreadsheet->getActiveSheet() ->getComment('E11') ->setAuthor('Mark Baker'); @@ -899,7 +899,7 @@ $spreadsheet->getActiveSheet() To apply an autofilter to a range of cells, use the following code: -``` php +```php $spreadsheet->getActiveSheet()->setAutoFilter('A1:C9'); ``` @@ -919,45 +919,85 @@ disallow inserting rows on a specific sheet, disallow sorting, ... - Cell: offers the option to lock/unlock a cell as well as show/hide the internal formula. +**Make sure you enable worksheet protection if you need any of the +worksheet or cell protection features!** This can be done using the following +code: + +```php +$spreadsheet->getActiveSheet()->getProtection()->setSheet(true); +``` + +### Document + An example on setting document security: -``` php -$spreadsheet->getSecurity()->setLockWindows(true); -$spreadsheet->getSecurity()->setLockStructure(true); -$spreadsheet->getSecurity()->setWorkbookPassword("PhpSpreadsheet"); +```php +$security = $spreadsheet->getSecurity(); +$security->setLockWindows(true); +$security->setLockStructure(true); +$security->setWorkbookPassword("PhpSpreadsheet"); ``` +### Worksheet + An example on setting worksheet security: -``` php -$spreadsheet->getActiveSheet() - ->getProtection()->setPassword('PhpSpreadsheet'); -$spreadsheet->getActiveSheet() - ->getProtection()->setSheet(true); -$spreadsheet->getActiveSheet() - ->getProtection()->setSort(true); -$spreadsheet->getActiveSheet() - ->getProtection()->setInsertRows(true); -$spreadsheet->getActiveSheet() - ->getProtection()->setFormatCells(true); +```php +$protection = $spreadsheet->getActiveSheet()->getProtection(); +$protection->setPassword('PhpSpreadsheet'); +$protection->setSheet(true); +$protection->setSort(true); +$protection->setInsertRows(true); +$protection->setFormatCells(true); ``` +If writing Xlsx files you can specify the algorithm used to hash the password +before calling `setPassword()` like so: + +```php +$protection = $spreadsheet->getActiveSheet()->getProtection(); +$protection->setAlgorithm(Protection::ALGORITHM_SHA_512); +$protection->setSpinCount(20000); +$protection->setPassword('PhpSpreadsheet'); +``` + +The salt should **not** be set manually and will be automatically generated +when setting a new password. + +### Cell + An example on setting cell security: -``` php +```php $spreadsheet->getActiveSheet()->getStyle('B1') ->getProtection() ->setLocked(\PhpOffice\PhpSpreadsheet\Style\Protection::PROTECTION_UNPROTECTED); ``` -**Make sure you enable worksheet protection if you need any of the -worksheet protection features!** This can be done using the following -code: +## Reading protected spreadsheet -``` php -$spreadsheet->getActiveSheet()->getProtection()->setSheet(true); +Spreadsheets that are protected as described above can always be read by +PhpSpreadsheet. There is no need to know the password or do anything special in +order to read a protected file. + +However if you need to implement a password verification mechanism, you can use the +following helper method: + + +```php +$protection = $spreadsheet->getActiveSheet()->getProtection(); +$allowed = $protection->verify('my password'); + +if ($allowed) { + doSomething(); +} else { + throw new Exception('Incorrect password'); +} ``` +If you need to completely prevent reading a file by any tool, including PhpSpreadsheet, +then you are looking for "encryption", not "protection". + ## Setting data validation on a cell Data validation is a powerful feature of Xlsx. It allows to specify an @@ -968,7 +1008,7 @@ filter can be a range (i.e. value must be between 0 and 10), a list The following piece of code only allows numbers between 10 and 20 to be entered in cell B3: -``` php +```php $validation = $spreadsheet->getActiveSheet()->getCell('B3') ->getDataValidation(); $validation->setType( \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_WHOLE ); @@ -987,7 +1027,7 @@ $validation->setFormula2(20); The following piece of code only allows an item picked from a list of data to be entered in cell B5: -``` php +```php $validation = $spreadsheet->getActiveSheet()->getCell('B5') ->getDataValidation(); $validation->setType( \PhpOffice\PhpSpreadsheet\Cell\DataValidation::TYPE_LIST ); @@ -1017,7 +1057,7 @@ the item values themselves can contain the comma `,` character itself. If you need data validation on multiple cells, one can clone the ruleset: -``` php +```php $spreadsheet->getActiveSheet()->getCell('B8')->setDataValidation(clone $validation); ``` @@ -1025,7 +1065,7 @@ $spreadsheet->getActiveSheet()->getCell('B8')->setDataValidation(clone $validati A column's width can be set using the following code: -``` php +```php $spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(12); ``` @@ -1033,7 +1073,7 @@ If you want PhpSpreadsheet to perform an automatic width calculation, use the following code. PhpSpreadsheet will approximate the column with to the width of the widest column value. -``` php +```php $spreadsheet->getActiveSheet()->getColumnDimension('B')->setAutoSize(true); ``` @@ -1070,7 +1110,7 @@ To set a worksheet's column visibility, you can use the following code. The first line explicitly shows the column C, the second line hides column D. -``` php +```php $spreadsheet->getActiveSheet()->getColumnDimension('C')->setVisible(true); $spreadsheet->getActiveSheet()->getColumnDimension('D')->setVisible(false); ``` @@ -1079,7 +1119,7 @@ $spreadsheet->getActiveSheet()->getColumnDimension('D')->setVisible(false); To group/outline a column, you can use the following code: -``` php +```php $spreadsheet->getActiveSheet()->getColumnDimension('E')->setOutlineLevel(1); ``` @@ -1087,7 +1127,7 @@ You can also collapse the column. Note that you should also set the column invisible, otherwise the collapse will not be visible in Excel 2007. -``` php +```php $spreadsheet->getActiveSheet()->getColumnDimension('E')->setCollapsed(true); $spreadsheet->getActiveSheet()->getColumnDimension('E')->setVisible(false); ``` @@ -1098,7 +1138,7 @@ on collapsing. You can instruct PhpSpreadsheet to add a summary to the right (default), or to the left. The following code adds the summary to the left: -``` php +```php $spreadsheet->getActiveSheet()->setShowSummaryRight(false); ``` @@ -1106,7 +1146,7 @@ $spreadsheet->getActiveSheet()->setShowSummaryRight(false); A row's height can be set using the following code: -``` php +```php $spreadsheet->getActiveSheet()->getRowDimension('10')->setRowHeight(100); ``` @@ -1119,7 +1159,7 @@ of values is between 0 and 409 pts, where 0 pts is a hidden row. To set a worksheet''s row visibility, you can use the following code. The following example hides row number 10. -``` php +```php $spreadsheet->getActiveSheet()->getRowDimension('10')->setVisible(false); ``` @@ -1131,21 +1171,21 @@ AutoFilter range if you save the file. To group/outline a row, you can use the following code: -``` php +```php $spreadsheet->getActiveSheet()->getRowDimension('5')->setOutlineLevel(1); ``` You can also collapse the row. Note that you should also set the row invisible, otherwise the collapse will not be visible in Excel 2007. -``` php +```php $spreadsheet->getActiveSheet()->getRowDimension('5')->setCollapsed(true); $spreadsheet->getActiveSheet()->getRowDimension('5')->setVisible(false); ``` Here's an example which collapses rows 50 to 80: -``` php +```php for ($i = 51; $i <= 80; $i++) { $spreadsheet->getActiveSheet()->setCellValue('A' . $i, "FName $i"); $spreadsheet->getActiveSheet()->setCellValue('B' . $i, "LName $i"); @@ -1162,7 +1202,7 @@ $spreadsheet->getActiveSheet()->getRowDimension(81)->setCollapsed(true); You can instruct PhpSpreadsheet to add a summary below the collapsible rows (default), or above. The following code adds the summary above: -``` php +```php $spreadsheet->getActiveSheet()->setShowSummaryBelow(false); ``` @@ -1172,13 +1212,13 @@ If you have a big piece of data you want to display in a worksheet, you can merge two or more cells together, to become one cell. This can be done using the following code: -``` php +```php $spreadsheet->getActiveSheet()->mergeCells('A18:E22'); ``` Removing a merge can be done using the unmergeCells method: -``` php +```php $spreadsheet->getActiveSheet()->unmergeCells('A18:E22'); ``` @@ -1187,7 +1227,7 @@ $spreadsheet->getActiveSheet()->unmergeCells('A18:E22'); You can insert/remove rows/columns at a specific position. The following code inserts 2 new rows, right before row 7: -``` php +```php $spreadsheet->getActiveSheet()->insertNewRowBefore(7, 2); ``` @@ -1198,7 +1238,7 @@ to a worksheet. Therefore, you must first instantiate a new `\PhpOffice\PhpSpreadsheet\Worksheet\Drawing`, and assign its properties a meaningful value: -``` php +```php $drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); $drawing->setName('Logo'); $drawing->setDescription('Logo'); @@ -1210,13 +1250,13 @@ To add the above drawing to the worksheet, use the following snippet of code. PhpSpreadsheet creates the link between the drawing and the worksheet: -``` php +```php $drawing->setWorksheet($spreadsheet->getActiveSheet()); ``` You can set numerous properties on a drawing, here are some examples: -``` php +```php $drawing->setName('Paid'); $drawing->setDescription('Paid'); $drawing->setPath('./images/paid.png'); @@ -1230,7 +1270,7 @@ $drawing->getShadow()->setDirection(45); You can also add images created using GD functions without needing to save them to disk first as In-Memory drawings. -``` php +```php // Use GD to create an in-memory image $gdImage = @imagecreatetruecolor(120, 20) or die('Cannot Initialize new GD image stream'); $textColor = imagecolorallocate($gdImage, 255, 255, 255); @@ -1258,7 +1298,7 @@ that has been loaded, and save them as individual image files to disk. The following code extracts images from the current active worksheet, and writes each as a separate file. -``` php +```php $i = 0; foreach ($spreadsheet->getActiveSheet()->getDrawingCollection() as $drawing) { if ($drawing instanceof \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing) { @@ -1303,7 +1343,7 @@ creates the following rich text string: > This invoice is ***payable within thirty days after the end of the > month*** unless specified otherwise on the invoice. -``` php +```php $richText = new \PhpOffice\PhpSpreadsheet\RichText\RichText(); $richText->createText('This invoice is '); $payable = $richText->createTextRun('payable within thirty days after the end of the month'); @@ -1319,7 +1359,7 @@ $spreadsheet->getActiveSheet()->getCell('A18')->setValue($richText); PhpSpreadsheet supports the definition of named ranges. These can be defined using the following code: -``` php +```php // Add some data $spreadsheet->setActiveSheetIndex(0); $spreadsheet->getActiveSheet()->setCellValue('A1', 'Firstname:'); @@ -1362,7 +1402,7 @@ your document is needed, it is recommended not to use `php://output`. Example of a script redirecting an Excel 2007 file to the client's browser: -``` php +```php /* Here there will be some code where you create $spreadsheet */ // redirect output to client browser @@ -1376,7 +1416,7 @@ $writer->save('php://output'); Example of a script redirecting an Xls file to the client's browser: -``` php +```php /* Here there will be some code where you create $spreadsheet */ // redirect output to client browser @@ -1404,7 +1444,7 @@ at the client browser, and/or that headers cannot be set by PHP Default column width can be set using the following code: -``` php +```php $spreadsheet->getActiveSheet()->getDefaultColumnDimension()->setWidth(12); ``` @@ -1412,7 +1452,7 @@ $spreadsheet->getActiveSheet()->getDefaultColumnDimension()->setWidth(12); Default row height can be set using the following code: -``` php +```php $spreadsheet->getActiveSheet()->getDefaultRowDimension()->setRowHeight(15); ``` @@ -1425,7 +1465,7 @@ file to a temporary location. Here''s an example which generates an image in memory and adds it to the active worksheet: -``` php +```php // Generate an image $gdImage = @imagecreatetruecolor(120, 20) or die('Cannot Initialize new GD image stream'); $textColor = imagecolorallocate($gdImage, 255, 255, 255); @@ -1446,7 +1486,7 @@ $drawing->setWorksheet($spreadsheet->getActiveSheet()); To set a worksheet's zoom level, the following code can be used: -``` php +```php $spreadsheet->getActiveSheet()->getSheetView()->setZoomScale(75); ``` @@ -1457,7 +1497,7 @@ Note that zoom level should be in range 10 - 400. Sometimes you want to set a color for sheet tab. For example you can have a red sheet tab: -``` php +```php $worksheet->getTabColor()->setRGB('FF0000'); ``` @@ -1465,7 +1505,7 @@ $worksheet->getTabColor()->setRGB('FF0000'); If you need to create more worksheets in the workbook, here is how: -``` php +```php $worksheet1 = $spreadsheet->createSheet(); $worksheet1->setTitle('Another sheet'); ``` @@ -1478,7 +1518,7 @@ worksheets in the workbook. Set a worksheet to be **hidden** using this code: -``` php +```php $spreadsheet->getActiveSheet() ->setSheetState(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_HIDDEN); ``` @@ -1500,7 +1540,7 @@ Worksheets can be set individually whether column `A` should start at left or right side. Default is left. Here is how to set columns from right-to-left. -``` php +```php // right-to-left worksheet $spreadsheet->getActiveSheet()->setRightToLeft(true); ``` diff --git a/docs/topics/settings.md b/docs/topics/settings.md index a9aae9f9..4463ceeb 100644 --- a/docs/topics/settings.md +++ b/docs/topics/settings.md @@ -13,7 +13,7 @@ Read more about [memory saving](./memory_saving.md). To enable cell caching, you must provide your own implementation of cache like so: -``` php +```php $cache = new MyCustomPsr16Implementation(); \PhpOffice\PhpSpreadsheet\Settings::setCache($cache); @@ -25,7 +25,7 @@ Some localisation elements have been included in PhpSpreadsheet. You can set a locale by changing the settings. To set the locale to Brazilian Portuguese you would use: -``` php +```php $locale = 'pt_br'; $validLocale = \PhpOffice\PhpSpreadsheet\Settings::setLocale($locale); if (!$validLocale) { diff --git a/docs/topics/worksheets.md b/docs/topics/worksheets.md index f97a0066..0199f13c 100644 --- a/docs/topics/worksheets.md +++ b/docs/topics/worksheets.md @@ -25,7 +25,7 @@ each worksheet "tab" is shown when the workbook is opened in MS Excel (or other appropriate Spreadsheet program). To access a sheet by its index, use the `getSheet()` method. -``` php +```php // Get the second sheet in the workbook // Note that sheets are indexed from 0 $spreadsheet->getSheet(1); @@ -38,7 +38,7 @@ workbook. To access a sheet by name, use the `getSheetByName()` method, specifying the name of the worksheet that you want to access. -``` php +```php // Retrieve the worksheet called 'Worksheet 1' $spreadsheet->getSheetByName('Worksheet 1'); ``` @@ -48,7 +48,7 @@ and you can access that directly. The currently active worksheet is the one that will be active when the workbook is opened in MS Excel (or other appropriate Spreadsheet program). -``` php +```php // Retrieve the current active worksheet $spreadsheet->getActiveSheet(); ``` @@ -64,7 +64,7 @@ a new "last" sheet; but you can also specify an index position as an argument, and the worksheet will be inserted at that position, shuffling all subsequent worksheets in the collection down a place. -``` php +```php $spreadsheet->createSheet(); ``` @@ -76,7 +76,7 @@ Alternatively, you can instantiate a new worksheet (setting the title to whatever you choose) and then insert it into your workbook using the `addSheet()` method. -``` php +```php // Create a new worksheet called "My Data" $myWorkSheet = new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet($spreadsheet, 'My Data'); @@ -93,7 +93,7 @@ Sheets within the same workbook can be copied by creating a clone of the worksheet you wish to copy, and then using the `addSheet()` method to insert the clone into the workbook. -``` php +```php $clonedWorksheet = clone $spreadsheet->getSheetByName('Worksheet 1'); $clonedWorksheet->setTitle('Copy of Worksheet 1'); $spreadsheet->addSheet($clonedWorksheet); @@ -117,7 +117,7 @@ duplicate name. You can delete a worksheet from a workbook, identified by its index position, using the `removeSheetByIndex()` method -``` php +```php $sheetIndex = $spreadsheet->getIndex( $spreadsheet->getSheetByName('Worksheet 1') ); diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index cdfe7b53..2d67a134 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -3456,10 +3456,8 @@ class Calculation if ((isset(self::$comparisonOperators[$opCharacter])) && (strlen($formula) > $index) && (isset(self::$comparisonOperators[$formula[$index + 1]]))) { $opCharacter .= $formula[++$index]; } - // Find out if we're currently at the beginning of a number, variable, cell reference, function, parenthesis or operand $isOperandOrFunction = preg_match($regexpMatchString, substr($formula, $index), $match); - if ($opCharacter == '-' && !$expectingOperator) { // Is it a negation instead of a minus? // Put a negation on the stack $stack->push('Unary Operator', '~', null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); @@ -3627,7 +3625,6 @@ class Calculation $expectingOperand = false; $val = $match[1]; $length = strlen($val); - if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $val, $matches)) { $val = preg_replace('/\s/u', '', $val); if (isset(self::$phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) { // it's a function @@ -3662,7 +3659,6 @@ class Calculation } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $val, $matches)) { // Watch for this case-change when modifying to allow cell references in different worksheets... // Should only be applied to the actual cell column, not the worksheet name - // If the last entry on the stack was a : operator, then we have a cell range reference $testPrevOp = $stack->last(1); if ($testPrevOp !== null && $testPrevOp['value'] == ':') { @@ -3719,6 +3715,8 @@ class Calculation } $localeConstant = false; + $stackItemType = 'Value'; + $stackItemReference = null; if ($opCharacter == self::FORMULA_STRING_QUOTE) { // UnEscape any quotes within the string $val = self::wrapResult(str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($val))); @@ -3729,12 +3727,17 @@ class Calculation $val = (int) $val; } } elseif (isset(self::$excelConstants[trim(strtoupper($val))])) { + $stackItemType = 'Constant'; $excelConstant = trim(strtoupper($val)); $val = self::$excelConstants[$excelConstant]; } elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !== false) { + $stackItemType = 'Constant'; $val = self::$excelConstants[$localeConstant]; + } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '.*/Ui', $val, $match)) { + $stackItemType = 'Named Range'; + $stackItemReference = $val; } - $details = $stack->getStackItem('Value', $val, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); + $details = $stack->getStackItem($stackItemType, $val, $stackItemReference, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); if ($localeConstant) { $details['localeValue'] = $localeConstant; } @@ -3776,8 +3779,12 @@ class Calculation } // If we're expecting an operator, but only have a space between the previous and next operands (and both are // Cell References) then we have an INTERSECTION operator - if (($expectingOperator) && (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/Ui', substr($formula, $index), $match)) && - ($output[count($output) - 1]['type'] == 'Cell Reference')) { + if (($expectingOperator) && + ((preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/Ui', substr($formula, $index), $match)) && + ($output[count($output) - 1]['type'] == 'Cell Reference') || + (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '.*/Ui', substr($formula, $index), $match)) && + ($output[count($output) - 1]['type'] == 'Named Range' || $output[count($output) - 1]['type'] == 'Value') + )) { while ($stack->count() > 0 && ($o2 = $stack->last()) && isset(self::$operators[$o2['value']]) && @@ -3840,7 +3847,6 @@ class Calculation $fakedForBranchPruning = []; // help us to know when pruning ['branchTestId' => true/false] $branchStore = []; - // Loop through each token in turn foreach ($tokens as $tokenData) { $token = $tokenData['value']; diff --git a/src/PhpSpreadsheet/Calculation/DateTime.php b/src/PhpSpreadsheet/Calculation/DateTime.php index d08ab543..19860794 100644 --- a/src/PhpSpreadsheet/Calculation/DateTime.php +++ b/src/PhpSpreadsheet/Calculation/DateTime.php @@ -668,30 +668,19 @@ class DateTime $endMonths = $PHPEndDateObject->format('n'); $endYears = $PHPEndDateObject->format('Y'); + $PHPDiffDateObject = $PHPEndDateObject->diff($PHPStartDateObject); + switch ($unit) { case 'D': $retVal = (int) $difference; break; case 'M': - $retVal = (int) ($endMonths - $startMonths) + ((int) ($endYears - $startYears) * 12); - // We're only interested in full months - if ($endDays < $startDays) { - --$retVal; - } + $retVal = (int) 12 * $PHPDiffDateObject->format('%y') + $PHPDiffDateObject->format('%m'); break; case 'Y': - $retVal = (int) ($endYears - $startYears); - // We're only interested in full months - if ($endMonths < $startMonths) { - --$retVal; - } elseif (($endMonths == $startMonths) && ($endDays < $startDays)) { - // Remove start month - --$retVal; - // Remove end month - --$retVal; - } + $retVal = (int) $PHPDiffDateObject->format('%y'); break; case 'MD': @@ -701,19 +690,12 @@ class DateTime $adjustDays = $PHPEndDateObject->format('j'); $retVal += ($adjustDays - $startDays); } else { - $retVal = $endDays - $startDays; + $retVal = (int) $PHPDiffDateObject->format('%d'); } break; case 'YM': - $retVal = (int) ($endMonths - $startMonths); - if ($retVal < 0) { - $retVal += 12; - } - // We're only interested in full months - if ($endDays < $startDays) { - --$retVal; - } + $retVal = (int) $PHPDiffDateObject->format('%m'); break; case 'YD': diff --git a/src/PhpSpreadsheet/Cell/Coordinate.php b/src/PhpSpreadsheet/Cell/Coordinate.php index 8c679913..2afeebe9 100644 --- a/src/PhpSpreadsheet/Cell/Coordinate.php +++ b/src/PhpSpreadsheet/Cell/Coordinate.php @@ -312,32 +312,59 @@ abstract class Coordinate /** * Extract all cell references in range, which may be comprised of multiple cell ranges. * - * @param string $pRange Range (e.g. A1 or A1:C10 or A1:E10 A20:E25) + * @param string $cellRange Range: e.g. 'A1' or 'A1:C10' or 'A1:E10,A20:E25' or 'A1:E5 C3:G7' or 'A1:C1,A3:C3 B1:C3' * * @return array Array containing single cell references */ - public static function extractAllCellReferencesInRange($pRange) + public static function extractAllCellReferencesInRange($cellRange): array { - $returnValue = []; + [$ranges, $operators] = self::getCellBlocksFromRangeString($cellRange); - // Explode spaces - $cellBlocks = self::getCellBlocksFromRangeString($pRange); - foreach ($cellBlocks as $cellBlock) { - $returnValue = array_merge($returnValue, self::getReferencesForCellBlock($cellBlock)); + $cells = []; + foreach ($ranges as $range) { + $cells[] = self::getReferencesForCellBlock($range); } + $cells = self::processRangeSetOperators($operators, $cells); + + if (empty($cells)) { + return []; + } + + $cellList = array_merge(...$cells); + $cellList = self::sortCellReferenceArray($cellList); + + return $cellList; + } + + private static function processRangeSetOperators(array $operators, array $cells): array + { + for ($offset = 0; $offset < count($operators); ++$offset) { + $operator = $operators[$offset]; + if ($operator !== ' ') { + continue; + } + + $cells[$offset] = array_intersect($cells[$offset], $cells[$offset + 1]); + unset($operators[$offset], $cells[$offset + 1]); + $operators = array_values($operators); + $cells = array_values($cells); + --$offset; + } + + return $cells; + } + + private static function sortCellReferenceArray(array $cellList): array + { // Sort the result by column and row $sortKeys = []; - foreach (array_unique($returnValue) as $coord) { - $column = ''; - $row = 0; - - sscanf($coord, '%[A-Z]%d', $column, $row); + foreach ($cellList as $coord) { + [$column, $row] = sscanf($coord, '%[A-Z]%d'); $sortKeys[sprintf('%3s%09d', $column, $row)] = $coord; } ksort($sortKeys); - // Return value return array_values($sortKeys); } @@ -482,15 +509,25 @@ abstract class Coordinate } /** - * Get the individual cell blocks from a range string, splitting by space and removing any $ characters. + * Get the individual cell blocks from a range string, removing any $ characters. + * then splitting by operators and returning an array with ranges and operators. * - * @param string $pRange + * @param string $rangeString * - * @return string[] + * @return array[] */ - private static function getCellBlocksFromRangeString($pRange) + private static function getCellBlocksFromRangeString($rangeString) { - return explode(' ', str_replace('$', '', strtoupper($pRange))); + $rangeString = str_replace('$', '', strtoupper($rangeString)); + + // split range sets on intersection (space) or union (,) operators + $tokens = preg_split('/([ ,])/', $rangeString, -1, PREG_SPLIT_DELIM_CAPTURE); + // separate the range sets and the operators into arrays + $split = array_chunk($tokens, 2); + $ranges = array_column($split, 0); + $operators = array_column($split, 1); + + return [$ranges, $operators]; } /** diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 797e59ea..fb7f0645 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -763,13 +763,8 @@ class Xlsx extends BaseReader } } - if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) { - $docSheet->getProtection()->setPassword((string) $xmlSheet->sheetProtection['password'], true); - if ($xmlSheet->protectedRanges->protectedRange) { - foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) { - $docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true); - } - } + if ($xmlSheet) { + $this->readSheetProtection($docSheet, $xmlSheet); } if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) { @@ -2031,4 +2026,29 @@ class Xlsx extends BaseReader return $workbookBasename; } + + private function readSheetProtection(Worksheet $docSheet, SimpleXMLElement $xmlSheet): void + { + if ($this->readDataOnly || !$xmlSheet->sheetProtection) { + return; + } + + $algorithmName = (string) $xmlSheet->sheetProtection['algorithmName']; + $protection = $docSheet->getProtection(); + $protection->setAlgorithm($algorithmName); + + if ($algorithmName) { + $protection->setPassword((string) $xmlSheet->sheetProtection['hashValue'], true); + $protection->setSalt((string) $xmlSheet->sheetProtection['saltValue']); + $protection->setSpinCount((int) $xmlSheet->sheetProtection['spinCount']); + } else { + $protection->setPassword((string) $xmlSheet->sheetProtection['password'], true); + } + + if ($xmlSheet->protectedRanges->protectedRange) { + foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) { + $docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true); + } + } + } } diff --git a/src/PhpSpreadsheet/Shared/PasswordHasher.php b/src/PhpSpreadsheet/Shared/PasswordHasher.php index 9b0080b9..9fefe88f 100644 --- a/src/PhpSpreadsheet/Shared/PasswordHasher.php +++ b/src/PhpSpreadsheet/Shared/PasswordHasher.php @@ -2,8 +2,41 @@ namespace PhpOffice\PhpSpreadsheet\Shared; +use PhpOffice\PhpSpreadsheet\Exception; +use PhpOffice\PhpSpreadsheet\Worksheet\Protection; + class PasswordHasher { + /** + * Get algorithm name for PHP. + */ + private static function getAlgorithm(string $algorithmName): string + { + if (!$algorithmName) { + return ''; + } + + // Mapping between algorithm name in Excel and algorithm name in PHP + $mapping = [ + Protection::ALGORITHM_MD2 => 'md2', + Protection::ALGORITHM_MD4 => 'md4', + Protection::ALGORITHM_MD5 => 'md5', + Protection::ALGORITHM_SHA_1 => 'sha1', + Protection::ALGORITHM_SHA_256 => 'sha256', + Protection::ALGORITHM_SHA_384 => 'sha384', + Protection::ALGORITHM_SHA_512 => 'sha512', + Protection::ALGORITHM_RIPEMD_128 => 'ripemd128', + Protection::ALGORITHM_RIPEMD_160 => 'ripemd160', + Protection::ALGORITHM_WHIRLPOOL => 'whirlpool', + ]; + + if (array_key_exists($algorithmName, $mapping)) { + return $mapping[$algorithmName]; + } + + throw new Exception('Unsupported password algorithm: ' . $algorithmName); + } + /** * Create a password hash from a given string. * @@ -12,10 +45,8 @@ class PasswordHasher * Spreadsheet_Excel_Writer by Xavier Noguer . * * @param string $pPassword Password to hash - * - * @return string Hashed password */ - public static function hashPassword($pPassword) + private static function defaultHashPassword(string $pPassword): string { $password = 0x0000; $charPos = 1; // char position @@ -34,4 +65,36 @@ class PasswordHasher return strtoupper(dechex($password)); } + + /** + * Create a password hash from a given string by a specific algorithm. + * + * 2.4.2.4 ISO Write Protection Method + * + * @see https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/1357ea58-646e-4483-92ef-95d718079d6f + * + * @param string $password Password to hash + * @param string $algorithm Hash algorithm used to compute the password hash value + * @param string $salt Pseudorandom string + * @param int $spinCount Number of times to iterate on a hash of a password + * + * @return string Hashed password + */ + public static function hashPassword(string $password, string $algorithm = '', string $salt = '', int $spinCount = 10000): string + { + $phpAlgorithm = self::getAlgorithm($algorithm); + if (!$phpAlgorithm) { + return self::defaultHashPassword($password); + } + + $saltValue = base64_decode($salt); + $encodedPassword = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8'); + + $hashValue = hash($phpAlgorithm, $saltValue . $encodedPassword, true); + for ($i = 0; $i < $spinCount; ++$i) { + $hashValue = hash($phpAlgorithm, $hashValue . pack('L', $i), true); + } + + return base64_encode($hashValue); + } } diff --git a/src/PhpSpreadsheet/Worksheet/Protection.php b/src/PhpSpreadsheet/Worksheet/Protection.php index 2fd3e919..81abc0b7 100644 --- a/src/PhpSpreadsheet/Worksheet/Protection.php +++ b/src/PhpSpreadsheet/Worksheet/Protection.php @@ -6,6 +6,17 @@ 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. * @@ -119,12 +130,40 @@ class Protection private $selectUnlockedCells = false; /** - * Password. + * Hashed password. * * @var string */ private $password = ''; + /** + * Algorithm name. + * + * @var string + */ + private $algorithm = ''; + + /** + * Hash value. + * + * @var string + */ + private $hash = ''; + + /** + * Salt value. + * + * @var string + */ + private $salt = ''; + + /** + * Spin count. + * + * @var int + */ + private $spinCount = 10000; + /** * Create a new Protection. */ @@ -542,7 +581,7 @@ class Protection } /** - * Get Password (hashed). + * Get hashed password. * * @return string */ @@ -562,13 +601,86 @@ class Protection public function setPassword($pValue, $pAlreadyHashed = false) { if (!$pAlreadyHashed) { - $pValue = PasswordHasher::hashPassword($pValue); + $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. */ diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 1ed77e05..e2b0dd87 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -187,7 +187,7 @@ class Worksheet implements IComparable /** * Collection of merged cell ranges. * - * @var array + * @var string[] */ private $mergeCells = []; @@ -1747,7 +1747,7 @@ class Worksheet implements IComparable /** * Get merge cells array. * - * @return array[] + * @return string[] */ public function getMergeCells() { @@ -1758,6 +1758,8 @@ class Worksheet implements IComparable * Set merge cells array for the entire sheet. Use instead mergeCells() to merge * a single cell range. * + * @param string[] $pValue + * * @return $this */ public function setMergeCells(array $pValue) diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 3d47eeaa..d101bb40 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -420,26 +420,33 @@ class Worksheet extends WriterPart // sheetProtection $objWriter->startElement('sheetProtection'); - if ($pSheet->getProtection()->getPassword() !== '') { - $objWriter->writeAttribute('password', $pSheet->getProtection()->getPassword()); + $protection = $pSheet->getProtection(); + + if ($protection->getAlgorithm()) { + $objWriter->writeAttribute('algorithmName', $protection->getAlgorithm()); + $objWriter->writeAttribute('hashValue', $protection->getPassword()); + $objWriter->writeAttribute('saltValue', $protection->getSalt()); + $objWriter->writeAttribute('spinCount', $protection->getSpinCount()); + } elseif ($protection->getPassword() !== '') { + $objWriter->writeAttribute('password', $protection->getPassword()); } - $objWriter->writeAttribute('sheet', ($pSheet->getProtection()->getSheet() ? 'true' : 'false')); - $objWriter->writeAttribute('objects', ($pSheet->getProtection()->getObjects() ? 'true' : 'false')); - $objWriter->writeAttribute('scenarios', ($pSheet->getProtection()->getScenarios() ? 'true' : 'false')); - $objWriter->writeAttribute('formatCells', ($pSheet->getProtection()->getFormatCells() ? 'true' : 'false')); - $objWriter->writeAttribute('formatColumns', ($pSheet->getProtection()->getFormatColumns() ? 'true' : 'false')); - $objWriter->writeAttribute('formatRows', ($pSheet->getProtection()->getFormatRows() ? 'true' : 'false')); - $objWriter->writeAttribute('insertColumns', ($pSheet->getProtection()->getInsertColumns() ? 'true' : 'false')); - $objWriter->writeAttribute('insertRows', ($pSheet->getProtection()->getInsertRows() ? 'true' : 'false')); - $objWriter->writeAttribute('insertHyperlinks', ($pSheet->getProtection()->getInsertHyperlinks() ? 'true' : 'false')); - $objWriter->writeAttribute('deleteColumns', ($pSheet->getProtection()->getDeleteColumns() ? 'true' : 'false')); - $objWriter->writeAttribute('deleteRows', ($pSheet->getProtection()->getDeleteRows() ? 'true' : 'false')); - $objWriter->writeAttribute('selectLockedCells', ($pSheet->getProtection()->getSelectLockedCells() ? 'true' : 'false')); - $objWriter->writeAttribute('sort', ($pSheet->getProtection()->getSort() ? 'true' : 'false')); - $objWriter->writeAttribute('autoFilter', ($pSheet->getProtection()->getAutoFilter() ? 'true' : 'false')); - $objWriter->writeAttribute('pivotTables', ($pSheet->getProtection()->getPivotTables() ? 'true' : 'false')); - $objWriter->writeAttribute('selectUnlockedCells', ($pSheet->getProtection()->getSelectUnlockedCells() ? 'true' : 'false')); + $objWriter->writeAttribute('sheet', ($protection->getSheet() ? 'true' : 'false')); + $objWriter->writeAttribute('objects', ($protection->getObjects() ? 'true' : 'false')); + $objWriter->writeAttribute('scenarios', ($protection->getScenarios() ? 'true' : 'false')); + $objWriter->writeAttribute('formatCells', ($protection->getFormatCells() ? 'true' : 'false')); + $objWriter->writeAttribute('formatColumns', ($protection->getFormatColumns() ? 'true' : 'false')); + $objWriter->writeAttribute('formatRows', ($protection->getFormatRows() ? 'true' : 'false')); + $objWriter->writeAttribute('insertColumns', ($protection->getInsertColumns() ? 'true' : 'false')); + $objWriter->writeAttribute('insertRows', ($protection->getInsertRows() ? 'true' : 'false')); + $objWriter->writeAttribute('insertHyperlinks', ($protection->getInsertHyperlinks() ? 'true' : 'false')); + $objWriter->writeAttribute('deleteColumns', ($protection->getDeleteColumns() ? 'true' : 'false')); + $objWriter->writeAttribute('deleteRows', ($protection->getDeleteRows() ? 'true' : 'false')); + $objWriter->writeAttribute('selectLockedCells', ($protection->getSelectLockedCells() ? 'true' : 'false')); + $objWriter->writeAttribute('sort', ($protection->getSort() ? 'true' : 'false')); + $objWriter->writeAttribute('autoFilter', ($protection->getAutoFilter() ? 'true' : 'false')); + $objWriter->writeAttribute('pivotTables', ($protection->getPivotTables() ? 'true' : 'false')); + $objWriter->writeAttribute('selectUnlockedCells', ($protection->getSelectUnlockedCells() ? 'true' : 'false')); $objWriter->endElement(); } @@ -1133,7 +1140,7 @@ class Worksheet extends WriterPart $this->getParentWriter()->getOffice2003Compatibility() === false, 'v', ($this->getParentWriter()->getPreCalculateFormulas() && !is_array($calculatedValue) && substr($calculatedValue, 0, 1) !== '#') - ? StringHelper::formatNumber($calculatedValue) : '0' + ? StringHelper::formatNumber($calculatedValue) : '0' ); } diff --git a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php index 84cac747..ee566db2 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Engine; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\NamedRange; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; @@ -28,7 +29,7 @@ class RangeTest extends TestCase /** * @dataProvider providerRangeEvaluation * - * @param mixed $formula + * @param string $formula * @param int $expectedResult */ public function testRangeEvaluation($formula, $expectedResult): void @@ -44,11 +45,93 @@ class RangeTest extends TestCase { return[ ['=SUM(A1:B3,A1:C2)', 48], + ['=COUNT(A1:B3,A1:C2)', 12], ['=SUM(A1:B3 A1:C2)', 12], + ['=COUNT(A1:B3 A1:C2)', 4], ['=SUM(A1:A3,C1:C3)', 30], + ['=COUNT(A1:A3,C1:C3)', 6], ['=SUM(A1:A3 C1:C3)', Functions::null()], + ['=COUNT(A1:A3 C1:C3)', 0], ['=SUM(A1:B2,B2:C3)', 40], + ['=COUNT(A1:B2,B2:C3)', 8], ['=SUM(A1:B2 B2:C3)', 5], + ['=COUNT(A1:B2 B2:C3)', 1], + ['=SUM(A1:C1,A3:C3,B1:C3)', 63], + ['=COUNT(A1:C1,A3:C3,B1:C3)', 12], + ['=SUM(A1:C1,A3:C3 B1:C3)', 23], + ['=COUNT(A1:C1,A3:C3 B1:C3)', 5], + ]; + } + + /** + * @dataProvider providerNamedRangeEvaluation + * + * @param string $group1 + * @param string $group2 + * @param string $formula + * @param int $expectedResult + */ + public function testNamedRangeEvaluation($group1, $group2, $formula, $expectedResult): void + { + $workSheet = $this->spreadSheet->getActiveSheet(); + $this->spreadSheet->addNamedRange(new NamedRange('GROUP1', $workSheet, $group1)); + $this->spreadSheet->addNamedRange(new NamedRange('GROUP2', $workSheet, $group2)); + + $workSheet->setCellValue('E1', $formula); + + $sumRresult = $workSheet->getCell('E1')->getCalculatedValue(); + self::assertSame($expectedResult, $sumRresult); + } + + public function providerNamedRangeEvaluation() + { + return[ + ['A1:B3', 'A1:C2', '=SUM(GROUP1,GROUP2)', 48], + ['A1:B3', 'A1:C2', '=COUNT(GROUP1,GROUP2)', 12], + ['A1:B3', 'A1:C2', '=SUM(GROUP1 GROUP2)', 12], + ['A1:B3', 'A1:C2', '=COUNT(GROUP1 GROUP2)', 4], + ['A1:B2', 'B2:C3', '=SUM(GROUP1,GROUP2)', 40], + ['A1:B2', 'B2:C3', '=COUNT(GROUP1,GROUP2)', 8], + ['A1:B2', 'B2:C3', '=SUM(GROUP1 GROUP2)', 5], + ['A1:B2', 'B2:C3', '=COUNT(GROUP1 GROUP2)', 1], + ]; + } + + /** + * @dataProvider providerCompositeNamedRangeEvaluation + * + * @param string $composite + * @param int $expectedSum + * @param int $expectedCount + */ + public function testCompositeNamedRangeEvaluation($composite, $expectedSum, $expectedCount): void + { + $workSheet = $this->spreadSheet->getActiveSheet(); + $this->spreadSheet->addNamedRange(new NamedRange('COMPOSITE', $workSheet, $composite)); + + $workSheet->setCellValue('E1', '=SUM(COMPOSITE)'); + $workSheet->setCellValue('E2', '=COUNT(COMPOSITE)'); + + $actualSum = $workSheet->getCell('E1')->getCalculatedValue(); + self::assertSame($expectedSum, $actualSum); + $actualCount = $workSheet->getCell('E2')->getCalculatedValue(); + self::assertSame($expectedCount, $actualCount); + } + + public function providerCompositeNamedRangeEvaluation() + { + return[ + // Calculation engine doesn't yet handle union ranges with overlap + // 'Union with overlap' => [ + // 'A1:C1,A3:C3,B1:C3', + // 63, + // 12, + // ], + 'Intersection' => [ + 'A1:C1,A3:C3 B1:C3', + 23, + 5, + ], ]; } } diff --git a/tests/PhpSpreadsheetTests/Cell/CoordinateTest.php b/tests/PhpSpreadsheetTests/Cell/CoordinateTest.php index 37579e80..8e0e98a9 100644 --- a/tests/PhpSpreadsheetTests/Cell/CoordinateTest.php +++ b/tests/PhpSpreadsheetTests/Cell/CoordinateTest.php @@ -83,10 +83,11 @@ class CoordinateTest extends TestCase * @dataProvider providerCoordinates * * @param mixed $expectedResult + * @param string $rangeSet */ - public function testCoordinateFromString($expectedResult, ...$args): void + public function testCoordinateFromString($expectedResult, $rangeSet): void { - $result = Coordinate::coordinateFromString(...$args); + $result = Coordinate::coordinateFromString($rangeSet); self::assertEquals($expectedResult, $result); } @@ -143,11 +144,12 @@ class CoordinateTest extends TestCase /** * @dataProvider providerAbsoluteCoordinates * - * @param mixed $expectedResult + * @param string $expectedResult + * @param string $rangeSet */ - public function testAbsoluteCoordinateFromString($expectedResult, ...$args): void + public function testAbsoluteCoordinateFromString($expectedResult, $rangeSet): void { - $result = Coordinate::absoluteCoordinate(...$args); + $result = Coordinate::absoluteCoordinate($rangeSet); self::assertEquals($expectedResult, $result); } @@ -175,10 +177,11 @@ class CoordinateTest extends TestCase * @dataProvider providerAbsoluteReferences * * @param mixed $expectedResult + * @param string $rangeSet */ - public function testAbsoluteReferenceFromString($expectedResult, ...$args): void + public function testAbsoluteReferenceFromString($expectedResult, $rangeSet): void { - $result = Coordinate::absoluteReference(...$args); + $result = Coordinate::absoluteReference($rangeSet); self::assertEquals($expectedResult, $result); } @@ -206,10 +209,11 @@ class CoordinateTest extends TestCase * @dataProvider providerSplitRange * * @param mixed $expectedResult + * @param string $rangeSet */ - public function testSplitRange($expectedResult, ...$args): void + public function testSplitRange($expectedResult, $rangeSet): void { - $result = Coordinate::splitRange(...$args); + $result = Coordinate::splitRange($rangeSet); foreach ($result as $key => $split) { if (!is_array($expectedResult[$key])) { self::assertEquals($expectedResult[$key], $split[0]); @@ -252,10 +256,11 @@ class CoordinateTest extends TestCase * @dataProvider providerRangeBoundaries * * @param mixed $expectedResult + * @param string $rangeSet */ - public function testRangeBoundaries($expectedResult, ...$args): void + public function testRangeBoundaries($expectedResult, $rangeSet): void { - $result = Coordinate::rangeBoundaries(...$args); + $result = Coordinate::rangeBoundaries($rangeSet); self::assertEquals($expectedResult, $result); } @@ -268,10 +273,11 @@ class CoordinateTest extends TestCase * @dataProvider providerRangeDimension * * @param mixed $expectedResult + * @param string $rangeSet */ - public function testRangeDimension($expectedResult, ...$args): void + public function testRangeDimension($expectedResult, $rangeSet): void { - $result = Coordinate::rangeDimension(...$args); + $result = Coordinate::rangeDimension($rangeSet); self::assertEquals($expectedResult, $result); } @@ -284,10 +290,11 @@ class CoordinateTest extends TestCase * @dataProvider providerGetRangeBoundaries * * @param mixed $expectedResult + * @param string $rangeSet */ - public function testGetRangeBoundaries($expectedResult, ...$args): void + public function testGetRangeBoundaries($expectedResult, $rangeSet): void { - $result = Coordinate::getRangeBoundaries(...$args); + $result = Coordinate::getRangeBoundaries($rangeSet); self::assertEquals($expectedResult, $result); } @@ -299,11 +306,12 @@ class CoordinateTest extends TestCase /** * @dataProvider providerExtractAllCellReferencesInRange * - * @param mixed $expectedResult + * @param array $expectedResult + * @param string $rangeSet */ - public function testExtractAllCellReferencesInRange($expectedResult, ...$args): void + public function testExtractAllCellReferencesInRange($expectedResult, $rangeSet): void { - $result = Coordinate::extractAllCellReferencesInRange(...$args); + $result = Coordinate::extractAllCellReferencesInRange($rangeSet); self::assertEquals($expectedResult, $result); } @@ -350,10 +358,11 @@ class CoordinateTest extends TestCase * @dataProvider providerCoordinateIsRange * * @param mixed $expectedResult + * @param string $rangeSet */ - public function testCoordinateIsRange($expectedResult, ...$args): void + public function testCoordinateIsRange($expectedResult, $rangeSet): void { - $result = Coordinate::coordinateIsRange(...$args); + $result = Coordinate::coordinateIsRange($rangeSet); self::assertEquals($expectedResult, $result); } diff --git a/tests/PhpSpreadsheetTests/Worksheet/ProtectionTest.php b/tests/PhpSpreadsheetTests/Worksheet/ProtectionTest.php new file mode 100644 index 00000000..1cc1ed32 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Worksheet/ProtectionTest.php @@ -0,0 +1,39 @@ +verify('foo'), 'non-protected always pass'); + + $protection->setSheet(true); + self::assertFalse($protection->verify('foo'), 'protected will fail'); + + $protection->setPassword('foo', true); + self::assertSame('foo', $protection->getPassword(), 'was not stored as-is, without hashing'); + self::assertFalse($protection->verify('foo'), 'setting already hashed password will not match'); + + $protection->setPassword('foo'); + self::assertSame('CC40', $protection->getPassword(), 'was hashed'); + self::assertTrue($protection->verify('foo'), 'setting non-hashed password will hash it and not match'); + + $protection->setAlgorithm(Protection::ALGORITHM_MD5); + self::assertFalse($protection->verify('foo'), 'changing algorithm will not match anymore'); + + $protection->setPassword('foo'); + $hash1 = $protection->getPassword(); + $protection->setPassword('foo'); + $hash2 = $protection->getPassword(); + + self::assertSame(24, mb_strlen($hash1)); + self::assertSame(24, mb_strlen($hash2)); + self::assertNotSame($hash1, $hash2, 'was hashed with automatic salt'); + self::assertTrue($protection->verify('foo'), 'setting password again, will hash with proper algorithm and will match'); + } +} diff --git a/tests/data/Calculation/DateTime/DATEDIF.php b/tests/data/Calculation/DateTime/DATEDIF.php index d113d3aa..a6d2d761 100644 --- a/tests/data/Calculation/DateTime/DATEDIF.php +++ b/tests/data/Calculation/DateTime/DATEDIF.php @@ -393,6 +393,10 @@ return [ 1, '19-12-1960', '26-01-2012', 'YM', ], + [ + 11, + '19-12-1960', '26-11-1962', 'YM', + ], [ 38, '19-12-1960', '26-01-2012', 'YD', @@ -402,7 +406,15 @@ return [ '19-12-1960', '26-01-2012', 'MD', ], [ - 50, + 0, + '19-12-1960', '12-12-1961', 'Y', + ], + [ + 1, + '19-12-1960', '12-12-1962', 'Y', + ], + [ + 51, '19-12-1960', '12-12-2012', 'Y', ], [ diff --git a/tests/data/CellExtractAllCellReferencesInRange.php b/tests/data/CellExtractAllCellReferencesInRange.php index cf093289..b005b1fe 100644 --- a/tests/data/CellExtractAllCellReferencesInRange.php +++ b/tests/data/CellExtractAllCellReferencesInRange.php @@ -22,12 +22,6 @@ return [ ], [ [ - 'B4', - 'B5', - 'B6', - 'D4', - 'D5', - 'D6', ], 'B4:B6 D4:D6', ], @@ -66,20 +60,10 @@ return [ ], [ [ - 'B4', - 'B5', - 'B6', - 'C4', 'C5', 'C6', - 'C7', - 'D4', 'D5', 'D6', - 'D7', - 'E5', - 'E6', - 'E7', ], 'B4:D6 C5:E7', ], @@ -105,7 +89,7 @@ return [ 'F5', 'F6', ], - 'B2:D4 C5:D5 E3:E5 D6:E6 F4:F6', + 'B2:D4,C5:D5,E3:E5,D6:E6,F4:F6', ], [ [ @@ -129,16 +113,13 @@ return [ 'F5', 'F6', ], - 'B2:D4 C3:E5 D4:F6', + 'B2:D4,C3:E5,D4:F6', ], [ [ - 'B4', 'B5', - 'B6', - 'B8', ], - 'B4:B6 B8', + 'B4:B6 B5', ], [ [ diff --git a/tests/data/Shared/PasswordHashes.php b/tests/data/Shared/PasswordHashes.php index b4f348ca..34c25cef 100644 --- a/tests/data/Shared/PasswordHashes.php +++ b/tests/data/Shared/PasswordHashes.php @@ -25,4 +25,30 @@ return [ 'CE4B', '', ], + [ + 'O6EXRLpLEDNJDL/AzYtnnA4O4bY=', + '', + 'SHA-1', + ], + [ + 'GYvlIMljDI1Czc4jfWrGaxU5pxl9n5Og0KUzyAfYxwk=', + 'PhpSpreadsheet', + 'SHA-256', + 'Php_salt', + 1000, + ], + [ + 'sSHdxQv9qgpkr4LDT0bYQxM9hOQJFRhJ4D752/NHQtDDR1EVy67NCEW9cPd6oWvCoBGd96MqKpuma1A7pN1nEA==', + 'Mark Baker', + 'SHA-512', + 'Mark_salt', + 10000, + ], + [ + 'r9KVLLCKIYOILvE2rcby+g==', + '!+&=()~§±æþ', + 'MD5', + 'Symbols_salt', + 100000, + ], ];