├── .gitignore ├── LICENSE ├── README.md ├── README_ru.md ├── composer.json ├── readme_resources ├── demo.png ├── demo_ru.png ├── exported_file.png ├── special_template.png └── template.png ├── samples ├── 10_images │ ├── exported_file.xlsx │ ├── images │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── 5.png │ ├── index.php │ ├── run.cmd │ └── template.xlsx ├── 1_simple │ ├── exported_file.xlsx │ ├── index.php │ ├── run.cmd │ └── template.xlsx ├── 2_simple │ ├── exported_file.xlsx │ ├── index.php │ ├── run.cmd │ └── template.xlsx ├── 3_basic │ ├── exported_file.xlsx │ ├── index.php │ ├── run.cmd │ └── template.xlsx ├── 4_side-effects │ ├── exported_file.xlsx │ ├── index.php │ ├── run.cmd │ └── template.xlsx ├── 5_styles │ ├── exported_file.xlsx │ ├── index.php │ ├── run.cmd │ └── template.xlsx ├── 6_sheets │ ├── exported_file.xlsx │ ├── index.php │ ├── params.php │ ├── run.cmd │ └── template.xlsx ├── 7_styles_without_setters │ ├── exported_file.xlsx │ ├── index.php │ ├── run.cmd │ └── template.xlsx ├── 8_special_template │ ├── exported_file.xlsx │ ├── index.php │ ├── run.cmd │ └── template.xlsx ├── 9_ods_files │ ├── exported_file.ods │ ├── index.php │ ├── run.cmd │ └── template.ods └── Bootstrap.php └── src ├── InsertedCells.php ├── PhpExcelTemplator.php ├── PhpExcelTemplatorOds.php ├── PhpExcelTemplatorPdf.php ├── PhpExcelTemplatorXls.php ├── ReferenceHelper.php ├── params ├── CallbackParam.php ├── ExcelParam.php └── SetterParam.php └── setters ├── CellSetterArray2DValue.php ├── CellSetterArrayValue.php ├── CellSetterArrayValueSpecial.php ├── CellSetterStringValue.php └── ICellSetter.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | composer.lock 3 | vendor 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Николай Сидорович 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Excel Templator 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/alhimik1986/php-excel-templator/v/stable)](https://packagist.org/packages/alhimik1986/php-excel-templator) 4 | [![Latest Unstable Version](https://poser.pugx.org/alhimik1986/php-excel-templator/v/unstable)](https://packagist.org/packages/alhimik1986/php-excel-templator) 5 | [![License](https://poser.pugx.org/alhimik1986/php-excel-templator/license)](https://packagist.org/packages/alhimik1986/php-excel-templator) 6 | [![Total Downloads](https://poser.pugx.org/alhimik1986/php-excel-templator/downloads)](https://packagist.org/packages/alhimik1986/php-excel-templator) 7 | [![Monthly Downloads](https://poser.pugx.org/alhimik1986/php-excel-templator/d/monthly)](https://packagist.org/packages/alhimik1986/php-excel-templator) 8 | [![Daily Downloads](https://poser.pugx.org/alhimik1986/php-excel-templator/d/daily)](https://packagist.org/packages/alhimik1986/php-excel-templator) 9 | 10 | [Инструкция на русском языке (Russian)](README_ru.md) 11 | 12 | It's PHP Spreadsheet extension that allows you to export excel files from an excel template. 13 | Using the extension you don’t need to create excel files from scratch using code, set styles and so on. 14 | 15 | Demo screenshot: 16 | 17 | ![Demo](readme_resources/demo.png) 18 | 19 | 20 | ## Simple example 21 | There is a simplest example of how this might look (using less code). 22 | Suppose we have an excel file with the following template variables: 23 | 24 | ![Template](readme_resources/template.png) 25 | 26 | The code will be as follows: 27 | ``` 28 | use alhimik1986\PhpExcelTemplator\PhpExcelTemplator; 29 | require_once('vendor/autoload.php'); // if you don't use framework 30 | 31 | PhpExcelTemplator::saveToFile('./template.xlsx', './exported_file.xlsx', [ 32 | '{current_date}' => date('d-m-Y'), 33 | '{department}' => 'Sales department', 34 | ]); 35 | ``` 36 | As a result, we get: 37 | 38 | ![Exported file](readme_resources/exported_file.png) 39 | 40 | Using this extension, we just create a template file with the styles we need and specify template variables in it. In the code, we just pass the parameters to template variables. 41 | 42 | ## Features 43 | - We can insert several template variables in one table cell (if the data type is "string") 44 | - We can insert a one-dimensional array, in this case additional rows will be created in the table 45 | - We can insert a two-dimensional array, in this case the respective columns and rows are created in the table 46 | - By specifying the value in the cells, you can change the styles of these cells, even when inserting arrays 47 | - We can apply the same template on several sheets of the table 48 | 49 | Features demo and usage examples are given in the folder "samples". 50 | 51 | ## Restrictions: 52 | - Possible so-called side effects when using one-dimensional or two-dimensional arrays in one sheet. Especially when it is located asymmetrically. An example of side effects is also given in the folder "samples". 53 | 54 | ## INSTALLATION: 55 | 56 | ``` 57 | $ composer require alhimik1986/php-excel-templator 58 | ``` 59 | 60 | ### Template variable naming rules 61 | The rules can be any, but I can offer my recommendation for naming template variables: 62 | - {var_name} - for string values 63 | - [var_name] - for one-dimensional arrays 64 | - [[var_name]] - for two-dimensional arrays 65 | 66 | 67 | ### How to insert a one-dimensional array, so that the table create columns, not rows? 68 | To do this, instead of a one-dimensional array, insert a two-dimensional one as follows: 69 | ``` 70 | $param['[[var_name]]'] = [['text 1', 'text 2', 'text 3']]; 71 | ``` 72 | 73 | ## Using setters 74 | In the example above, the minimum code without setters was used. 75 | The data types (for example: a string, a one-dimensional array, or a two-dimensional array) in this code is automatically recognized and the necessary setter is chose. 76 | But if we want to use a specific setter, the same code will look like this: 77 | ``` 78 | use alhimik1986\PhpExcelTemplator\PhpExcelTemplator; 79 | use alhimik1986\PhpExcelTemplator\params\ExcelParam; 80 | use alhimik1986\PhpExcelTemplator\params\CallbackParam; 81 | use alhimik1986\PhpExcelTemplator\setters\CellSetterStringValue; 82 | 83 | require_once('vendor/autoload.php'); // if you don't use framework 84 | 85 | $params = [ 86 | '{current_date}' => new ExcelParam(CellSetterStringValue::class, date('d-m-Y')), 87 | '{department}' => new ExcelParam(CellSetterStringValue::class, 'Sales department'), 88 | ]; 89 | PhpExcelTemplator::saveToFile('./template.xlsx', './exported_file.xlsx', $params); 90 | ``` 91 | At the moment the extension has 3 kinds of setters: 92 | - CellSetterStringValue (for string values) 93 | - CellSetterArrayValue (for one-dimensional arrays) 94 | - CellSetterArray2DValue (for two-dimensional arrays) 95 | 96 | You ask, what for specify setters explicitly? 97 | - First, because it's flexible: let's say you want to create your own setter with your own algorithms that eliminate the side effects, which I mentioned above. 98 | - Secondly, in each setter, you can pass a callback function in which we can change the styles of the inserted cells. For example, you need to highlight with bold font the employees who made the best sales in this month. 99 | 100 | Examples of code that uses all kinds of setters are listed in the folder "samples". 101 | 102 | ## How to set styles without setters? 103 | In most cases to use the setters explicitly is not so convenient. I suppose you want to use minimum code. Therefore, I made it possible to set styles without using setters: 104 | ``` 105 | use alhimik1986\PhpExcelTemplator\PhpExcelTemplator; 106 | use alhimik1986\PhpExcelTemplator\params\CallbackParam; 107 | require_once('vendor/autoload.php'); // if you don't use framework 108 | 109 | $params = [ 110 | '{current_date}' => date('d-m-Y'), 111 | '{department}' => 'Sales department', 112 | '[sales_amount]' => [ 113 | '10230', 114 | '45100', 115 | '70500', 116 | ], 117 | ]; 118 | 119 | $callbacks = [ 120 | '[sales_amount]' => function(CallbackParam $param) { 121 | $amount = $param->param[$param->row_index]; 122 | if ($amount > 50000) { 123 | $cell_coordinate = $param->coordinate; 124 | $param->sheet->getStyle($cell_coordinate)->getFont()->setBold(true); 125 | } 126 | }, 127 | ]; 128 | 129 | PhpExcelTemplator::saveToFile('./template.xlsx', './exported_file.xlsx', $params, $callbacks); 130 | ``` 131 | 132 | ## Special setter for special templates (CellSetterArrayValueSpecial) 133 | 134 | There are special templates, which require to insert the whole row, and not insert cell with shifting down. Moreover, it's required to merge cells, as well as the cell in which there was a template variable. 135 | 136 | ![Special template](readme_resources/special_template.png) 137 | 138 | 139 | For these templates, a special setter has been created: CellSetterArrayValueSpecial. Examples of code that uses it is given in folder: samples/8_special_template. 140 | 141 | 142 | ## Events 143 | 144 | The following are possible events and an explanation of why they can be applied: 145 | ```php 146 | $events = [ 147 | PhpExcelTemplator::BEFORE_INSERT_PARAMS => function(Worksheet $sheet, array $templateVarsArr) { 148 | // fires before inserting values into template variables 149 | }, 150 | PhpExcelTemplator::AFTER_INSERT_PARAMS => function(Worksheet $sheet, array $templateVarsArr) { 151 | // fires after inserting values into template variables. 152 | // It is used if you want to insert values​into a spreadsheet after columns and rows have been created. 153 | // For example, when inserting an array of images. 154 | // If you insert images using $callbacks, then the images can shift to the right due to the fact that on the next line the template variable can create additional columns. 155 | // See an example: samples/10_images 156 | }, 157 | PhpExcelTemplator::BEFORE_SAVE => function(Spreadsheet $spreadsheet, IWriter $writer) { 158 | // fires before saving to a file. It is used when you need to modify the $writer or $spreadsheet object before saving, for example, $writer->setPreCalculateFormulas(false); 159 | }, 160 | ]; 161 | $callbacks = []; 162 | $templateFile = './template.xlsx'; 163 | $fileName = './exported_file.xlsx'; 164 | $params = [ 165 | // ... 166 | ]; 167 | 168 | PhpExcelTemplator::saveToFile($templateFile, $fileName, $params, $callbacks, $events); 169 | ``` 170 | -------------------------------------------------------------------------------- /README_ru.md: -------------------------------------------------------------------------------- 1 | # PHP Excel Templator 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/alhimik1986/php-excel-templator/v/stable)](https://packagist.org/packages/alhimik1986/php-excel-templator) 4 | [![Latest Unstable Version](https://poser.pugx.org/alhimik1986/php-excel-templator/v/unstable)](https://packagist.org/packages/alhimik1986/php-excel-templator) 5 | [![License](https://poser.pugx.org/alhimik1986/php-excel-templator/license)](https://packagist.org/packages/alhimik1986/php-excel-templator) 6 | [![Total Downloads](https://poser.pugx.org/alhimik1986/php-excel-templator/downloads)](https://packagist.org/packages/alhimik1986/php-excel-templator) 7 | [![Monthly Downloads](https://poser.pugx.org/alhimik1986/php-excel-templator/d/monthly)](https://packagist.org/packages/alhimik1986/php-excel-templator) 8 | [![Daily Downloads](https://poser.pugx.org/alhimik1986/php-excel-templator/d/daily)](https://packagist.org/packages/alhimik1986/php-excel-templator) 9 | 10 | PHP Excel модуль, позволяющий экспортировать excel-файлы из excel-шаблона. 11 | Теперь не нужно при помощи кода с нуля создавать excel-файлы, прописывать в нём стили и т. д. 12 | 13 | Демонстрация: 14 | 15 | ![Демонстрация](readme_resources/demo_ru.png) 16 | 17 | ## Простой пример 18 | Самый простой пример, как это может выглядеть (с минимальным количеством кода): 19 | Допустим, у нас есть excel-файл со следующими шаблонными переменными: 20 | 21 | ![Шаблон](readme_resources/template.png) 22 | 23 | Код будет выглядеть следующим образом: 24 | ``` 25 | use alhimik1986\PhpExcelTemplator\PhpExcelTemplator; 26 | require_once('vendor/autoload.php'); // если используется чистый код без фреймворка 27 | 28 | PhpExcelTemplator::saveToFile('./template.xlsx', './exported_file.xlsx', [ 29 | '{current_date}' => date('d-m-Y'), 30 | '{department}' => 'Sales department', 31 | ]); 32 | ``` 33 | В результате мы получим: 34 | 35 | ![Результат](readme_resources/exported_file.png) 36 | 37 | С помощью этого модуля мы просто создаём файл шаблона с нужными нам стилями, указываем в нём шаблонные переменные. А в коде мы лишь передаём параметры, в которых указываем какое значение поместить в указанную шаблонную переменную. 38 | 39 | ## Характеристики 40 | - Возможность вставить несколько шаблонных переменных в одной ячейке таблицы (если тип данных "строка") 41 | - Возможность вставить одномерный массив, в этом случае в таблице будут создаваться дополнительные строки 42 | - Возможность вставлять двумерный массив, в этом случае в таблице создаются соответствующие колонки и строки 43 | - Всталяя значение в ячейки можно менять и стили этих ячеек, даже при вставке массивов 44 | - Возможность применять один и тот же шаблон на нескольких листах таблицы 45 | 46 | Демонстрация характеристик и примеры использования приведены в папке samples. 47 | 48 | ## Ограничения: 49 | - Возможны так называемые побочные эффекты при использовании нескольких одномерных или двумерных массивов, чередующихся в определенных местах. Пример побочных эффектов также приведён в папке samples. 50 | 51 | ## УСТАНОВКА: 52 | 53 | ``` 54 | $ composer require alhimik1986/php-excel-templator 55 | ``` 56 | 57 | ### Правила именования шаблонных переменных 58 | Правила могут быть любыми, но я могу предложить свою рекомендацию по именованию шаблонных переменных: 59 | - {var_name} - для строковых значений 60 | - [var_name] - для одномерных массивов 61 | - [[var_name]] - для двумерных массивов 62 | 63 | 64 | ### Как вставить одномерный массив, чтобы в таблице создавались не строки, а столбцы? 65 | Для этого вместо одномерного массива вставляем двумерный следующим образом: 66 | ``` 67 | $param['[[var_name]]'] = [['text 1', 'text 2', 'text 3']]; 68 | ``` 69 | 70 | ## Испрользование сеттеров 71 | В примере выше использовался минимальный код без сеттеров. 72 | В нём тип данных (например: строка, одномерный массив или двумерный массив) распознаётся автоматически и подставляется нужный сеттер. 73 | Но если мы хотим использовать определённый сеттер, тогда тот же самый код будет выглядеть следующим образом: 74 | ``` 75 | use alhimik1986\PhpExcelTemplator\PhpExcelTemplator; 76 | use alhimik1986\PhpExcelTemplator\params\ExcelParam; 77 | use alhimik1986\PhpExcelTemplator\params\CallbackParam; 78 | use alhimik1986\PhpExcelTemplator\setters\CellSetterStringValue; 79 | 80 | require_once('vendor/autoload.php'); // если используется чистый код без фреймворка 81 | 82 | $params = [ 83 | '{current_date}' => new ExcelParam(CellSetterStringValue::class, date('d-m-Y')), 84 | '{department}' => new ExcelParam(CellSetterStringValue::class, 'Sales department'), 85 | ]; 86 | PhpExcelTemplator::saveToFile('./template.xlsx', './exported_file.xlsx', $params); 87 | ``` 88 | На данный момент существует 3 вида сеттеров: 89 | - CellSetterStringValue (для строчных значений) 90 | - CellSetterArrayValue (для одномерных массивов) 91 | - CellSetterArray2DValue (для двумерных массивов) 92 | 93 | Вы спросите, для чего указывать сеттеры явно? 94 | Во-первых, для гибкости: вдруг вы захотите создать собственный сеттер со своими алгоритмами, которые устраняют упоминаемые мной побочные эффекты. 95 | Во-вторых, в каждом сеттере можно передавать функцию обратного вызова, в которой мы можем менять стили вставляемых ячеек. Например, нужно отметить жирным шрифтом сотрудников, которые отличились в этом месяце. 96 | 97 | Примеры кода, в котором используются все виды сеттеров, приведены папке samples. 98 | 99 | ## Как задать стили без использования сеттеров? 100 | В большинстве случаев задавать сеттеры явно - не так удобно. Хочется использовать минимум кода. Поэтому есть возможность задать стили без использования сеттеров: 101 | ``` 102 | use alhimik1986\PhpExcelTemplator\PhpExcelTemplator; 103 | use alhimik1986\PhpExcelTemplator\params\CallbackParam; 104 | require_once('vendor/autoload.php'); // если используется чистый код без фреймворка 105 | 106 | $params = [ 107 | '{current_date}' => date('d-m-Y'), 108 | '{department}' => 'Sales department', 109 | '[sales_amount]' => [ 110 | '10230', 111 | '45100', 112 | '70500', 113 | ], 114 | ]; 115 | 116 | $callbacks = [ 117 | '[sales_amount]' => function(CallbackParam $param) { 118 | $amount = $param->param[$param->row_index]; 119 | if ($amount > 50000) { 120 | $cell_coordinate = $param->coordinate; 121 | $param->sheet->getStyle($cell_coordinate)->getFont()->setBold(true); 122 | } 123 | }, 124 | ]; 125 | 126 | PhpExcelTemplator::saveToFile('./template.xlsx', './exported_file.xlsx', $params, $callbacks); 127 | ``` 128 | 129 | ## Специальный сеттер для специального шаблона (CellSetterArrayValueSpecial) 130 | Существуют такие особые шаблоны, в которых, вставляя одномерный массив, нужно именно вставлять полностью строку, а не вставлять ячейку со сдвигом вниз и, к тому же, делать объединение ячеек, так же как и в ячейке, в которой была шаблонная переменная. 131 | 132 | ![Специальный шаблон](readme_resources/special_template.png) 133 | 134 | Для таких шаблонов создан специальный сеттер: CellSetterArrayValueSpecial. Пример его использования приведён в папке: samples/8_special_template. 135 | 136 | 137 | ## Использование событий 138 | 139 | Ниже я привёл перечень возможных событий и пояснения, для чего их можно применить: 140 | ```php 141 | $events = [ 142 | PhpExcelTemplator::BEFORE_INSERT_PARAMS => function(Worksheet $sheet, array $templateVarsArr) { 143 | // Возникает перед вставкой значений в шаблонные переменные 144 | }, 145 | PhpExcelTemplator::AFTER_INSERT_PARAMS => function(Worksheet $sheet, array $templateVarsArr) { 146 | // Возникает после вставки значений в шаблонные переменные. 147 | // Применяется, если нужно что-нибудь вставить в таблицу после того, как были созданы колонки и строки. 148 | // Например, когда нужно вставить массив картинок. 149 | // Если вставить картинки, используя $callbacks, то картинки могут смещаться вправо, 150 | // так как при вставке значений в шаблонные переменные, которые находятся ниже, могу создаваться дополнительные колонки. 151 | // Поэтому массив картинок лучше вставлять после того, как все дополнительные колонки был созданы. 152 | // Для этого используется это событие. 153 | // Пример его использования приведён в папке: samples/10_images 154 | }, 155 | PhpExcelTemplator::BEFORE_SAVE => function(Spreadsheet $spreadsheet, IWriter $writer) { 156 | // Возникает перед созданием excel-файла после вставки значений в шаблонные переменные. 157 | // Может использоваться, если нам нужно произвести манипуляции с объектом $writer или $spreadsheet. 158 | // Например: $writer->setPreCalculateFormulas(false); 159 | 160 | }, 161 | ]; 162 | $callbacks = []; 163 | $templateFile = './template.xlsx'; 164 | $fileName = './exported_file.xlsx'; 165 | $params = [ 166 | // ... 167 | ]; 168 | 169 | PhpExcelTemplator::saveToFile($templateFile, $fileName, $params, $callbacks, $events); 170 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alhimik1986/php-excel-templator", 3 | "description": "PHP Spreadsheet extension for generating excel files from template", 4 | "keywords": ["php", "excel", "template", "templator", "templater"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Sidorovich Nikolay", 9 | "email": "sidorovich21101986@mail.ru" 10 | } 11 | ], 12 | "minimum-stability": "dev", 13 | "autoload": { 14 | "psr-4": { 15 | "alhimik1986\\PhpExcelTemplator\\": "src" 16 | } 17 | }, 18 | "require": { 19 | "php": "^7.4 || ^8.0", 20 | "phpoffice/phpspreadsheet": ">=1.22" 21 | }, 22 | "require-dev": { 23 | "roave/security-advisories": "dev-master" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /readme_resources/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/readme_resources/demo.png -------------------------------------------------------------------------------- /readme_resources/demo_ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/readme_resources/demo_ru.png -------------------------------------------------------------------------------- /readme_resources/exported_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/readme_resources/exported_file.png -------------------------------------------------------------------------------- /readme_resources/special_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/readme_resources/special_template.png -------------------------------------------------------------------------------- /readme_resources/template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/readme_resources/template.png -------------------------------------------------------------------------------- /samples/10_images/exported_file.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/10_images/exported_file.xlsx -------------------------------------------------------------------------------- /samples/10_images/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/10_images/images/1.png -------------------------------------------------------------------------------- /samples/10_images/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/10_images/images/2.png -------------------------------------------------------------------------------- /samples/10_images/images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/10_images/images/3.png -------------------------------------------------------------------------------- /samples/10_images/images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/10_images/images/4.png -------------------------------------------------------------------------------- /samples/10_images/images/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/10_images/images/5.png -------------------------------------------------------------------------------- /samples/10_images/index.php: -------------------------------------------------------------------------------- 1 | date('d-m-Y'), 24 | '{department}' => 'Sales department', 25 | '[sales_manager]' => [ 26 | 'Nalty A.', 27 | 'Ochoa S.', 28 | 'Patel O.', 29 | ], 30 | '[[images]]' => [ 31 | ['1', '2', '3', '4', '5'], 32 | ], 33 | '[[sales_amount_by_hours]]' => [ 34 | ['10000', '2000', '300', '40000', '500'], 35 | ['1000', '200000', '3000', '400', '5000'], 36 | ['10000', '2000', '30000', '40000', '500000'], 37 | ], 38 | ]; 39 | /* 40 | // If you insert images like here, then these images will shift to the right, 41 | // because the parameter [[sales_amount_by_hours]] creates additional columns. 42 | $callbacks = [ 43 | '[[images]]' => function(CallbackParam $param) { 44 | $sheet = $param->sheet; 45 | $row_index = $param->row_index; 46 | $col_index = $param->col_index; 47 | $cell_coordinate = $param->coordinate; 48 | $amount = $param->param[$row_index][$col_index]; 49 | 50 | $drawing = new Drawing(); 51 | $drawing->setPath(__DIR__.'/images/' . ((int)$amount) . '.png'); 52 | $drawing->setCoordinates($cell_coordinate); 53 | $drawing->setWorksheet($sheet); 54 | }, 55 | ]; 56 | */ 57 | // To get around this bug, we have to insert pictures after inserting the parameters. 58 | // For this we will use events. 59 | $events = [ 60 | PhpExcelTemplator::AFTER_INSERT_PARAMS => function(Worksheet $sheet, array $templateVarsArr) { 61 | $imageVarCol = null; 62 | $imageVarColIndex = null; 63 | $imageVarRow = null; 64 | foreach ($templateVarsArr as $rowKey => $row) { 65 | foreach ($row as $colKey => $colContent) { 66 | if ($colContent == '[[images]]') { 67 | $imageVarColIndex = $colKey + 1; 68 | $imageVarRow = $rowKey + 1; 69 | $imageVarCol = Coordinate::stringFromColumnIndex($imageVarColIndex); 70 | } 71 | } 72 | } 73 | $col_width = $sheet->getColumnDimension($imageVarCol)->getWidth(); 74 | 75 | for ($i = 0; $i < 5; $i++) { 76 | $colIndex = $imageVarColIndex + $i; 77 | $coordinate = Coordinate::stringFromColumnIndex($colIndex) . $imageVarRow; 78 | $drawing = new Drawing(); 79 | $drawing->setPath(__DIR__ . '/images/' . ($i + 1) . '.png'); 80 | $drawing->setCoordinates($coordinate); 81 | $drawing->setWorksheet($sheet); 82 | // The templator copy style of cell, but not width. Let's make it manually 83 | $sheet->getColumnDimension(Coordinate::stringFromColumnIndex($colIndex))->setWidth($col_width); 84 | // Clear cell, which must contain just an image 85 | $sheet->getCell($coordinate)->setValue(null); 86 | } 87 | }, 88 | ]; 89 | $callbacks = []; 90 | 91 | PhpExcelTemplator::saveToFile($templateFile, $fileName, $params, $callbacks, $events); 92 | // PhpExcelTemplator::outputToFile($templateFile, $fileName, $params, $callbacks); // to download the file from web page 93 | -------------------------------------------------------------------------------- /samples/10_images/run.cmd: -------------------------------------------------------------------------------- 1 | @php index.php -------------------------------------------------------------------------------- /samples/10_images/template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/10_images/template.xlsx -------------------------------------------------------------------------------- /samples/1_simple/exported_file.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/1_simple/exported_file.xlsx -------------------------------------------------------------------------------- /samples/1_simple/index.php: -------------------------------------------------------------------------------- 1 | date('d-m-Y'), 8 | '{department}' => 'Sales department', 9 | ]); 10 | 11 | /* 12 | // to download the file from web page 13 | PhpExcelTemplator::outputToFile('./template.xlsx', './exported_file.xlsx', [ 14 | '{current_date}' => date('d-m-Y'), 15 | '{department}' => 'Sales department', 16 | ]); 17 | */ 18 | -------------------------------------------------------------------------------- /samples/1_simple/run.cmd: -------------------------------------------------------------------------------- 1 | @php index.php -------------------------------------------------------------------------------- /samples/1_simple/template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/1_simple/template.xlsx -------------------------------------------------------------------------------- /samples/2_simple/exported_file.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/2_simple/exported_file.xlsx -------------------------------------------------------------------------------- /samples/2_simple/index.php: -------------------------------------------------------------------------------- 1 | date('d-m-Y'), 12 | '{department}' => 'Sales department', 13 | '[date]' => [ 14 | '01-06-2018', 15 | '02-06-2018', 16 | '03-06-2018', 17 | '04-06-2018', 18 | '05-06-2018', 19 | ], 20 | '[code]' => [ 21 | '0001543', 22 | '0003274', 23 | '000726', 24 | '0012553', 25 | '0008245', 26 | ], 27 | '[manager]' => [ 28 | 'Adams D.', 29 | 'Baker A.', 30 | 'Clark H.', 31 | 'Davis O.', 32 | 'Evans P.', 33 | ], 34 | '[sales_amount]' => [ 35 | '10 230 $', 36 | '45 100 $', 37 | '70 500 $', 38 | '362 180 $', 39 | '5 900 $', 40 | ], 41 | '[sales_manager]' => [ 42 | 'Nalty A.', 43 | 'Ochoa S.', 44 | 'Patel O.', 45 | ], 46 | '[[hours]]' => [ 47 | ['01', '02', '03', '04', '05', '06', '07', '08'], 48 | ], 49 | '[[sales_amount_by_hours]]' => [ 50 | ['100', '200', '300', '400', '500', '600', '700', '800'], 51 | ['1000', '2000', '3000', '4000', '5000', '6000', '7000', '8000'], 52 | ['10000', '20000', '30000', '40000', '50000', '60000', '70000', '80000'], 53 | ], 54 | ]; 55 | PhpExcelTemplator::saveToFile($templateFile, $fileName, $params); 56 | // PhpExcelTemplator::outputToFile($templateFile, $fileName, $params); // to download the file from web page 57 | -------------------------------------------------------------------------------- /samples/2_simple/run.cmd: -------------------------------------------------------------------------------- 1 | @php index.php -------------------------------------------------------------------------------- /samples/2_simple/template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/2_simple/template.xlsx -------------------------------------------------------------------------------- /samples/3_basic/exported_file.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/3_basic/exported_file.xlsx -------------------------------------------------------------------------------- /samples/3_basic/index.php: -------------------------------------------------------------------------------- 1 | new ExcelParam(CellSetterStringValue::class, $now->format('d-m-Y')), 59 | '{department}' => new ExcelParam(CellSetterStringValue::class, 'Sales department'), 60 | 61 | '[date]' => new ExcelParam(CellSetterArrayValue::class, $dateArr), 62 | '[code]' => new ExcelParam(CellSetterArrayValue::class, $codeArr), 63 | '[manager]' => new ExcelParam(CellSetterArrayValue::class, $managerArr), 64 | '[sales_amount]' => new ExcelParam(CellSetterArrayValue::class, $salesAmountArr), 65 | 66 | '[sales_manager]' => new ExcelParam(CellSetterArrayValue::class, $salesManagerArr), 67 | '[[hours]]' => new ExcelParam(CellSetterArray2DValue::class, $hoursArr), 68 | '[[sales_amount_by_hours]]' => new ExcelParam(CellSetterArray2DValue::class, $numOfSalesByHours), 69 | ]; 70 | PhpExcelTemplator::saveToFile($templateFile, $fileName, $params); 71 | // PhpExcelTemplator::outputToFile($templateFile, $fileName, $params); // to download the file from web page 72 | -------------------------------------------------------------------------------- /samples/3_basic/run.cmd: -------------------------------------------------------------------------------- 1 | @php index.php -------------------------------------------------------------------------------- /samples/3_basic/template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/3_basic/template.xlsx -------------------------------------------------------------------------------- /samples/4_side-effects/exported_file.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/4_side-effects/exported_file.xlsx -------------------------------------------------------------------------------- /samples/4_side-effects/index.php: -------------------------------------------------------------------------------- 1 | new ExcelParam(CellSetterStringValue::class, $now->format('d-m-Y')), 59 | '{department}' => new ExcelParam(CellSetterStringValue::class, 'Sales department'), 60 | 61 | '[date]' => new ExcelParam(CellSetterArrayValue::class, $dateArr), 62 | '[code]' => new ExcelParam(CellSetterArrayValue::class, $codeArr), 63 | '[manager]' => new ExcelParam(CellSetterArrayValue::class, $managerArr), 64 | '[sales_amount]' => new ExcelParam(CellSetterArrayValue::class, $salesAmountArr), 65 | 66 | '[sales_manager]' => new ExcelParam(CellSetterArrayValue::class, $salesManagerArr), 67 | '[[hours]]' => new ExcelParam(CellSetterArray2DValue::class, $hoursArr), 68 | '[[sales_amount_by_hours]]' => new ExcelParam(CellSetterArray2DValue::class, $numOfSalesByHours), 69 | ]; 70 | PhpExcelTemplator::saveToFile($templateFile, $fileName, $params); 71 | // PhpExcelTemplator::outputToFile($templateFile, $fileName, $params); // to download the file from web page 72 | -------------------------------------------------------------------------------- /samples/4_side-effects/run.cmd: -------------------------------------------------------------------------------- 1 | @php index.php -------------------------------------------------------------------------------- /samples/4_side-effects/template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/4_side-effects/template.xlsx -------------------------------------------------------------------------------- /samples/5_styles/exported_file.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/5_styles/exported_file.xlsx -------------------------------------------------------------------------------- /samples/5_styles/index.php: -------------------------------------------------------------------------------- 1 | new ExcelParam(STRING_TYPE, $now->format('d-m-Y')), 64 | '{department}' => new ExcelParam(STRING_TYPE, 'Sales department'), 65 | 66 | '[date]' => new ExcelParam(ARRAY_TYPE, $dateArr), 67 | '[code]' => new ExcelParam(ARRAY_TYPE, $codeArr), 68 | '[manager]' => new ExcelParam(ARRAY_TYPE, $managerArr), 69 | '[sales_amount]' => new ExcelParam(ARRAY_TYPE, $salesAmountArr, function(CallbackParam $param) { 70 | $sheet = $param->sheet; 71 | $row_index = $param->row_index; 72 | $cell_coordinate = $param->coordinate; 73 | $amount = $param->param[$row_index]; 74 | $amount = preg_replace('/[\s\$]/', '', $amount); 75 | if ($amount > 50000) { 76 | $sheet->getStyle($cell_coordinate)->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)->getStartColor()->setARGB('FFB5FFA8'); 77 | $sheet->getStyle($cell_coordinate)->getFont()->setBold(true); 78 | } 79 | }), 80 | 81 | '[sales_manager]' => new ExcelParam(ARRAY_TYPE, $salesManagerArr), 82 | '[[hours]]' => new ExcelParam(ARRAY_2D_TYPE, $hoursArr), 83 | '[[sales_amount_by_hours]]' => new ExcelParam(ARRAY_2D_TYPE, $salesByHoursArr, function(CallbackParam $param) { 84 | $sheet = $param->sheet; 85 | $row_index = $param->row_index; 86 | $col_index = $param->col_index; 87 | $cell_coordinate = $param->coordinate; 88 | $amount = $param->param[$row_index][$col_index]; 89 | if ($amount > 50000) { 90 | $sheet->getStyle($cell_coordinate)->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)->getStartColor()->setARGB('FFB5FFA8'); 91 | $sheet->getStyle($cell_coordinate)->getFont()->setBold(true); 92 | } 93 | }), 94 | ]; 95 | PhpExcelTemplator::saveToFile($templateFile, $fileName, $params); 96 | // PhpExcelTemplator::outputToFile($templateFile, $fileName, $params); // to download the file from web page 97 | -------------------------------------------------------------------------------- /samples/5_styles/run.cmd: -------------------------------------------------------------------------------- 1 | @php index.php -------------------------------------------------------------------------------- /samples/5_styles/template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/5_styles/template.xlsx -------------------------------------------------------------------------------- /samples/6_sheets/exported_file.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/6_sheets/exported_file.xlsx -------------------------------------------------------------------------------- /samples/6_sheets/index.php: -------------------------------------------------------------------------------- 1 | getActiveSheet()->toArray(); 14 | $templateSheet = clone $spreadsheet->getActiveSheet(); 15 | $callbacks = []; 16 | $events = []; 17 | 18 | $sheet1 = $spreadsheet->getSheet(0); 19 | PhpExcelTemplator::renderWorksheet($sheet1, $templateVarsArr, $params, $callbacks, $events); 20 | 21 | $sheet2 = clone $templateSheet; 22 | $sheet2->setTitle('Workshet 2'); 23 | $spreadsheet->addSheet($sheet2); 24 | PhpExcelTemplator::renderWorksheet($sheet2, $templateVarsArr, $params, $callbacks, $events); 25 | 26 | $sheet3 = clone $templateSheet; 27 | $sheet3->setTitle('Workshet 3'); 28 | $spreadsheet->addSheet($sheet3); 29 | PhpExcelTemplator::renderWorksheet($sheet3, $templateVarsArr, $params, $callbacks, $events); 30 | 31 | PhpExcelTemplator::saveSpreadsheetToFile($spreadsheet, $fileName); 32 | // PhpExcelTemplator::outputSpreadsheetToFile($spreadsheet, $fileName); // to download the file from web page 33 | -------------------------------------------------------------------------------- /samples/6_sheets/params.php: -------------------------------------------------------------------------------- 1 | new ExcelParam(STRING_TYPE, $now->format('d-m-Y')), 41 | '{department}' => new ExcelParam(STRING_TYPE, 'Sales department'), 42 | 43 | '[date]' => new ExcelParam(ARRAY_TYPE, $dateArr), 44 | '[code]' => new ExcelParam(ARRAY_TYPE, $codeArr), 45 | '[manager]' => new ExcelParam(ARRAY_TYPE, $managerArr), 46 | '[sales_amount]' => new ExcelParam(ARRAY_TYPE, $salesAmountArr), 47 | ]; 48 | 49 | return $params; 50 | -------------------------------------------------------------------------------- /samples/6_sheets/run.cmd: -------------------------------------------------------------------------------- 1 | @php index.php -------------------------------------------------------------------------------- /samples/6_sheets/template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/6_sheets/template.xlsx -------------------------------------------------------------------------------- /samples/7_styles_without_setters/exported_file.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/7_styles_without_setters/exported_file.xlsx -------------------------------------------------------------------------------- /samples/7_styles_without_setters/index.php: -------------------------------------------------------------------------------- 1 | date('d-m-Y'), 13 | '{department}' => 'Sales department', 14 | '[date]' => [ 15 | '01-06-2018', 16 | '02-06-2018', 17 | '03-06-2018', 18 | '04-06-2018', 19 | '05-06-2018', 20 | ], 21 | '[code]' => [ 22 | '0001543', 23 | '0003274', 24 | '000726', 25 | '0012553', 26 | '0008245', 27 | ], 28 | '[manager]' => [ 29 | 'Adams D.', 30 | 'Baker A.', 31 | 'Clark H.', 32 | 'Davis O.', 33 | 'Evans P.', 34 | ], 35 | '[sales_amount]' => [ 36 | '10 230 $', 37 | '45 100 $', 38 | '70 500 $', 39 | '362 180 $', 40 | '5 900 $', 41 | ], 42 | '[sales_manager]' => [ 43 | 'Nalty A.', 44 | 'Ochoa S.', 45 | 'Patel O.', 46 | ], 47 | '[[hours]]' => [ 48 | ['01', '02', '03', '04', '05', '06', '07', '08'], 49 | ], 50 | '[[sales_amount_by_hours]]' => [ 51 | ['10000', '2000', '300', '40000', '500', '600', '700', '800000'], 52 | ['1000', '200000', '3000', '400', '5000', '6000', '7000', '8000'], 53 | ['10000', '2000', '30000', '40000', '500000', '60', '70000', '800'], 54 | ], 55 | ]; 56 | $callbacks = [ 57 | '[sales_amount]' => function(CallbackParam $param) { 58 | $sheet = $param->sheet; 59 | $row_index = $param->row_index; 60 | $cell_coordinate = $param->coordinate; 61 | $amount = $param->param[$row_index]; 62 | $amount = preg_replace('/[\s\$]/', '', $amount); 63 | if ($amount > 50000) { 64 | $sheet->getStyle($cell_coordinate)->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)->getStartColor()->setARGB('FFB5FFA8'); 65 | $sheet->getStyle($cell_coordinate)->getFont()->setBold(true); 66 | } 67 | }, 68 | '[[sales_amount_by_hours]]' => function(CallbackParam $param) { 69 | $sheet = $param->sheet; 70 | $row_index = $param->row_index; 71 | $col_index = $param->col_index; 72 | $cell_coordinate = $param->coordinate; 73 | $amount = $param->param[$row_index][$col_index]; 74 | if ($amount > 50000) { 75 | $sheet->getStyle($cell_coordinate)->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)->getStartColor()->setARGB('FFB5FFA8'); 76 | $sheet->getStyle($cell_coordinate)->getFont()->setBold(true); 77 | } 78 | }, 79 | ]; 80 | PhpExcelTemplator::saveToFile($templateFile, $fileName, $params, $callbacks); 81 | // PhpExcelTemplator::outputToFile($templateFile, $fileName, $params, $callbacks); // to download the file from web page 82 | -------------------------------------------------------------------------------- /samples/7_styles_without_setters/run.cmd: -------------------------------------------------------------------------------- 1 | @php index.php -------------------------------------------------------------------------------- /samples/7_styles_without_setters/template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/7_styles_without_setters/template.xlsx -------------------------------------------------------------------------------- /samples/8_special_template/exported_file.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/8_special_template/exported_file.xlsx -------------------------------------------------------------------------------- /samples/8_special_template/index.php: -------------------------------------------------------------------------------- 1 | new ExcelParam(SPECIAL_ARRAY_TYPE, [ 16 | 'product 1', 17 | 'product 2', 18 | 'product 3', 19 | 'product 4', 20 | 'product 5', 21 | ]), 22 | '[product_count]' => new ExcelParam(SPECIAL_ARRAY_TYPE, [ 23 | '1', 24 | '2', 25 | '3', 26 | '4', 27 | '5', 28 | ]), 29 | '[product_price]' => new ExcelParam(SPECIAL_ARRAY_TYPE, [ 30 | '100', 31 | '200', 32 | '300', 33 | '400', 34 | '500', 35 | ]), 36 | '[product_summ]' => new ExcelParam(SPECIAL_ARRAY_TYPE, [ 37 | '100', 38 | '400', 39 | '900', 40 | '1 600', 41 | '2 500', 42 | ]), 43 | '[product_oem]' => new ExcelParam(SPECIAL_ARRAY_TYPE, [ 44 | 'Product OEM 1', 45 | 'Product OEM 2', 46 | 'Product OEM 3', 47 | 'Product OEM 4', 48 | 'Product OEM 5', 49 | ]), 50 | '[product_arrival_date]' => new ExcelParam(SPECIAL_ARRAY_TYPE, [ 51 | '2018-01-01', 52 | '2018-01-02', 53 | '2018-01-03', 54 | '2018-01-04', 55 | '2018-01-05', 56 | ]), 57 | '[product_currency]' => new ExcelParam(SPECIAL_ARRAY_TYPE, [ 58 | 'р', 59 | 'р', 60 | 'р', 61 | 'р', 62 | 'р', 63 | ]), 64 | '{all_summ}' => '5 500', 65 | '{dolg}' => '0', 66 | '{document_id}' => '1111111', 67 | '{document_date}' => date('Y-m-d'), 68 | ]; 69 | $callbacks = [ 70 | '{all_summ}' => function(CallbackParam $param) { 71 | $sheet = $param->sheet; 72 | $cell_coordinate = $param->coordinate; 73 | $sheet->getStyle($cell_coordinate)->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)->getStartColor()->setARGB('FFB5FFA8'); 74 | }, 75 | ]; 76 | PhpExcelTemplator::saveToFile($templateFile, $fileName, $params, $callbacks); 77 | // PhpExcelTemplator::outputToFile($templateFile, $fileName, $params, $callbacks); // to download the file from web page 78 | -------------------------------------------------------------------------------- /samples/8_special_template/run.cmd: -------------------------------------------------------------------------------- 1 | @php index.php -------------------------------------------------------------------------------- /samples/8_special_template/template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/8_special_template/template.xlsx -------------------------------------------------------------------------------- /samples/9_ods_files/exported_file.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/9_ods_files/exported_file.ods -------------------------------------------------------------------------------- /samples/9_ods_files/index.php: -------------------------------------------------------------------------------- 1 | date('d-m-Y'), 8 | '{department}' => 'Sales department', 9 | ]); 10 | -------------------------------------------------------------------------------- /samples/9_ods_files/run.cmd: -------------------------------------------------------------------------------- 1 | @php index.php -------------------------------------------------------------------------------- /samples/9_ods_files/template.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alhimik1986/php-excel-templator/38a37b5a8ff82778c48eb8744d93b5a525ccad28/samples/9_ods_files/template.ods -------------------------------------------------------------------------------- /samples/Bootstrap.php: -------------------------------------------------------------------------------- 1 | inserted_rows = $inserted_rows; 26 | $this->inserted_cols = $inserted_cols; 27 | } 28 | 29 | /** 30 | * @param $rowKey integer Current index of row 31 | * @param $colKey integer Current index of column 32 | * 33 | * @return integer Total count of inserted rows, that were inserted from 0 row to current row 34 | */ 35 | public function getInsertedRowsCount(int $rowKey, int $colKey): int 36 | { 37 | $this->_addKeysTo2DArrayIfNotExists($this->inserted_rows, $colKey, $rowKey); 38 | 39 | $result = 0; 40 | for ($i = 0; $i < $rowKey; $i++) { 41 | $result += $this->inserted_rows[$colKey][$i]; 42 | } 43 | return $result; 44 | } 45 | 46 | /** 47 | * @param $rowKey integer Current index of row 48 | * @param $colKey integer Current index of column 49 | * 50 | * @return integer Total count of inserted columns, that were inserted from 0 column to current column 51 | */ 52 | public function getInsertedColsCount(int $rowKey, int $colKey): int 53 | { 54 | $this->_addKeysTo2DArrayIfNotExists($this->inserted_cols, $rowKey, $colKey); 55 | 56 | $result = 0; 57 | for ($i = 0; $i < $colKey; $i++) { 58 | $result += $this->inserted_cols[$rowKey][$i]; 59 | } 60 | return $result; 61 | } 62 | 63 | /** 64 | * @param $rowKey integer The row index, where was template variable 65 | * @param $colKey integer The column index, where was template variable 66 | * @param $insertedColsCount integer 67 | */ 68 | public function setInsertedCols(int $rowKey, int $colKey, int $insertedColsCount): void 69 | { 70 | $this->_addKeysTo2DArrayIfNotExists($this->inserted_cols, $rowKey, $colKey); 71 | $this->inserted_cols[$rowKey][$colKey] = $insertedColsCount; 72 | } 73 | 74 | /** 75 | * @param $rowKey integer The row index, where was template variable 76 | * @param $colKey integer The column index, where was template variable 77 | * @param $insertedRowsCount integer 78 | */ 79 | public function setInsertedRows(int $rowKey, int $colKey, int $insertedRowsCount): void 80 | { 81 | $this->_addKeysTo2DArrayIfNotExists($this->inserted_rows, $colKey, $rowKey); 82 | $this->inserted_rows[$colKey][$rowKey] = $insertedRowsCount; 83 | } 84 | 85 | /** 86 | * @param $rowKey integer The row index, where was template variable 87 | * @param $colKey integer The column index, where was template variable 88 | * @param $insertedColsCount integer 89 | */ 90 | public function addInsertedCols(int $rowKey, int $colKey, int $insertedColsCount): void 91 | { 92 | $this->_addKeysTo2DArrayIfNotExists($this->inserted_cols, $rowKey, $colKey); 93 | $this->inserted_cols[$rowKey][$colKey] += $insertedColsCount; 94 | } 95 | 96 | public function addInsertedRows(int $rowKey, int $colKey, int $insertedRowsCount): void 97 | { 98 | $this->_addKeysTo2DArrayIfNotExists($this->inserted_rows, $colKey, $rowKey); 99 | $this->inserted_rows[$colKey][$rowKey] += $insertedRowsCount; 100 | } 101 | 102 | public function getInsertedRows(int $rowKey, int $colKey): int 103 | { 104 | $this->_addKeysTo2DArrayIfNotExists($this->inserted_rows, $colKey, $rowKey); 105 | return $this->inserted_rows[$colKey][$rowKey]; 106 | } 107 | 108 | public function getInsertedCols(int $rowKey, int $colKey): int 109 | { 110 | $this->_addKeysTo2DArrayIfNotExists($this->inserted_cols, $rowKey, $colKey); 111 | return $this->inserted_cols[$rowKey][$colKey]; 112 | } 113 | 114 | /** 115 | * Inserts indexes to specified array. The array filled with indexes from 0 to $key_index, if the index does not exist. 116 | */ 117 | private function _addKeysToArrayIfNotExists(array &$array, int $keyIndex): void 118 | { 119 | for ($i = 0; $i <= $keyIndex; $i++) { 120 | if (!array_key_exists($i, $array)) { 121 | $array[$i] = 0; 122 | } 123 | } 124 | } 125 | 126 | /** 127 | * Inserts indexes to specified 2D array. The array filled with indexes from 0 to $key_index, if the indexes does not exist. 128 | */ 129 | private function _addKeysTo2DArrayIfNotExists(array &$array, int $i_max, int $j_max): void 130 | { 131 | for ($i = 0; $i <= $i_max; $i++) { 132 | for ($j = 0; $j <= $j_max; $j++) { 133 | if (!array_key_exists($i, $array)) { 134 | $array[$i] = []; 135 | } 136 | if (!array_key_exists($j, $array[$i])) { 137 | $array[$i][$j] = 0; 138 | } 139 | } 140 | } 141 | } 142 | 143 | /** 144 | * @param $rowKey integer The row index, where was template variable 145 | * @param $colKey integer The column index, where was template variable 146 | * 147 | * @return integer Index of row, considering the count of inserted columns and rows 148 | */ 149 | public function getCurrentRowIndex(int $rowKey, int $colKey): int 150 | { 151 | $inserted_rows = $this->getInsertedRowsCount($rowKey, $colKey); 152 | return $rowKey + 1 + $inserted_rows; 153 | } 154 | 155 | /** 156 | * @return integer Index of column, considering the count of inserted columns and rows 157 | */ 158 | public function getCurrentColIndex(int $rowKey, int $colKey): int 159 | { 160 | $inserted_cols = $this->getInsertedColsCount($rowKey, $colKey); 161 | return $colKey + 1 + $inserted_cols; 162 | } 163 | 164 | /** 165 | * @return string Index of column (as a letter), considering the count of inserted columns and rows 166 | */ 167 | public function getCurrentCol(int $rowKey, int $colKey): string 168 | { 169 | $col_index = $this->getCurrentColIndex($rowKey, $colKey); 170 | return Coordinate::stringFromColumnIndex($col_index); 171 | } 172 | 173 | /** 174 | * @return string Cell coordinate, considering the count of inserted columns and rows 175 | */ 176 | public function getCurrentCellCoordinate(int $rowKey, int $colKey): string 177 | { 178 | $col = $this->getCurrentCol($rowKey, $colKey); 179 | $row_index = $this->getCurrentRowIndex($rowKey, $colKey); 180 | return $col . $row_index; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/PhpExcelTemplator.php: -------------------------------------------------------------------------------- 1 | |array>|array>> $params Parameters of the setter 30 | * @param array $callbacks An associative array of callbacks to change cell styles without using setters 31 | * @param array $events Events, applied for additional manipulations with the spreadsheet and the writer 32 | * 33 | * @throws \PhpOffice\PhpSpreadsheet\Exception 34 | */ 35 | public static function outputToFile(string $templateFile, string $outputFile, array $params, array $callbacks = [], array $events = []): void 36 | { 37 | $spreadsheet = static::getSpreadsheet($templateFile); 38 | $sheet = $spreadsheet->getActiveSheet(); 39 | $templateVarsArr = $sheet->toArray(); 40 | static::renderWorksheet($sheet, $templateVarsArr, $params, $callbacks, $events); 41 | static::outputSpreadsheetToFile($spreadsheet, $outputFile, $events); 42 | } 43 | 44 | /** 45 | * @param string $templateFile Path to *.xlsx template file 46 | * @param string $outputFile Exported file path 47 | * @param ExcelParam[]|array|array>|array>> $params Parameters of the setter 48 | * @param array $callbacks An associative array of callbacks to change cell styles without using setters 49 | * @param array $events Events, applied for additional manipulations with the spreadsheet and the writer 50 | * 51 | * @throws \PhpOffice\PhpSpreadsheet\Exception 52 | */ 53 | public static function saveToFile(string $templateFile, string $outputFile, array $params, array $callbacks = [], array $events = []): void 54 | { 55 | $spreadsheet = static::getSpreadsheet($templateFile); 56 | $sheet = $spreadsheet->getActiveSheet(); 57 | $templateVarsArr = $sheet->toArray(); 58 | static::renderWorksheet($sheet, $templateVarsArr, $params, $callbacks, $events); 59 | static::saveSpreadsheetToFile($spreadsheet, $outputFile, $events); 60 | } 61 | 62 | /** 63 | * @param string $templateFile Path to *.xlsx template file 64 | * 65 | * @return Spreadsheet 66 | */ 67 | protected static function getSpreadsheet(string $templateFile): Spreadsheet 68 | { 69 | return IOFactory::load($templateFile); 70 | } 71 | 72 | /** 73 | * @param Worksheet $sheet The sheet, which contains the template variables 74 | * @param array> $templateVarsArr An array of cells contained in the template file 75 | * @param ExcelParam[]|array|array>|array>> $params Parameters of the setter 76 | * @param array $callbacks An associative array of callbacks to change cell styles without using setters 77 | * @param array $events Events, applied for additional manipulations with the spreadsheet and the writer 78 | * 79 | * @return Worksheet 80 | * @throws \PhpOffice\PhpSpreadsheet\Exception 81 | * @throws Exception 82 | */ 83 | public static function renderWorksheet(Worksheet $sheet, array $templateVarsArr, array $params, array $callbacks = [], array $events = []): Worksheet 84 | { 85 | $params = static::getCorrectedParams($params, $callbacks); 86 | static::clearTemplateVarsInSheet($sheet, $templateVarsArr, $params); 87 | 88 | if (isset($events[self::BEFORE_INSERT_PARAMS]) && is_callable($events[self::BEFORE_INSERT_PARAMS])) { 89 | $events[self::BEFORE_INSERT_PARAMS]($sheet, $templateVarsArr); 90 | } 91 | 92 | static::insertParams($sheet, $templateVarsArr, $params); 93 | 94 | if (isset($events[self::AFTER_INSERT_PARAMS]) && is_callable($events[self::AFTER_INSERT_PARAMS])) { 95 | $events[self::AFTER_INSERT_PARAMS]($sheet, $templateVarsArr); 96 | } 97 | 98 | return $sheet; 99 | } 100 | 101 | /** 102 | * If the params are an array, it will be converted to ExcelParam with the corresponding setter. 103 | * 104 | * @param ExcelParam[]|array|array>|array>> $params Parameters of the setter 105 | * @param array $callbacks 106 | * 107 | * @return array 108 | */ 109 | protected static function getCorrectedParams(array $params, array $callbacks): array 110 | { 111 | $result = []; 112 | foreach ($params as $key => $param) { 113 | if (!$param instanceof ExcelParam) { 114 | $setterClass = CellSetterStringValue::class; 115 | $callback = array_key_exists($key, $callbacks) ? $callbacks[$key] : static function () { 116 | }; 117 | 118 | if (is_array($param)) { 119 | $valueArr = reset($param); 120 | $setterClass = is_array($valueArr) 121 | ? CellSetterArray2DValue::class 122 | : CellSetterArrayValue::class; 123 | } 124 | 125 | $result[$key] = new ExcelParam($setterClass, $param, $callback); 126 | } else { 127 | $result[$key] = $param; 128 | } 129 | } 130 | 131 | return $result; 132 | } 133 | 134 | /** 135 | * Exports the spreadsheet to download it as a file. 136 | * 137 | * @param Spreadsheet $spreadsheet 138 | * @param string $outputFile 139 | * @param array $events 140 | * 141 | * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception 142 | */ 143 | public static function outputSpreadsheetToFile(Spreadsheet $spreadsheet, string $outputFile, array $events = []): void 144 | { 145 | $writer = static::getWriter($spreadsheet); 146 | Calculation::getInstance($spreadsheet)->clearCalculationCache(); 147 | static::setHeaders(basename($outputFile)); 148 | 149 | if (isset($events[self::BEFORE_SAVE]) && is_callable($events[self::BEFORE_SAVE])) { 150 | $events[self::BEFORE_SAVE]($spreadsheet, $writer); 151 | } 152 | 153 | $writer->save('php://output'); 154 | } 155 | 156 | /** 157 | * Saves the spreadsheet as a file. 158 | * 159 | * @param Spreadsheet $spreadsheet 160 | * @param string $outputFile 161 | * @param array $events 162 | * 163 | * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception 164 | */ 165 | public static function saveSpreadsheetToFile(Spreadsheet $spreadsheet, string $outputFile, array $events = []): void 166 | { 167 | $writer = static::getWriter($spreadsheet); 168 | Calculation::getInstance($spreadsheet)->clearCalculationCache(); 169 | 170 | if (isset($events['beforeSave']) && is_callable($events['beforeSave'])) { 171 | $events['beforeSave']($spreadsheet, $writer); 172 | } 173 | 174 | $writer->save($outputFile); 175 | } 176 | 177 | /** 178 | * @param Spreadsheet $spreadsheet 179 | * 180 | * @return IWriter 181 | * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception 182 | */ 183 | protected static function getWriter(Spreadsheet $spreadsheet): IWriter 184 | { 185 | return IOFactory::createWriter($spreadsheet, 'Xlsx'); 186 | } 187 | 188 | /** 189 | * Sets the header parameters needed to download the Excel file. 190 | * 191 | * @param string $fileName 192 | */ 193 | protected static function setHeaders(string $fileName): void 194 | { 195 | header('Content-Disposition: attachment; filename="' . $fileName . '"'); 196 | header('Content-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); 197 | header('Pragma: public'); 198 | 199 | header('Content-Transfer-Encoding: binary'); 200 | header('Cache-Control: must-revalidate'); 201 | } 202 | 203 | /** 204 | * Clears template variables in the sheet of the template file 205 | * 206 | * @param Worksheet $sheet 207 | * @param array> $templateVars Cells array of the template file 208 | * @param array $params 209 | */ 210 | protected static function clearTemplateVarsInSheet(Worksheet $sheet, array $templateVars, array $params): void 211 | { 212 | $paramKeys = array_keys($params); 213 | foreach ($templateVars as $row_key => $row) { 214 | foreach ($row as $col_key => $col_content) { 215 | if ($col_content) { 216 | foreach ($paramKeys as $paramKey) { 217 | if (strpos($col_content, $paramKey) !== false) { 218 | $cell_address = CellAddress::fromColumnAndRow($col_key + 1, $row_key + 1); 219 | $sheet->setCellValueExplicit($cell_address, null, DataType::TYPE_NULL); 220 | } 221 | } 222 | } 223 | } 224 | } 225 | } 226 | 227 | /** 228 | * Inserts values to cells instead of template variables 229 | * 230 | * @param Worksheet $sheet 231 | * @param array> $templateVarsArr 232 | * @param array $params 233 | * 234 | * @throws Exception 235 | */ 236 | public static function insertParams(Worksheet $sheet, array $templateVarsArr, array $params): void 237 | { 238 | $insertedCells = new InsertedCells(); 239 | foreach ($templateVarsArr as $rowKey => $row) { 240 | foreach ($row as $colKey => $colContent) { 241 | $colVarNames = self::_getTemplateVarsFromString($colContent, $params); 242 | foreach ($colVarNames as $tplVarName) { 243 | 244 | $setterClass = $params[$tplVarName]->setterClass; 245 | /** @var ICellSetter $setter */ 246 | $setter = new $setterClass(); 247 | 248 | $setterParam = new SetterParam([ 249 | 'sheet' => $sheet, 250 | 'tplVarName' => $tplVarName, 251 | 'params' => $params, 252 | 'rowKey' => $rowKey, 253 | 'colKey' => $colKey, 254 | 'colContent' => $colContent 255 | ]); 256 | 257 | $insertedCells = $setter->setCellValue($setterParam, $insertedCells); 258 | 259 | // After inserting value to the cell, I get the content of this cell to insert another value 260 | // (when the cell has some template variables) 261 | if (count($colVarNames) > 1) { 262 | $coordinate = $insertedCells->getCurrentCellCoordinate($rowKey, $colKey); 263 | $colContent = $sheet->getCell($coordinate)->getValue(); 264 | } 265 | } 266 | } 267 | } 268 | } 269 | 270 | /** 271 | * @param ?string $string The content of the cell, that may contain a template variable 272 | * @param array $params 273 | * 274 | * @return String[] Template variables in a string 275 | */ 276 | private static function _getTemplateVarsFromString(?string $string, array $params): array 277 | { 278 | $result = []; 279 | $paramKeys = array_keys($params); 280 | 281 | foreach ($paramKeys as $paramKey) { 282 | if ($string !== null && strpos($string, $paramKey) !== false) { 283 | $result[] = $paramKey; 284 | } 285 | } 286 | 287 | return $result; 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/PhpExcelTemplatorOds.php: -------------------------------------------------------------------------------- 1 | load($templateFile); 19 | } 20 | 21 | protected static function getWriter(Spreadsheet $spreadsheet): IWriter 22 | { 23 | return IOFactory::createWriter($spreadsheet, 'Ods'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/PhpExcelTemplatorPdf.php: -------------------------------------------------------------------------------- 1 | = ($beforeRow + $numberOfRows)) && 108 | ($cellRow < $beforeRow) 109 | ) { 110 | return true; 111 | } elseif ( 112 | $numberOfCols < 0 && 113 | ($cellColumnIndex >= ($beforeColumnIndex + $numberOfCols)) && 114 | ($cellColumnIndex < $beforeColumnIndex) 115 | ) { 116 | return true; 117 | } 118 | 119 | return false; 120 | } 121 | 122 | /** 123 | * Update page breaks when inserting/deleting rows/columns. 124 | * 125 | * @param Worksheet $worksheet The worksheet that we're editing 126 | * @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1') 127 | * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before 128 | * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) 129 | * @param int $beforeRow Number of the row we're inserting/deleting before 130 | * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) 131 | */ 132 | protected function adjustPageBreaks(Worksheet $worksheet, $beforeCellAddress, $beforeColumnIndex, $numberOfColumns, $beforeRow, $numberOfRows): void 133 | { 134 | $aBreaks = $worksheet->getBreaks(); 135 | ($numberOfColumns > 0 || $numberOfRows > 0) ? 136 | uksort($aBreaks, ['self', 'cellReverseSort']) : uksort($aBreaks, ['self', 'cellSort']); 137 | 138 | foreach ($aBreaks as $key => $value) { 139 | if (self::cellAddressInDeleteRange($key, $beforeRow, $numberOfRows, $beforeColumnIndex, $numberOfColumns)) { 140 | // If we're deleting, then clear any defined breaks that are within the range 141 | // of rows/columns that we're deleting 142 | $worksheet->setBreak($key, Worksheet::BREAK_NONE); 143 | } else { 144 | // Otherwise update any affected breaks by inserting a new break at the appropriate point 145 | // and removing the old affected break 146 | $newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); 147 | if ($key != $newReference) { 148 | $worksheet->setBreak($newReference, $value) 149 | ->setBreak($key, Worksheet::BREAK_NONE); 150 | } 151 | } 152 | } 153 | } 154 | 155 | /** 156 | * Update cell comments when inserting/deleting rows/columns. 157 | * 158 | * @param Worksheet $worksheet The worksheet that we're editing 159 | * @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1') 160 | * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before 161 | * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) 162 | * @param int $beforeRow Number of the row we're inserting/deleting before 163 | * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) 164 | */ 165 | protected function adjustComments($worksheet, $beforeCellAddress, $beforeColumnIndex, $numberOfColumns, $beforeRow, $numberOfRows): void 166 | { 167 | $aComments = $worksheet->getComments(); 168 | $aNewComments = []; // the new array of all comments 169 | 170 | foreach ($aComments as $key => &$value) { 171 | // Any comments inside a deleted range will be ignored 172 | if (!self::cellAddressInDeleteRange($key, $beforeRow, $numberOfRows, $beforeColumnIndex, $numberOfColumns)) { 173 | // Otherwise build a new array of comments indexed by the adjusted cell reference 174 | $newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); 175 | $aNewComments[$newReference] = $value; 176 | } 177 | } 178 | // Replace the comments array with the new set of comments 179 | $worksheet->setComments($aNewComments); 180 | } 181 | 182 | /** 183 | * Update hyperlinks when inserting/deleting rows/columns. 184 | * 185 | * @param Worksheet $worksheet The worksheet that we're editing 186 | * @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1') 187 | * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) 188 | * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) 189 | */ 190 | protected function adjustHyperlinks($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void 191 | { 192 | $aHyperlinkCollection = $worksheet->getHyperlinkCollection(); 193 | ($numberOfColumns > 0 || $numberOfRows > 0) ? 194 | uksort($aHyperlinkCollection, ['self', 'cellReverseSort']) : uksort($aHyperlinkCollection, ['self', 'cellSort']); 195 | 196 | foreach ($aHyperlinkCollection as $key => $value) { 197 | $newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); 198 | if ($key != $newReference) { 199 | $worksheet->setHyperlink($newReference, $value); 200 | $worksheet->setHyperlink($key, null); 201 | } 202 | } 203 | } 204 | 205 | /** 206 | * Update data validations when inserting/deleting rows/columns. 207 | * 208 | * @param Worksheet $worksheet The worksheet that we're editing 209 | * @param string $before Insert/Delete before this cell address (e.g. 'A1') 210 | * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) 211 | * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) 212 | */ 213 | protected function adjustDataValidations(Worksheet $worksheet, $before, $numberOfColumns, $numberOfRows): void 214 | { 215 | $aDataValidationCollection = $worksheet->getDataValidationCollection(); 216 | ($numberOfColumns > 0 || $numberOfRows > 0) ? 217 | uksort($aDataValidationCollection, ['self', 'cellReverseSort']) : uksort($aDataValidationCollection, ['self', 'cellSort']); 218 | 219 | foreach ($aDataValidationCollection as $key => $value) { 220 | $newReference = $this->updateCellReference($key, $before, $numberOfColumns, $numberOfRows); 221 | if ($key != $newReference) { 222 | $worksheet->setDataValidation($newReference, $value); 223 | $worksheet->setDataValidation($key, null); 224 | } 225 | } 226 | } 227 | 228 | /** 229 | * Update merged cells when inserting/deleting rows/columns. 230 | * 231 | * @param Worksheet $worksheet The worksheet that we're editing 232 | * @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1') 233 | * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) 234 | * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) 235 | */ 236 | protected function adjustMergeCells(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void 237 | { 238 | $aMergeCells = $worksheet->getMergeCells(); 239 | $aNewMergeCells = []; // the new array of all merge cells 240 | foreach ($aMergeCells as $key => &$value) { 241 | $newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); 242 | $aNewMergeCells[$newReference] = $newReference; 243 | } 244 | $worksheet->setMergeCells($aNewMergeCells); // replace the merge cells array 245 | } 246 | 247 | /** 248 | * Update protected cells when inserting/deleting rows/columns. 249 | * 250 | * @param Worksheet $worksheet The worksheet that we're editing 251 | * @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1') 252 | * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) 253 | * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) 254 | */ 255 | protected function adjustProtectedCells(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void 256 | { 257 | $aProtectedCells = $worksheet->getProtectedCells(); 258 | ($numberOfColumns > 0 || $numberOfRows > 0) ? 259 | uksort($aProtectedCells, ['self', 'cellReverseSort']) : uksort($aProtectedCells, ['self', 'cellSort']); 260 | foreach ($aProtectedCells as $key => $value) { 261 | $newReference = $this->updateCellReference($key, $beforeCellAddress, $numberOfColumns, $numberOfRows); 262 | if ($key != $newReference) { 263 | $worksheet->protectCells($newReference, $value, true); 264 | $worksheet->unprotectCells($key); 265 | } 266 | } 267 | } 268 | 269 | /** 270 | * Update column dimensions when inserting/deleting rows/columns. 271 | * 272 | * @param Worksheet $worksheet The worksheet that we're editing 273 | * @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1') 274 | * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) 275 | * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) 276 | */ 277 | protected function adjustColumnDimensions(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows): void 278 | { 279 | $aColumnDimensions = array_reverse($worksheet->getColumnDimensions(), true); 280 | if (!empty($aColumnDimensions)) { 281 | foreach ($aColumnDimensions as $objColumnDimension) { 282 | $newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1', $beforeCellAddress, $numberOfColumns, $numberOfRows); 283 | [$newReference] = Coordinate::coordinateFromString($newReference); 284 | if ($objColumnDimension->getColumnIndex() != $newReference) { 285 | $objColumnDimension->setColumnIndex($newReference); 286 | } 287 | } 288 | $worksheet->refreshColumnDimensions(); 289 | } 290 | } 291 | 292 | /** 293 | * Update row dimensions when inserting/deleting rows/columns. 294 | * 295 | * @param Worksheet $worksheet The worksheet that we're editing 296 | * @param string $beforeCellAddress Insert/Delete before this cell address (e.g. 'A1') 297 | * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) 298 | * @param int $beforeRow Number of the row we're inserting/deleting before 299 | * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) 300 | */ 301 | protected function adjustRowDimensions(Worksheet $worksheet, $beforeCellAddress, $numberOfColumns, $beforeRow, $numberOfRows): void 302 | { 303 | $aRowDimensions = array_reverse($worksheet->getRowDimensions(), true); 304 | if (!empty($aRowDimensions)) { 305 | foreach ($aRowDimensions as $objRowDimension) { 306 | $newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex(), $beforeCellAddress, $numberOfColumns, $numberOfRows); 307 | [, $newReference] = Coordinate::coordinateFromString($newReference); 308 | if ($objRowDimension->getRowIndex() != $newReference) { 309 | $objRowDimension->setRowIndex($newReference); 310 | } 311 | } 312 | $worksheet->refreshRowDimensions(); 313 | 314 | $copyDimension = $worksheet->getRowDimension($beforeRow - 1); 315 | for ($i = $beforeRow; $i <= $beforeRow - 1 + $numberOfRows; ++$i) { 316 | $newDimension = $worksheet->getRowDimension($i); 317 | $newDimension->setRowHeight($copyDimension->getRowHeight()); 318 | $newDimension->setVisible($copyDimension->getVisible()); 319 | $newDimension->setOutlineLevel($copyDimension->getOutlineLevel()); 320 | $newDimension->setCollapsed($copyDimension->getCollapsed()); 321 | } 322 | } 323 | } 324 | 325 | /** 326 | * Insert a new column or row, updating all possible related data. 327 | * 328 | * @param string $beforeCellAddress Insert before this cell address (e.g. 'A1') 329 | * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) 330 | * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) 331 | * @param Worksheet $worksheet The worksheet that we're editing 332 | */ 333 | public function insertNewBefore( 334 | string $beforeCellAddress, 335 | int $numberOfColumns, 336 | int $numberOfRows, 337 | Worksheet $worksheet, 338 | $pAfter=null 339 | ): void { 340 | $remove = ($numberOfColumns < 0 || $numberOfRows < 0); 341 | $allCoordinates = $worksheet->getCoordinates(); 342 | 343 | // Get coordinate of $beforeCellAddress 344 | [$beforeColumn, $beforeRow] = Coordinate::indexesFromString($beforeCellAddress); 345 | 346 | // Clear cells if we are removing columns or rows 347 | $highestColumn = $worksheet->getHighestColumn(); 348 | $highestRow = $worksheet->getHighestRow(); 349 | 350 | //******************************* My changes ******************************************** 351 | // Get coordinate of $pAfter 352 | if ($pAfter !== null) { 353 | list($afterColumn, $afterRow) = Coordinate::coordinateFromString($pAfter); 354 | $afterColumnIndex = Coordinate::columnIndexFromString($afterColumn); 355 | 356 | $highestColumn = Coordinate::stringFromColumnIndex($afterColumnIndex); 357 | $highestRow = $afterRow+1; 358 | } 359 | //*************************************************************************************** 360 | 361 | // 1. Clear column strips if we are removing columns 362 | if ($numberOfColumns < 0 && $beforeColumn - 2 + $numberOfColumns > 0) { 363 | for ($i = 1; $i <= $highestRow - 1; ++$i) { 364 | for ($j = $beforeColumn - 1 + $numberOfColumns; $j <= $beforeColumn - 2; ++$j) { 365 | $coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i; 366 | $worksheet->removeConditionalStyles($coordinate); 367 | if ($worksheet->cellExists($coordinate)) { 368 | $worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL); 369 | $worksheet->getCell($coordinate)->setXfIndex(0); 370 | } 371 | } 372 | } 373 | } 374 | 375 | // 2. Clear row strips if we are removing rows 376 | if ($numberOfRows < 0 && $beforeRow - 1 + $numberOfRows > 0) { 377 | for ($i = $beforeColumn - 1; $i <= Coordinate::columnIndexFromString($highestColumn) - 1; ++$i) { 378 | for ($j = $beforeRow + $numberOfRows; $j <= $beforeRow - 1; ++$j) { 379 | $coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j; 380 | $worksheet->removeConditionalStyles($coordinate); 381 | if ($worksheet->cellExists($coordinate)) { 382 | $worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL); 383 | $worksheet->getCell($coordinate)->setXfIndex(0); 384 | } 385 | } 386 | } 387 | } 388 | 389 | // Find missing coordinates. This is important when inserting column before the last column 390 | $missingCoordinates = array_filter( 391 | array_map(function ($row) use ($highestColumn) { 392 | return $highestColumn . $row; 393 | }, range(1, $highestRow)), 394 | function ($coordinate) use ($allCoordinates) { 395 | return !in_array($coordinate, $allCoordinates); 396 | } 397 | ); 398 | 399 | // Create missing cells with null values 400 | if (!empty($missingCoordinates)) { 401 | foreach ($missingCoordinates as $coordinate) { 402 | $worksheet->createNewCell($coordinate); 403 | } 404 | 405 | // Refresh all coordinates 406 | $allCoordinates = $worksheet->getCoordinates(); 407 | } 408 | 409 | // Loop through cells, bottom-up, and change cell coordinate 410 | if ($remove) { 411 | // It's faster to reverse and pop than to use unshift, especially with large cell collections 412 | $allCoordinates = array_reverse($allCoordinates); 413 | } 414 | while ($coordinate = array_pop($allCoordinates)) { 415 | $cell = $worksheet->getCell($coordinate); 416 | $cellIndex = Coordinate::columnIndexFromString($cell->getColumn()); 417 | 418 | if ($cellIndex - 1 + $numberOfColumns < 0) { 419 | continue; 420 | } 421 | 422 | // New coordinate 423 | $newCoordinate = Coordinate::stringFromColumnIndex($cellIndex + $numberOfColumns) . ($cell->getRow() + $numberOfRows); 424 | 425 | // Should the cell be updated? Move value and cellXf index from one cell to another. 426 | if (($cellIndex >= $beforeColumn) && ($cell->getRow() >= $beforeRow)) { 427 | // Update cell styles 428 | $worksheet->getCell($newCoordinate)->setXfIndex($cell->getXfIndex()); 429 | 430 | // Insert this cell at its new location 431 | if ($cell->getDataType() == DataType::TYPE_FORMULA) { 432 | // Formula should be adjusted 433 | $worksheet->getCell($newCoordinate) 434 | ->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle())); 435 | } else { 436 | // Formula should not be adjusted 437 | $worksheet->getCell($newCoordinate)->setValueExplicit($cell->getValue(), $cell->getDataType()); 438 | } 439 | 440 | // Clear the original cell 441 | $worksheet->getCellCollection()->delete($coordinate); 442 | } else { 443 | /* We don't need to update styles for rows/columns before our insertion position, 444 | but we do still need to adjust any formulae in those cells */ 445 | if ($cell->getDataType() == DataType::TYPE_FORMULA) { 446 | // Formula should be adjusted 447 | $cell->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle())); 448 | } 449 | } 450 | } 451 | 452 | // Duplicate styles for the newly inserted cells 453 | //******************************* My changes ******************************************** 454 | // $highestColumn = $worksheet->getHighestColumn(); 455 | // $highestRow = $worksheet->getHighestRow(); 456 | //*************************************************************************************** 457 | 458 | if ($numberOfColumns > 0 && $beforeColumn - 2 > 0) { 459 | for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) { 460 | // Style 461 | $coordinate = Coordinate::stringFromColumnIndex($beforeColumn - 1) . $i; 462 | if ($worksheet->cellExists($coordinate)) { 463 | $xfIndex = $worksheet->getCell($coordinate)->getXfIndex(); 464 | $conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ? 465 | $worksheet->getConditionalStyles($coordinate) : false; 466 | for ($j = $beforeColumn; $j <= $beforeColumn - 1 + $numberOfColumns; ++$j) { 467 | $worksheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex); 468 | if ($conditionalStyles) { 469 | $cloned = []; 470 | foreach ($conditionalStyles as $conditionalStyle) { 471 | $cloned[] = clone $conditionalStyle; 472 | } 473 | $worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($j) . $i, $cloned); 474 | } 475 | } 476 | } 477 | } 478 | } 479 | 480 | if ($numberOfRows > 0 && $beforeRow - 1 > 0) { 481 | for ($i = $beforeColumn; $i <= Coordinate::columnIndexFromString($highestColumn); ++$i) { 482 | // Style 483 | $coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1); 484 | if ($worksheet->cellExists($coordinate)) { 485 | $xfIndex = $worksheet->getCell($coordinate)->getXfIndex(); 486 | $conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ? 487 | $worksheet->getConditionalStyles($coordinate) : false; 488 | for ($j = $beforeRow; $j <= $beforeRow - 1 + $numberOfRows; ++$j) { 489 | $worksheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex); 490 | if ($conditionalStyles) { 491 | $cloned = []; 492 | foreach ($conditionalStyles as $conditionalStyle) { 493 | $cloned[] = clone $conditionalStyle; 494 | } 495 | $worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($i) . $j, $cloned); 496 | } 497 | } 498 | } 499 | } 500 | } 501 | 502 | // Update worksheet: column dimensions 503 | $this->adjustColumnDimensions($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); 504 | 505 | // Update worksheet: row dimensions 506 | $this->adjustRowDimensions($worksheet, $beforeCellAddress, $numberOfColumns, $beforeRow, $numberOfRows); 507 | 508 | // Update worksheet: page breaks 509 | $this->adjustPageBreaks($worksheet, $beforeCellAddress, $beforeColumn, $numberOfColumns, $beforeRow, $numberOfRows); 510 | 511 | // Update worksheet: comments 512 | $this->adjustComments($worksheet, $beforeCellAddress, $beforeColumn, $numberOfColumns, $beforeRow, $numberOfRows); 513 | 514 | // Update worksheet: hyperlinks 515 | $this->adjustHyperlinks($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); 516 | 517 | // Update worksheet: data validations 518 | $this->adjustDataValidations($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); 519 | 520 | // Update worksheet: merge cells 521 | $this->adjustMergeCells($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); 522 | 523 | // Update worksheet: protected cells 524 | $this->adjustProtectedCells($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); 525 | 526 | // Update worksheet: autofilter 527 | $autoFilter = $worksheet->getAutoFilter(); 528 | $autoFilterRange = $autoFilter->getRange(); 529 | if (!empty($autoFilterRange)) { 530 | if ($numberOfColumns != 0) { 531 | $autoFilterColumns = $autoFilter->getColumns(); 532 | if (count($autoFilterColumns) > 0) { 533 | $column = ''; 534 | $row = 0; 535 | sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row); 536 | $columnIndex = Coordinate::columnIndexFromString($column); 537 | [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange); 538 | if ($columnIndex <= $rangeEnd[0]) { 539 | if ($numberOfColumns < 0) { 540 | // If we're actually deleting any columns that fall within the autofilter range, 541 | // then we delete any rules for those columns 542 | $deleteColumn = $columnIndex + $numberOfColumns - 1; 543 | $deleteCount = abs($numberOfColumns); 544 | for ($i = 1; $i <= $deleteCount; ++$i) { 545 | if (isset($autoFilterColumns[Coordinate::stringFromColumnIndex($deleteColumn + 1)])) { 546 | $autoFilter->clearColumn(Coordinate::stringFromColumnIndex($deleteColumn + 1)); 547 | } 548 | ++$deleteColumn; 549 | } 550 | } 551 | $startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0]; 552 | 553 | // Shuffle columns in autofilter range 554 | if ($numberOfColumns > 0) { 555 | $startColRef = $startCol; 556 | $endColRef = $rangeEnd[0]; 557 | $toColRef = $rangeEnd[0] + $numberOfColumns; 558 | 559 | do { 560 | $autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef)); 561 | --$endColRef; 562 | --$toColRef; 563 | } while ($startColRef <= $endColRef); 564 | } else { 565 | // For delete, we shuffle from beginning to end to avoid overwriting 566 | $startColID = Coordinate::stringFromColumnIndex($startCol); 567 | $toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns); 568 | $endColID = Coordinate::stringFromColumnIndex($rangeEnd[0] + 1); 569 | do { 570 | $autoFilter->shiftColumn($startColID, $toColID); 571 | ++$startColID; 572 | ++$toColID; 573 | } while ($startColID != $endColID); 574 | } 575 | } 576 | } 577 | } 578 | $worksheet->setAutoFilter($this->updateCellReference($autoFilterRange, $beforeCellAddress, $numberOfColumns, $numberOfRows)); 579 | } 580 | 581 | // Update worksheet: freeze pane 582 | if ($worksheet->getFreezePane()) { 583 | $splitCell = $worksheet->getFreezePane() ?? ''; 584 | $topLeftCell = $worksheet->getTopLeftCell() ?? ''; 585 | 586 | $splitCell = $this->updateCellReference($splitCell, $beforeCellAddress, $numberOfColumns, $numberOfRows); 587 | $topLeftCell = $this->updateCellReference($topLeftCell, $beforeCellAddress, $numberOfColumns, $numberOfRows); 588 | 589 | $worksheet->freezePane($splitCell, $topLeftCell); 590 | } 591 | 592 | // Page setup 593 | if ($worksheet->getPageSetup()->isPrintAreaSet()) { 594 | $worksheet->getPageSetup()->setPrintArea($this->updateCellReference($worksheet->getPageSetup()->getPrintArea(), $beforeCellAddress, $numberOfColumns, $numberOfRows)); 595 | } 596 | 597 | // Update worksheet: drawings 598 | $aDrawings = $worksheet->getDrawingCollection(); 599 | foreach ($aDrawings as $objDrawing) { 600 | $newReference = $this->updateCellReference($objDrawing->getCoordinates(), $beforeCellAddress, $numberOfColumns, $numberOfRows); 601 | if ($objDrawing->getCoordinates() != $newReference) { 602 | $objDrawing->setCoordinates($newReference); 603 | } 604 | } 605 | 606 | // Update workbook: define names 607 | if (count($worksheet->getParent()->getDefinedNames()) > 0) { 608 | foreach ($worksheet->getParent()->getDefinedNames() as $definedName) { 609 | if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) { 610 | $definedName->setValue($this->updateCellReference($definedName->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows)); 611 | } 612 | } 613 | } 614 | 615 | // Garbage collect 616 | $worksheet->garbageCollect(); 617 | } 618 | 619 | /** 620 | * Update references within formulas. 621 | * 622 | * @param string $formula Formula to update 623 | * @param string $beforeCellAddress Insert before this one 624 | * @param int $numberOfColumns Number of columns to insert 625 | * @param int $numberOfRows Number of rows to insert 626 | * @param string $worksheetName Worksheet name/title 627 | * 628 | * @return string Updated formula 629 | */ 630 | public function updateFormulaReferences($formula = '', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0, $worksheetName = '') 631 | { 632 | // Update cell references in the formula 633 | $formulaBlocks = explode('"', $formula); 634 | $i = false; 635 | foreach ($formulaBlocks as &$formulaBlock) { 636 | // Ignore blocks that were enclosed in quotes (alternating entries in the $formulaBlocks array after the explode) 637 | if ($i = !$i) { 638 | $adjustCount = 0; 639 | $newCellTokens = $cellTokens = []; 640 | // Search for row ranges (e.g. 'Sheet1'!3:5 or 3:5) with or without $ absolutes (e.g. $3:5) 641 | $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); 642 | if ($matchCount > 0) { 643 | foreach ($matches as $match) { 644 | $fromString = ($match[2] > '') ? $match[2] . '!' : ''; 645 | $fromString .= $match[3] . ':' . $match[4]; 646 | $modified3 = substr($this->updateCellReference('$A' . $match[3], $beforeCellAddress, $numberOfColumns, $numberOfRows), 2); 647 | $modified4 = substr($this->updateCellReference('$A' . $match[4], $beforeCellAddress, $numberOfColumns, $numberOfRows), 2); 648 | 649 | if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { 650 | if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { 651 | $toString = ($match[2] > '') ? $match[2] . '!' : ''; 652 | $toString .= $modified3 . ':' . $modified4; 653 | // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more 654 | $column = 100000; 655 | $row = 10000000 + (int) trim($match[3], '$'); 656 | $cellIndex = $column . $row; 657 | 658 | $newCellTokens[$cellIndex] = preg_quote($toString, '/'); 659 | $cellTokens[$cellIndex] = '/(? 0) { 668 | foreach ($matches as $match) { 669 | $fromString = ($match[2] > '') ? $match[2] . '!' : ''; 670 | $fromString .= $match[3] . ':' . $match[4]; 671 | $modified3 = substr($this->updateCellReference($match[3] . '$1', $beforeCellAddress, $numberOfColumns, $numberOfRows), 0, -2); 672 | $modified4 = substr($this->updateCellReference($match[4] . '$1', $beforeCellAddress, $numberOfColumns, $numberOfRows), 0, -2); 673 | 674 | if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { 675 | if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { 676 | $toString = ($match[2] > '') ? $match[2] . '!' : ''; 677 | $toString .= $modified3 . ':' . $modified4; 678 | // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more 679 | $column = Coordinate::columnIndexFromString(trim($match[3], '$')) + 100000; 680 | $row = 10000000; 681 | $cellIndex = $column . $row; 682 | 683 | $newCellTokens[$cellIndex] = preg_quote($toString, '/'); 684 | $cellTokens[$cellIndex] = '/(? 0) { 693 | foreach ($matches as $match) { 694 | $fromString = ($match[2] > '') ? $match[2] . '!' : ''; 695 | $fromString .= $match[3] . ':' . $match[4]; 696 | $modified3 = $this->updateCellReference($match[3], $beforeCellAddress, $numberOfColumns, $numberOfRows); 697 | $modified4 = $this->updateCellReference($match[4], $beforeCellAddress, $numberOfColumns, $numberOfRows); 698 | 699 | if ($match[3] . $match[4] !== $modified3 . $modified4) { 700 | if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { 701 | $toString = ($match[2] > '') ? $match[2] . '!' : ''; 702 | $toString .= $modified3 . ':' . $modified4; 703 | [$column, $row] = Coordinate::coordinateFromString($match[3]); 704 | // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more 705 | $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000; 706 | $row = (int) trim($row, '$') + 10000000; 707 | $cellIndex = $column . $row; 708 | 709 | $newCellTokens[$cellIndex] = preg_quote($toString, '/'); 710 | $cellTokens[$cellIndex] = '/(? 0) { 720 | foreach ($matches as $match) { 721 | $fromString = ($match[2] > '') ? $match[2] . '!' : ''; 722 | $fromString .= $match[3]; 723 | 724 | $modified3 = $this->updateCellReference($match[3], $beforeCellAddress, $numberOfColumns, $numberOfRows); 725 | if ($match[3] !== $modified3) { 726 | if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { 727 | $toString = ($match[2] > '') ? $match[2] . '!' : ''; 728 | $toString .= $modified3; 729 | [$column, $row] = Coordinate::coordinateFromString($match[3]); 730 | $columnAdditionalIndex = $column[0] === '$' ? 1 : 0; 731 | $rowAdditionalIndex = $row[0] === '$' ? 1 : 0; 732 | // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more 733 | $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000; 734 | $row = (int) trim($row, '$') + 10000000; 735 | $cellIndex = $row . $rowAdditionalIndex . $column . $columnAdditionalIndex; 736 | 737 | $newCellTokens[$cellIndex] = preg_quote($toString, '/'); 738 | $cellTokens[$cellIndex] = '/(? 0) { 745 | if ($numberOfColumns > 0 || $numberOfRows > 0) { 746 | krsort($cellTokens); 747 | krsort($newCellTokens); 748 | } else { 749 | ksort($cellTokens); 750 | ksort($newCellTokens); 751 | } // Update cell references in the formula 752 | $formulaBlock = str_replace('\\', '', preg_replace($cellTokens, $newCellTokens, $formulaBlock)); 753 | } 754 | } 755 | } 756 | unset($formulaBlock); 757 | 758 | // Then rebuild the formula string 759 | return implode('"', $formulaBlocks); 760 | } 761 | 762 | /** 763 | * Update all cell references within a formula, irrespective of worksheet. 764 | */ 765 | public function updateFormulaReferencesAnyWorksheet(string $formula = '', int $numberOfColumns = 0, int $numberOfRows = 0): string 766 | { 767 | $formula = $this->updateCellReferencesAllWorksheets($formula, $numberOfColumns, $numberOfRows); 768 | 769 | if ($numberOfColumns !== 0) { 770 | $formula = $this->updateColumnRangesAllWorksheets($formula, $numberOfColumns); 771 | } 772 | 773 | if ($numberOfRows !== 0) { 774 | $formula = $this->updateRowRangesAllWorksheets($formula, $numberOfRows); 775 | } 776 | 777 | return $formula; 778 | } 779 | 780 | private function updateCellReferencesAllWorksheets(string $formula, int $numberOfColumns, int $numberOfRows): string 781 | { 782 | $splitCount = preg_match_all( 783 | '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui', 784 | $formula, 785 | $splitRanges, 786 | PREG_OFFSET_CAPTURE 787 | ); 788 | 789 | $columnLengths = array_map('strlen', array_column($splitRanges[6], 0)); 790 | $rowLengths = array_map('strlen', array_column($splitRanges[7], 0)); 791 | $columnOffsets = array_column($splitRanges[6], 1); 792 | $rowOffsets = array_column($splitRanges[7], 1); 793 | 794 | $columns = $splitRanges[6]; 795 | $rows = $splitRanges[7]; 796 | 797 | while ($splitCount > 0) { 798 | --$splitCount; 799 | $columnLength = $columnLengths[$splitCount]; 800 | $rowLength = $rowLengths[$splitCount]; 801 | $columnOffset = $columnOffsets[$splitCount]; 802 | $rowOffset = $rowOffsets[$splitCount]; 803 | $column = $columns[$splitCount][0]; 804 | $row = $rows[$splitCount][0]; 805 | 806 | if (!empty($column) && $column[0] !== '$') { 807 | $column = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($column) + $numberOfColumns); 808 | $formula = substr($formula, 0, $columnOffset) . $column . substr($formula, $columnOffset + $columnLength); 809 | } 810 | if (!empty($row) && $row[0] !== '$') { 811 | $row += $numberOfRows; 812 | $formula = substr($formula, 0, $rowOffset) . $row . substr($formula, $rowOffset + $rowLength); 813 | } 814 | } 815 | 816 | return $formula; 817 | } 818 | 819 | private function updateColumnRangesAllWorksheets(string $formula, int $numberOfColumns): string 820 | { 821 | $splitCount = preg_match_all( 822 | '/' . Calculation::CALCULATION_REGEXP_COLUMNRANGE_RELATIVE . '/mui', 823 | $formula, 824 | $splitRanges, 825 | PREG_OFFSET_CAPTURE 826 | ); 827 | 828 | $fromColumnLengths = array_map('strlen', array_column($splitRanges[1], 0)); 829 | $fromColumnOffsets = array_column($splitRanges[1], 1); 830 | $toColumnLengths = array_map('strlen', array_column($splitRanges[2], 0)); 831 | $toColumnOffsets = array_column($splitRanges[2], 1); 832 | 833 | $fromColumns = $splitRanges[1]; 834 | $toColumns = $splitRanges[2]; 835 | 836 | while ($splitCount > 0) { 837 | --$splitCount; 838 | $fromColumnLength = $fromColumnLengths[$splitCount]; 839 | $toColumnLength = $toColumnLengths[$splitCount]; 840 | $fromColumnOffset = $fromColumnOffsets[$splitCount]; 841 | $toColumnOffset = $toColumnOffsets[$splitCount]; 842 | $fromColumn = $fromColumns[$splitCount][0]; 843 | $toColumn = $toColumns[$splitCount][0]; 844 | 845 | if (!empty($fromColumn) && $fromColumn[0] !== '$') { 846 | $fromColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($fromColumn) + $numberOfColumns); 847 | $formula = substr($formula, 0, $fromColumnOffset) . $fromColumn . substr($formula, $fromColumnOffset + $fromColumnLength); 848 | } 849 | if (!empty($toColumn) && $toColumn[0] !== '$') { 850 | $toColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($toColumn) + $numberOfColumns); 851 | $formula = substr($formula, 0, $toColumnOffset) . $toColumn . substr($formula, $toColumnOffset + $toColumnLength); 852 | } 853 | } 854 | 855 | return $formula; 856 | } 857 | 858 | private function updateRowRangesAllWorksheets(string $formula, int $numberOfRows): string 859 | { 860 | $splitCount = preg_match_all( 861 | '/' . Calculation::CALCULATION_REGEXP_ROWRANGE_RELATIVE . '/mui', 862 | $formula, 863 | $splitRanges, 864 | PREG_OFFSET_CAPTURE 865 | ); 866 | 867 | $fromRowLengths = array_map('strlen', array_column($splitRanges[1], 0)); 868 | $fromRowOffsets = array_column($splitRanges[1], 1); 869 | $toRowLengths = array_map('strlen', array_column($splitRanges[2], 0)); 870 | $toRowOffsets = array_column($splitRanges[2], 1); 871 | 872 | $fromRows = $splitRanges[1]; 873 | $toRows = $splitRanges[2]; 874 | 875 | while ($splitCount > 0) { 876 | --$splitCount; 877 | $fromRowLength = $fromRowLengths[$splitCount]; 878 | $toRowLength = $toRowLengths[$splitCount]; 879 | $fromRowOffset = $fromRowOffsets[$splitCount]; 880 | $toRowOffset = $toRowOffsets[$splitCount]; 881 | $fromRow = $fromRows[$splitCount][0]; 882 | $toRow = $toRows[$splitCount][0]; 883 | 884 | if (!empty($fromRow) && $fromRow[0] !== '$') { 885 | $fromRow += $numberOfRows; 886 | $formula = substr($formula, 0, $fromRowOffset) . $fromRow . substr($formula, $fromRowOffset + $fromRowLength); 887 | } 888 | if (!empty($toRow) && $toRow[0] !== '$') { 889 | $toRow += $numberOfRows; 890 | $formula = substr($formula, 0, $toRowOffset) . $toRow . substr($formula, $toRowOffset + $toRowLength); 891 | } 892 | } 893 | 894 | return $formula; 895 | } 896 | 897 | /** 898 | * Update cell reference. 899 | * 900 | * @param string $cellReference Cell address or range of addresses 901 | * @param string $beforeCellAddress Insert before this one 902 | * @param int $numberOfColumns Number of columns to increment 903 | * @param int $numberOfRows Number of rows to increment 904 | * 905 | * @return string Updated cell range 906 | */ 907 | public function updateCellReference($cellReference = 'A1', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0) 908 | { 909 | // Is it in another worksheet? Will not have to update anything. 910 | if (strpos($cellReference, '!') !== false) { 911 | return $cellReference; 912 | // Is it a range or a single cell? 913 | } elseif (!Coordinate::coordinateIsRange($cellReference)) { 914 | // Single cell 915 | return $this->updateSingleCellReference($cellReference, $beforeCellAddress, $numberOfColumns, $numberOfRows); 916 | } elseif (Coordinate::coordinateIsRange($cellReference)) { 917 | // Range 918 | return $this->updateCellRange($cellReference, $beforeCellAddress, $numberOfColumns, $numberOfRows); 919 | } 920 | 921 | // Return original 922 | return $cellReference; 923 | } 924 | 925 | /** 926 | * Update named formulas (i.e. containing worksheet references / named ranges). 927 | * 928 | * @param Spreadsheet $spreadsheet Object to update 929 | * @param string $oldName Old name (name to replace) 930 | * @param string $newName New name 931 | */ 932 | public function updateNamedFormulas(Spreadsheet $spreadsheet, $oldName = '', $newName = ''): void 933 | { 934 | if ($oldName == '') { 935 | return; 936 | } 937 | 938 | foreach ($spreadsheet->getWorksheetIterator() as $sheet) { 939 | foreach ($sheet->getCoordinates(false) as $coordinate) { 940 | $cell = $sheet->getCell($coordinate); 941 | if (($cell !== null) && ($cell->getDataType() == DataType::TYPE_FORMULA)) { 942 | $formula = $cell->getValue(); 943 | if (strpos($formula, $oldName) !== false) { 944 | $formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula); 945 | $formula = str_replace($oldName . '!', $newName . '!', $formula); 946 | $cell->setValueExplicit($formula, DataType::TYPE_FORMULA); 947 | } 948 | } 949 | } 950 | } 951 | } 952 | 953 | /** 954 | * Update cell range. 955 | * 956 | * @param string $cellRange Cell range (e.g. 'B2:D4', 'B:C' or '2:3') 957 | * @param string $beforeCellAddress Insert before this one 958 | * @param int $numberOfColumns Number of columns to increment 959 | * @param int $numberOfRows Number of rows to increment 960 | * 961 | * @return string Updated cell range 962 | */ 963 | private function updateCellRange($cellRange = 'A1:A1', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0) 964 | { 965 | if (!Coordinate::coordinateIsRange($cellRange)) { 966 | throw new Exception('Only cell ranges may be passed to this method.'); 967 | } 968 | 969 | // Update range 970 | $range = Coordinate::splitRange($cellRange); 971 | $ic = count($range); 972 | for ($i = 0; $i < $ic; ++$i) { 973 | $jc = count($range[$i]); 974 | for ($j = 0; $j < $jc; ++$j) { 975 | if (ctype_alpha($range[$i][$j])) { 976 | $r = Coordinate::coordinateFromString($this->updateSingleCellReference($range[$i][$j] . '1', $beforeCellAddress, $numberOfColumns, $numberOfRows)); 977 | $range[$i][$j] = $r[0]; 978 | } elseif (ctype_digit($range[$i][$j])) { 979 | $r = Coordinate::coordinateFromString($this->updateSingleCellReference('A' . $range[$i][$j], $beforeCellAddress, $numberOfColumns, $numberOfRows)); 980 | $range[$i][$j] = $r[1]; 981 | } else { 982 | $range[$i][$j] = $this->updateSingleCellReference($range[$i][$j], $beforeCellAddress, $numberOfColumns, $numberOfRows); 983 | } 984 | } 985 | } 986 | 987 | // Recreate range string 988 | return Coordinate::buildRange($range); 989 | } 990 | 991 | /** 992 | * Update single cell reference. 993 | * 994 | * @param string $cellReference Single cell reference 995 | * @param string $beforeCellAddress Insert before this one 996 | * @param int $numberOfColumns Number of columns to increment 997 | * @param int $numberOfRows Number of rows to increment 998 | * 999 | * @return string Updated cell reference 1000 | */ 1001 | private function updateSingleCellReference($cellReference = 'A1', $beforeCellAddress = 'A1', $numberOfColumns = 0, $numberOfRows = 0) 1002 | { 1003 | if (Coordinate::coordinateIsRange($cellReference)) { 1004 | throw new Exception('Only single cell references may be passed to this method.'); 1005 | } 1006 | 1007 | // Get coordinate of $beforeCellAddress 1008 | [$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($beforeCellAddress); 1009 | 1010 | // Get coordinate of $cellReference 1011 | [$newColumn, $newRow] = Coordinate::coordinateFromString($cellReference); 1012 | 1013 | // Verify which parts should be updated 1014 | $updateColumn = (($newColumn[0] != '$') && ($beforeColumn[0] != '$') && (Coordinate::columnIndexFromString($newColumn) >= Coordinate::columnIndexFromString($beforeColumn))); 1015 | $updateRow = (($newRow[0] != '$') && ($beforeRow[0] != '$') && $newRow >= $beforeRow); 1016 | 1017 | // Create new column reference 1018 | if ($updateColumn) { 1019 | $newColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($newColumn) + $numberOfColumns); 1020 | } 1021 | 1022 | // Create new row reference 1023 | if ($updateRow) { 1024 | $newRow = (int) $newRow + $numberOfRows; 1025 | } 1026 | 1027 | // Return new reference 1028 | return $newColumn . $newRow; 1029 | } 1030 | 1031 | /** 1032 | * __clone implementation. Cloning should not be allowed in a Singleton! 1033 | */ 1034 | final public function __clone() 1035 | { 1036 | throw new Exception('Cloning a Singleton is not allowed!'); 1037 | } 1038 | } 1039 | -------------------------------------------------------------------------------- /src/params/CallbackParam.php: -------------------------------------------------------------------------------- 1 | value[$row_index][$col_index] 30 | */ 31 | public int $row_index; 32 | 33 | /** 34 | * Current subindex of the parameter. Scheme: ICellSetter->value[$row_index][$col_index] 35 | */ 36 | public int $col_index; 37 | 38 | public function __construct(array $params) 39 | { 40 | $fields = ['sheet', 'coordinate', 'param', 'tpl_var_name', 'row_index', 'col_index']; 41 | foreach($fields as $field) { 42 | if ( ! array_key_exists($field, $params)) { 43 | throw new RuntimeException('In the constructor of '.__CLASS__.' the parameter '.$field.' was not specified.'); 44 | } 45 | $this->$field = $params[$field]; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/params/ExcelParam.php: -------------------------------------------------------------------------------- 1 | setterClass = $setterClass; 30 | $this->value = $value; 31 | $this->callback = $callback; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/params/SetterParam.php: -------------------------------------------------------------------------------- 1 | $field = $params[$field]; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/setters/CellSetterArray2DValue.php: -------------------------------------------------------------------------------- 1 | sheet; 23 | $rowKey = $setterParam->rowKey; 24 | $colKey = $setterParam->colKey; 25 | $tplVarName = $setterParam->tplVarName; 26 | $param = $setterParam->params[$tplVarName]; 27 | if (!$this->_validateValue($param->value)) { 28 | return $insertedCells; 29 | } 30 | 31 | $pColumn = $insertedCells->getCurrentColIndex($rowKey, $colKey); 32 | $pRow = $insertedCells->getCurrentRowIndex($rowKey, $colKey); 33 | $values = $param->value; 34 | $this->_insertNewRowsAndColsIfNeed($sheet, $values, $pColumn, $pRow); 35 | 36 | foreach ($values as $rowIndex => $value_arr) { 37 | foreach ($value_arr as $colIndex => $value) { 38 | $pColumnWord = Coordinate::stringFromColumnIndex($pColumn + $colIndex); 39 | $currCellCoordinates = $pColumnWord . ($pRow + $rowIndex); 40 | 41 | $sheet->setCellValue($currCellCoordinates, $value); 42 | if ($param->callback) { 43 | $callbackParam = new CallbackParam([ 44 | 'sheet' => $sheet, 45 | 'coordinate' => $currCellCoordinates, 46 | 'param' => $param->value, 47 | 'tpl_var_name' => $tplVarName, 48 | 'row_index' => $rowIndex, 49 | 'col_index' => $colIndex, 50 | ]); 51 | call_user_func($param->callback, $callbackParam); 52 | } 53 | } 54 | } 55 | 56 | $firstValue = reset($values); 57 | $insertedCells->addInsertedCols($rowKey, $colKey, count($firstValue) - 1); 58 | $insertedCells->addInsertedRows($rowKey, $colKey, count($values) - 1); 59 | 60 | return $insertedCells; 61 | } 62 | 63 | private function _validateValue(array $value): bool 64 | { 65 | foreach ($value as $key => $val) { 66 | if (!is_array($val)) { 67 | throw new RuntimeException('In the ' . ExcelParam::class . ' class the field "value" with index "' . $key . '" must be an array, when the setter ' . __CLASS__ . ' is used.'); 68 | } 69 | } 70 | return count($value) > 0; 71 | } 72 | 73 | /** 74 | * @param Worksheet $sheet 75 | * @param array $values 76 | * @param integer $pCol The current column index 77 | * @param integer $pRow 78 | */ 79 | private function _insertNewRowsAndColsIfNeed(Worksheet $sheet, array $values, int $pCol, int $pRow): void 80 | { 81 | $objReferenceHelper = ReferenceHelper::getInstance(); 82 | 83 | foreach ($values as $rowIndex => $valueArr) { 84 | $rowIndex = (int)$rowIndex; 85 | $colsToInsert = count($valueArr) - 1; 86 | //$highestColumn = Coordinate::columnIndexFromString($sheet->getHighestColumn()); 87 | 88 | // Inserting row 89 | if ($rowIndex > 0) { 90 | $rowsToInsert = 1; 91 | $pCol1 = Coordinate::stringFromColumnIndex($pCol); 92 | $pCol2 = Coordinate::stringFromColumnIndex($pCol); 93 | $coordinate1 = $pCol1 . ($pRow + $rowIndex); 94 | $coordinate2 = $pCol2 . ($sheet->getHighestRow() + $rowsToInsert); 95 | $objReferenceHelper->insertNewBefore($coordinate1, 0, $rowsToInsert, $sheet, $coordinate2); 96 | //echo 'Row '.$coordinate1.' '.$coordinate2."\n"; 97 | } 98 | 99 | // Inserting columns 100 | $pCol_1 = Coordinate::stringFromColumnIndex($pCol + 1); 101 | $pCol_2 = Coordinate::stringFromColumnIndex($pCol + $colsToInsert); 102 | $coordinate1 = $pCol_1 . ($pRow + $rowIndex); 103 | $coordinate2 = $pCol_2 . ($pRow + $rowIndex); 104 | $objReferenceHelper->insertNewBefore($coordinate1, $colsToInsert, 0, $sheet, $coordinate2); 105 | //echo 'Column '.$coordinate1.' '.$coordinate2.' '.$colsToInsert."\n"; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/setters/CellSetterArrayValue.php: -------------------------------------------------------------------------------- 1 | sheet; 21 | $rowKey = $setterParam->rowKey; 22 | $colKey = $setterParam->colKey; 23 | $tplVarName = $setterParam->tplVarName; 24 | $param = $setterParam->params[$tplVarName]; 25 | if (!$this->_validateValue($param->value)) { 26 | return $insertedCells; 27 | } 28 | 29 | $pColumn = $insertedCells->getCurrentCol($rowKey, $colKey); 30 | $pColumnIndex = $insertedCells->getCurrentColIndex($rowKey, $colKey); 31 | $pRow = $insertedCells->getCurrentRowIndex($rowKey, $colKey); 32 | $values = $param->value; 33 | $this->_insertNewRowsIfNeed($sheet, $values, $pColumnIndex, $pRow); 34 | 35 | foreach ($values as $rowIndex => $value) { 36 | $currCellCoordinates = $pColumn . ($pRow + $rowIndex); 37 | 38 | $sheet->setCellValue($currCellCoordinates, $value); 39 | if ($param->callback) { 40 | $callbackParam = new CallbackParam([ 41 | 'sheet' => $sheet, 42 | 'coordinate' => $currCellCoordinates, 43 | 'param' => $param->value, 44 | 'tpl_var_name' => $tplVarName, 45 | 'row_index' => $rowIndex, 46 | 'col_index' => 0, 47 | ]); 48 | call_user_func($param->callback, $callbackParam); 49 | } 50 | } 51 | 52 | $insertedCells->addInsertedRows($rowKey, $colKey, count($values) - 1); 53 | 54 | return $insertedCells; 55 | } 56 | 57 | private function _validateValue(array $value): bool 58 | { 59 | return count($value) > 0; 60 | } 61 | 62 | /** 63 | * @param Worksheet $sheet 64 | * @param String[] $values 65 | * @param integer $pColumnIndex The current column index 66 | * @param integer $pRow The current row index 67 | */ 68 | private function _insertNewRowsIfNeed(Worksheet $sheet, array $values, int $pColumnIndex, int $pRow): void 69 | { 70 | $rowsToInsert = count($values) - 1; 71 | if ($rowsToInsert > 0) { 72 | $objReferenceHelper = ReferenceHelper::getInstance(); 73 | $pCol = Coordinate::stringFromColumnIndex($pColumnIndex); 74 | $coordinate1 = $pCol . ($pRow + 1); 75 | $coordinate2 = $pCol . ($sheet->getHighestRow() + $rowsToInsert); 76 | $objReferenceHelper->insertNewBefore($coordinate1, 0, $rowsToInsert, $sheet, $coordinate2); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/setters/CellSetterArrayValueSpecial.php: -------------------------------------------------------------------------------- 1 | sheet; 20 | $rowKey = $setterParam->rowKey; 21 | $colKey = $setterParam->colKey; 22 | $tplVarName = $setterParam->tplVarName; 23 | $param = $setterParam->params[$tplVarName]; 24 | if (!$this->_validateValue($param->value)) { 25 | return $insertedCells; 26 | } 27 | 28 | $pColumn = $insertedCells->getCurrentCol($rowKey, $colKey); 29 | $pColumnIndex = $insertedCells->getCurrentColIndex($rowKey, $colKey); 30 | $pRow = $insertedCells->getCurrentRowIndex($rowKey, $colKey); 31 | $values = $param->value; 32 | $this->_insertNewRowsIfNeed($sheet, $values, $insertedCells, $rowKey, $pColumnIndex, $pRow); 33 | 34 | foreach ($values as $row_index => $value) { 35 | $currCellCoordinates = $pColumn . ($pRow + $row_index); 36 | 37 | $sheet->setCellValue($currCellCoordinates, $value); 38 | if ($param->callback) { 39 | $callbackParam = new CallbackParam([ 40 | 'sheet' => $sheet, 41 | 'coordinate' => $currCellCoordinates, 42 | 'param' => $param->value, 43 | 'tpl_var_name' => $tplVarName, 44 | 'row_index' => $row_index, 45 | 'col_index' => 0, 46 | ]); 47 | call_user_func($param->callback, $callbackParam); 48 | } 49 | } 50 | 51 | $maxInsertedRows = $this->_getMaxInsertedRows($rowKey, $insertedCells); 52 | $maxInsertedRows = (count($values) - 1 > $maxInsertedRows) ? count($values) - 1 : $maxInsertedRows; 53 | $maxColumnIndex = Coordinate::columnIndexFromString($sheet->getHighestColumn()); 54 | for ($i = 0; $i <= $maxColumnIndex; $i++) { 55 | $insertedCells->setInsertedRows($rowKey, $i, $maxInsertedRows); 56 | } 57 | 58 | return $insertedCells; 59 | } 60 | 61 | private function _validateValue(array $value): bool 62 | { 63 | return count($value) > 0; 64 | } 65 | 66 | /** 67 | * @param Worksheet $sheet 68 | * @param String[] $values 69 | * @param InsertedCells $insertedCells 70 | * @param integer $row_key The row index, where was template variable 71 | * @param integer $pColumnIndex The current column index 72 | * @param integer $pRow The current row index 73 | * 74 | * @throws SpreadsheetException 75 | */ 76 | private function _insertNewRowsIfNeed(Worksheet $sheet, array $values, InsertedCells $insertedCells, int $row_key, int $pColumnIndex, int $pRow): void 77 | { 78 | $maxInsertedRows = $this->_getMaxInsertedRows($row_key, $insertedCells); 79 | $rowsToInsert = (count($values) - 1 > $maxInsertedRows) ? count($values) - 1 : 0; 80 | $maxInsertedRows = ($maxInsertedRows > $rowsToInsert) ? $maxInsertedRows : $rowsToInsert; 81 | if ($rowsToInsert > 0) { 82 | $sheet->insertNewRowBefore($pRow + 1, $rowsToInsert); 83 | } 84 | $this->_mergeColumnsIfNeed($sheet, $pColumnIndex, $pRow, $maxInsertedRows); 85 | } 86 | 87 | /** 88 | * @param InsertedCells $insertedCells 89 | * @param integer $rowKey The row index, where was template variable 90 | * 91 | * @return integer Maximum number of inserted rows in all columns of the specified row 92 | */ 93 | private function _getMaxInsertedRows(int $rowKey, InsertedCells $insertedCells): int 94 | { 95 | $maxInsertedRows = 0; 96 | foreach ($insertedCells->inserted_rows as $col_key => $insertedRowsInCol) { 97 | $insertedRows = $insertedCells->getInsertedRows($rowKey, $col_key); 98 | if ($insertedRows > $maxInsertedRows) { 99 | $maxInsertedRows = $insertedRows; 100 | } 101 | } 102 | 103 | return $maxInsertedRows; 104 | } 105 | 106 | /** 107 | * @param Worksheet $sheet 108 | * @param integer $pColumnIndex Current column index 109 | * @param integer $pRow Current row index 110 | * @param integer $maxInsertedRows Maximum number of inserted rows in the current row 111 | * 112 | * @throws SpreadsheetException 113 | */ 114 | private function _mergeColumnsIfNeed(Worksheet $sheet, int $pColumnIndex, int $pRow, int $maxInsertedRows): void 115 | { 116 | $pCol = Coordinate::stringFromColumnIndex($pColumnIndex); 117 | $coordinate = $pCol . $pRow; 118 | $mergedCellsCount = $this->_getMergedCellsCount($sheet, $coordinate); 119 | if ($mergedCellsCount > 0) { 120 | for ($rowOffset = 0; $rowOffset < $maxInsertedRows; $rowOffset++) { 121 | $rowIndex = $pRow + 1 + $rowOffset; 122 | $coordinate1 = $pCol . $rowIndex; 123 | $pCol2 = Coordinate::stringFromColumnIndex($pColumnIndex + $mergedCellsCount); 124 | $coordinate2 = $pCol2 . $rowIndex; 125 | $mergeRange = Coordinate::buildRange([[$coordinate1, $coordinate2]]); 126 | $sheet->mergeCells($mergeRange); 127 | } 128 | } 129 | } 130 | 131 | /** 132 | * @param Worksheet $sheet 133 | * @param string $coordinate Current cell coordinate 134 | * 135 | * @return integer Number of merged cells in the specified cell coordinate 136 | * @throws SpreadsheetException 137 | */ 138 | private function _getMergedCellsCount(Worksheet $sheet, string $coordinate): int 139 | { 140 | $mergedCellsCount = 0; 141 | $cell = $sheet->getCell($coordinate); 142 | foreach ($sheet->getMergeCells() as $cells) { 143 | if ($cell->isInRange($cells)) { 144 | $cellsRangeArr = Coordinate::splitRange($cells); 145 | $cellsArr = $cellsRangeArr[0]; 146 | $coord1 = Coordinate::coordinateFromString($cellsArr[0]); 147 | $coord2 = Coordinate::coordinateFromString($cellsArr[1]); 148 | $colIndex1 = Coordinate::columnIndexFromString($coord1[0]); 149 | $colIndex2 = Coordinate::columnIndexFromString($coord2[0]); 150 | $mergedCellsCount = abs($colIndex2 - $colIndex1); 151 | } 152 | } 153 | 154 | return $mergedCellsCount; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/setters/CellSetterStringValue.php: -------------------------------------------------------------------------------- 1 | sheet; 16 | $rowKey = $setterParam->rowKey; 17 | $colKey = $setterParam->colKey; 18 | $currentColContent = $setterParam->colContent; 19 | $tplVarName = $setterParam->tplVarName; 20 | $param = $setterParam->params[$tplVarName]; 21 | if (!$this->_validateValue($param->value)) { 22 | return $insertedCells; 23 | } 24 | 25 | $coordinate = $insertedCells->getCurrentCellCoordinate($rowKey, $colKey); 26 | $col_value = strtr($currentColContent, [$tplVarName => $param->value]); 27 | $sheet->setCellValue($coordinate, $col_value); 28 | if ($param->callback) { 29 | $callbackParam = new CallbackParam([ 30 | 'sheet' => $sheet, 31 | 'coordinate' => $coordinate, 32 | 'param' => $param->value, 33 | 'tpl_var_name' => $tplVarName, 34 | 'row_index' => 0, 35 | 'col_index' => 0, 36 | ]); 37 | call_user_func($param->callback, $callbackParam); 38 | } 39 | 40 | return $insertedCells; 41 | } 42 | 43 | /** 44 | * @param mixed $value 45 | * 46 | * @return bool 47 | */ 48 | private function _validateValue($value): bool 49 | { 50 | if (is_array($value)) { 51 | throw new RuntimeException('In the ' . ExcelParam::class . ' class the field "value" must be an array, when the setter ' . __CLASS__ . ' is used.'); 52 | } 53 | return true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/setters/ICellSetter.php: -------------------------------------------------------------------------------- 1 |