You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

958 lines
33 KiB

  1. <?php
  2. namespace iron\widgets;
  3. use yii\helpers\ArrayHelper;
  4. use yii\base\InvalidConfigException;
  5. use yii\base\InvalidParamException;
  6. use yii\i18n\Formatter;
  7. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  8. /**
  9. * Excel Widget for generate Excel File or for load Excel File.
  10. *
  11. * Usage
  12. * -----
  13. *
  14. * Exporting data into an excel file.
  15. *
  16. * ~~~
  17. *
  18. * // export data only one worksheet.
  19. *
  20. * \moonland\phpexcel\Excel::widget([
  21. * 'models' => $allModels,
  22. * 'mode' => 'export', //default value as 'export'
  23. * 'columns' => ['column1','column2','column3'],
  24. * //without header working, because the header will be get label from attribute label.
  25. * 'headers' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3'],
  26. * ]);
  27. *
  28. * \moonland\phpexcel\Excel::export([
  29. * 'models' => $allModels,
  30. * 'columns' => ['column1','column2','column3'],
  31. * //without header working, because the header will be get label from attribute label.
  32. * 'headers' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3'],
  33. * ]);
  34. *
  35. * // export data with multiple worksheet.
  36. *
  37. * \moonland\phpexcel\Excel::widget([
  38. * 'isMultipleSheet' => true,
  39. * 'models' => [
  40. * 'sheet1' => $allModels1,
  41. * 'sheet2' => $allModels2,
  42. * 'sheet3' => $allModels3
  43. * ],
  44. * 'mode' => 'export', //default value as 'export'
  45. * 'columns' => [
  46. * 'sheet1' => ['column1','column2','column3'],
  47. * 'sheet2' => ['column1','column2','column3'],
  48. * 'sheet3' => ['column1','column2','column3']
  49. * ],
  50. * //without header working, because the header will be get label from attribute label.
  51. * 'headers' => [
  52. * 'sheet1' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3'],
  53. * 'sheet2' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3'],
  54. * 'sheet3' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3']
  55. * ],
  56. * ]);
  57. *
  58. * \moonland\phpexcel\Excel::export([
  59. * 'isMultipleSheet' => true,
  60. * 'models' => [
  61. * 'sheet1' => $allModels1,
  62. * 'sheet2' => $allModels2,
  63. * 'sheet3' => $allModels3
  64. * ],
  65. * 'columns' => [
  66. * 'sheet1' => ['column1','column2','column3'],
  67. * 'sheet2' => ['column1','column2','column3'],
  68. * 'sheet3' => ['column1','column2','column3']
  69. * ],
  70. * //without header working, because the header will be get label from attribute label.
  71. * 'headers' => [
  72. * 'sheet1' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3'],
  73. * 'sheet2' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3'],
  74. * 'sheet3' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3']
  75. * ],
  76. * ]);
  77. *
  78. * ~~~
  79. *
  80. * New Feature for exporting data, you can use this if you familiar yii gridview.
  81. * That is same with gridview data column.
  82. * Columns in array mode valid params are 'attribute', 'header', 'format', 'value', and footer (TODO).
  83. * Columns in string mode valid layout are 'attribute:format:header:footer(TODO)'.
  84. *
  85. * ~~~
  86. *
  87. * \moonland\phpexcel\Excel::export([
  88. * 'models' => Post::find()->all(),
  89. * 'columns' => [
  90. * 'author.name:text:Author Name',
  91. * [
  92. * 'attribute' => 'content',
  93. * 'header' => 'Content Post',
  94. * 'format' => 'text',
  95. * 'value' => function($model) {
  96. * return ExampleClass::removeText('example', $model->content);
  97. * },
  98. * ],
  99. * 'like_it:text:Reader like this content',
  100. * 'created_at:datetime',
  101. * [
  102. * 'attribute' => 'updated_at',
  103. * 'format' => 'date',
  104. * ],
  105. * ],
  106. * 'headers' => [
  107. * 'created_at' => 'Date Created Content',
  108. * ],
  109. * ]);
  110. *
  111. * ~~~
  112. *
  113. *
  114. * Import file excel and return into an array.
  115. *
  116. * ~~~
  117. *
  118. * $data = \moonland\phpexcel\Excel::import($fileName, $config); // $config is an optional
  119. *
  120. * $data = \moonland\phpexcel\Excel::widget([
  121. * 'mode' => 'import',
  122. * 'fileName' => $fileName,
  123. * 'setFirstRecordAsKeys' => true, // if you want to set the keys of record column with first record, if it not set, the header with use the alphabet column on excel.
  124. * 'setIndexSheetByName' => true, // set this if your excel data with multiple worksheet, the index of array will be set with the sheet name. If this not set, the index will use numeric.
  125. * 'getOnlySheet' => 'sheet1', // you can set this property if you want to get the specified sheet from the excel data with multiple worksheet.
  126. * ]);
  127. *
  128. * $data = \moonland\phpexcel\Excel::import($fileName, [
  129. * 'setFirstRecordAsKeys' => true, // if you want to set the keys of record column with first record, if it not set, the header with use the alphabet column on excel.
  130. * 'setIndexSheetByName' => true, // set this if your excel data with multiple worksheet, the index of array will be set with the sheet name. If this not set, the index will use numeric.
  131. * 'getOnlySheet' => 'sheet1', // you can set this property if you want to get the specified sheet from the excel data with multiple worksheet.
  132. * ]);
  133. *
  134. * // import data with multiple file.
  135. *
  136. * $data = \moonland\phpexcel\Excel::widget([
  137. * 'mode' => 'import',
  138. * 'fileName' => [
  139. * 'file1' => $fileName1,
  140. * 'file2' => $fileName2,
  141. * 'file3' => $fileName3,
  142. * ],
  143. * 'setFirstRecordAsKeys' => true, // if you want to set the keys of record column with first record, if it not set, the header with use the alphabet column on excel.
  144. * 'setIndexSheetByName' => true, // set this if your excel data with multiple worksheet, the index of array will be set with the sheet name. If this not set, the index will use numeric.
  145. * 'getOnlySheet' => 'sheet1', // you can set this property if you want to get the specified sheet from the excel data with multiple worksheet.
  146. * ]);
  147. *
  148. * $data = \moonland\phpexcel\Excel::import([
  149. * 'file1' => $fileName1,
  150. * 'file2' => $fileName2,
  151. * 'file3' => $fileName3,
  152. * ], [
  153. * 'setFirstRecordAsKeys' => true, // if you want to set the keys of record column with first record, if it not set, the header with use the alphabet column on excel.
  154. * 'setIndexSheetByName' => true, // set this if your excel data with multiple worksheet, the index of array will be set with the sheet name. If this not set, the index will use numeric.
  155. * 'getOnlySheet' => 'sheet1', // you can set this property if you want to get the specified sheet from the excel data with multiple worksheet.
  156. * ]);
  157. *
  158. * ~~~
  159. *
  160. * Result example from the code on the top :
  161. *
  162. * ~~~
  163. *
  164. * // only one sheet or specified sheet.
  165. *
  166. * Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
  167. * [1] => Array([name] => Example, [email] => example@moonlandsoft.com, [framework interest] => Yii2));
  168. *
  169. * // data with multiple worksheet
  170. *
  171. * Array([Sheet1] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
  172. * [1] => Array([name] => Example, [email] => example@moonlandsoft.com, [framework interest] => Yii2)),
  173. * [Sheet2] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
  174. * [1] => Array([name] => Example, [email] => example@moonlandsoft.com, [framework interest] => Yii2)));
  175. *
  176. * // data with multiple file and specified sheet or only one worksheet
  177. *
  178. * Array([file1] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
  179. * [1] => Array([name] => Example, [email] => example@moonlandsoft.com, [framework interest] => Yii2)),
  180. * [file2] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
  181. * [1] => Array([name] => Example, [email] => example@moonlandsoft.com, [framework interest] => Yii2)));
  182. *
  183. * // data with multiple file and multiple worksheet
  184. *
  185. * Array([file1] => Array([Sheet1] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
  186. * [1] => Array([name] => Example, [email] => example@moonlandsoft.com, [framework interest] => Yii2)),
  187. * [Sheet2] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
  188. * [1] => Array([name] => Example, [email] => example@moonlandsoft.com, [framework interest] => Yii2))),
  189. * [file2] => Array([Sheet1] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
  190. * [1] => Array([name] => Example, [email] => example@moonlandsoft.com, [framework interest] => Yii2)),
  191. * [Sheet2] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
  192. * [1] => Array([name] => Example, [email] => example@moonlandsoft.com, [framework interest] => Yii2))));
  193. *
  194. * ~~~
  195. *
  196. * @property string $mode is an export mode or import mode. valid value are 'export' and 'import'
  197. * @property boolean $isMultipleSheet for set the export excel with multiple sheet.
  198. * @property array $properties for set property on the excel object.
  199. * @property array $models Model object or DataProvider object with much data.
  200. * @property array $columns to get the attributes from the model, this valid value only the exist attribute on the model.
  201. * If this is not set, then all attribute of the model will be set as columns.
  202. * @property array $headers to set the header column on first line. Set this if want to custom header.
  203. * If not set, the header will get attributes label of model attributes.
  204. * @property string|array $fileName is a name for file name to export or import. Multiple file name only use for import mode, not work if you use the export mode.
  205. * @property string $savePath is a directory to save the file or you can blank this to set the file as attachment.
  206. * @property string $format for excel to export. Valid value are 'Xls','Xlsx','Xml','Ods','Slk','Gnumeric','Csv', and 'Html'.
  207. * @property boolean $setFirstTitle to set the title column on the first line. The columns will have a header on the first line.
  208. * @property boolean $asAttachment to set the file excel to download mode.
  209. * @property boolean $setFirstRecordAsKeys to set the first record on excel file to a keys of array per line.
  210. * If you want to set the keys of record column with first record, if it not set, the header with use the alphabet column on excel.
  211. * @property boolean $setIndexSheetByName to set the sheet index by sheet name or array result if the sheet not only one
  212. * @property string $getOnlySheet is a sheet name to getting the data. This is only get the sheet with same name.
  213. * @property array|Formatter $formatter the formatter used to format model attribute values into displayable texts.
  214. * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]]
  215. * instance. If this property is not set, the "formatter" application component will be used.
  216. *
  217. * @author Moh Khoirul Anam <moh.khoirul.anaam@gmail.com>
  218. * @copyright 2014
  219. * @since 1
  220. */
  221. class Excel extends \yii\base\Widget
  222. {
  223. // Border style
  224. const BORDER_NONE = 'none';
  225. const BORDER_DASHDOT = 'dashDot';
  226. const BORDER_DASHDOTDOT = 'dashDotDot';
  227. const BORDER_DASHED = 'dashed';
  228. const BORDER_DOTTED = 'dotted';
  229. const BORDER_DOUBLE = 'double';
  230. const BORDER_HAIR = 'hair';
  231. const BORDER_MEDIUM = 'medium';
  232. const BORDER_MEDIUMDASHDOT = 'mediumDashDot';
  233. const BORDER_MEDIUMDASHDOTDOT = 'mediumDashDotDot';
  234. const BORDER_MEDIUMDASHED = 'mediumDashed';
  235. const BORDER_SLANTDASHDOT = 'slantDashDot';
  236. const BORDER_THICK = 'thick';
  237. const BORDER_THIN = 'thin';
  238. // Colors
  239. const COLOR_BLACK = 'FF000000';
  240. const COLOR_WHITE = 'FFFFFFFF';
  241. const COLOR_RED = 'FFFF0000';
  242. const COLOR_DARKRED = 'FF800000';
  243. const COLOR_BLUE = 'FF0000FF';
  244. const COLOR_DARKBLUE = 'FF000080';
  245. const COLOR_GREEN = 'FF00FF00';
  246. const COLOR_DARKGREEN = 'FF008000';
  247. const COLOR_YELLOW = 'FFFFFF00';
  248. const COLOR_DARKYELLOW = 'FF808000';
  249. // Horizontal alignment styles
  250. const HORIZONTAL_GENERAL = 'general';
  251. const HORIZONTAL_LEFT = 'left';
  252. const HORIZONTAL_RIGHT = 'right';
  253. const HORIZONTAL_CENTER = 'center';
  254. const HORIZONTAL_CENTER_CONTINUOUS = 'centerContinuous';
  255. const HORIZONTAL_JUSTIFY = 'justify';
  256. const HORIZONTAL_FILL = 'fill';
  257. const HORIZONTAL_DISTRIBUTED = 'distributed'; // Excel2007 only
  258. // Vertical alignment styles
  259. const VERTICAL_BOTTOM = 'bottom';
  260. const VERTICAL_TOP = 'top';
  261. const VERTICAL_CENTER = 'center';
  262. const VERTICAL_JUSTIFY = 'justify';
  263. const VERTICAL_DISTRIBUTED = 'distributed'; // Excel2007 only
  264. // Read order
  265. const READORDER_CONTEXT = 0;
  266. const READORDER_LTR = 1;
  267. const READORDER_RTL = 2;
  268. // Fill types
  269. const FILL_NONE = 'none';
  270. const FILL_SOLID = 'solid';
  271. const FILL_GRADIENT_LINEAR = 'linear';
  272. const FILL_GRADIENT_PATH = 'path';
  273. const FILL_PATTERN_DARKDOWN = 'darkDown';
  274. const FILL_PATTERN_DARKGRAY = 'darkGray';
  275. const FILL_PATTERN_DARKGRID = 'darkGrid';
  276. const FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal';
  277. const FILL_PATTERN_DARKTRELLIS = 'darkTrellis';
  278. const FILL_PATTERN_DARKUP = 'darkUp';
  279. const FILL_PATTERN_DARKVERTICAL = 'darkVertical';
  280. const FILL_PATTERN_GRAY0625 = 'gray0625';
  281. const FILL_PATTERN_GRAY125 = 'gray125';
  282. const FILL_PATTERN_LIGHTDOWN = 'lightDown';
  283. const FILL_PATTERN_LIGHTGRAY = 'lightGray';
  284. const FILL_PATTERN_LIGHTGRID = 'lightGrid';
  285. const FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal';
  286. const FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis';
  287. const FILL_PATTERN_LIGHTUP = 'lightUp';
  288. const FILL_PATTERN_LIGHTVERTICAL = 'lightVertical';
  289. const FILL_PATTERN_MEDIUMGRAY = 'mediumGray';
  290. // Pre-defined formats
  291. const FORMAT_GENERAL = 'General';
  292. const FORMAT_TEXT = '@';
  293. const FORMAT_NUMBER = '0';
  294. const FORMAT_NUMBER_00 = '0.00';
  295. const FORMAT_NUMBER_COMMA_SEPARATED1 = '#,##0.00';
  296. const FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-';
  297. const FORMAT_PERCENTAGE = '0%';
  298. const FORMAT_PERCENTAGE_00 = '0.00%';
  299. const FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd';
  300. const FORMAT_DATE_YYYYMMDD = 'yy-mm-dd';
  301. const FORMAT_DATE_DDMMYYYY = 'dd/mm/yy';
  302. const FORMAT_DATE_DMYSLASH = 'd/m/yy';
  303. const FORMAT_DATE_DMYMINUS = 'd-m-yy';
  304. const FORMAT_DATE_DMMINUS = 'd-m';
  305. const FORMAT_DATE_MYMINUS = 'm-yy';
  306. const FORMAT_DATE_XLSX14 = 'mm-dd-yy';
  307. const FORMAT_DATE_XLSX15 = 'd-mmm-yy';
  308. const FORMAT_DATE_XLSX16 = 'd-mmm';
  309. const FORMAT_DATE_XLSX17 = 'mmm-yy';
  310. const FORMAT_DATE_XLSX22 = 'm/d/yy h:mm';
  311. const FORMAT_DATE_DATETIME = 'd/m/yy h:mm';
  312. const FORMAT_DATE_TIME1 = 'h:mm AM/PM';
  313. const FORMAT_DATE_TIME2 = 'h:mm:ss AM/PM';
  314. const FORMAT_DATE_TIME3 = 'h:mm';
  315. const FORMAT_DATE_TIME4 = 'h:mm:ss';
  316. const FORMAT_DATE_TIME5 = 'mm:ss';
  317. const FORMAT_DATE_TIME6 = 'h:mm:ss';
  318. const FORMAT_DATE_TIME7 = 'i:s.S';
  319. const FORMAT_DATE_TIME8 = 'h:mm:ss;@';
  320. const FORMAT_DATE_YYYYMMDDSLASH = 'yy/mm/dd;@';
  321. const FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-';
  322. const FORMAT_CURRENCY_USD = '$#,##0_-';
  323. const FORMAT_CURRENCY_EUR_SIMPLE = '#,##0.00_-"€"';
  324. const FORMAT_CURRENCY_EUR = '#,##0_-"€"';
  325. /**
  326. * @var string mode is an export mode or import mode. valid value are 'export' and 'import'.
  327. */
  328. public $mode = 'export';
  329. /**
  330. * @var boolean for set the export excel with multiple sheet.
  331. */
  332. public $isMultipleSheet = false;
  333. /**
  334. * @var array properties for set property on the excel object.
  335. */
  336. public $properties;
  337. /**
  338. * @var Model object or DataProvider object with much data.
  339. */
  340. public $models;
  341. /**
  342. * @var array columns to get the attributes from the model, this valid value only the exist attribute on the model.
  343. * If this is not set, then all attribute of the model will be set as columns.
  344. */
  345. public $columns = [];
  346. /**
  347. * @var array header to set the header column on first line. Set this if want to custom header.
  348. * If not set, the header will get attributes label of model attributes.
  349. */
  350. public $headers = [];
  351. /**
  352. * @var string|array name for file name to export or save.
  353. */
  354. public $fileName;
  355. /**
  356. * @var string save path is a directory to save the file or you can blank this to set the file as attachment.
  357. */
  358. public $savePath;
  359. /**
  360. * @var string format for excel to export. Valid value are 'Xls','Xlsx','Xml','Ods','Slk','Gnumeric','Csv', and 'Html'.
  361. */
  362. public $format;
  363. /**
  364. * @var boolean to set the title column on the first line.
  365. */
  366. public $setFirstTitle = true;
  367. /**
  368. * @var boolean to set the file excel to download mode.
  369. */
  370. public $asAttachment = false;
  371. /**
  372. * @var boolean to set the first record on excel file to a keys of array per line.
  373. * If you want to set the keys of record column with first record, if it not set, the header with use the alphabet column on excel.
  374. */
  375. public $setFirstRecordAsKeys = true;
  376. /**
  377. * @var boolean to set the sheet index by sheet name or array result if the sheet not only one.
  378. */
  379. public $setIndexSheetByName = false;
  380. /**
  381. * @var string sheetname to getting. This is only get the sheet with same name.
  382. */
  383. public $getOnlySheet;
  384. /**
  385. * @var boolean to set the import data will return as array.
  386. */
  387. public $asArray;
  388. /**
  389. * @var array to unread record by index number.
  390. */
  391. public $leaveRecordByIndex = [];
  392. /**
  393. * @var array to read record by index, other will leave.
  394. */
  395. public $getOnlyRecordByIndex = [];
  396. /**
  397. * @var array|Formatter the formatter used to format model attribute values into displayable texts.
  398. * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]]
  399. * instance. If this property is not set, the "formatter" application component will be used.
  400. */
  401. public $formatter;
  402. /**
  403. * @var boolean define the column autosize
  404. */
  405. public $autoSize = false;
  406. /**
  407. * @var boolean if true, this writer pre-calculates all formulas in the spreadsheet. This can be slow on large spreadsheets, and maybe even unwanted.
  408. */
  409. public $preCalculationFormula = false;
  410. /**
  411. * @var boolean Because of a bug in the Office2003 compatibility pack, there can be some small issues when opening Xlsx spreadsheets (mostly related to formula calculation)
  412. */
  413. public $compatibilityOffice2003 = false;
  414. /**
  415. * @var custom CSV delimiter for import. Works only with CSV files
  416. */
  417. public $CSVDelimiter = ";";
  418. /**
  419. * @var custom CSV encoding for import. Works only with CSV files
  420. */
  421. public $CSVEncoding = "UTF-8";
  422. /**
  423. * (non-PHPdoc)
  424. * @see \yii\base\Object::init()
  425. */
  426. public function init()
  427. {
  428. parent::init();
  429. if ($this->formatter == null) {
  430. $this->formatter = \Yii::$app->getFormatter();
  431. } elseif (is_array($this->formatter)) {
  432. $this->formatter = \Yii::createObject($this->formatter);
  433. }
  434. if (!$this->formatter instanceof Formatter) {
  435. throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.');
  436. }
  437. }
  438. /**
  439. * Setting data from models
  440. */
  441. public function executeColumns(&$activeSheet = null, $models, $columns = [], $headers = [])
  442. {
  443. if ($activeSheet == null) {
  444. $activeSheet = $this->activeSheet;
  445. }
  446. $hasHeader = false;
  447. $row = 1;
  448. $char = 26;
  449. foreach ($models as $model) {
  450. if (empty($columns)) {
  451. $columns = $model->attributes();
  452. }
  453. if ($this->setFirstTitle && !$hasHeader) {
  454. $isPlus = false;
  455. $colplus = 0;
  456. $colnum = 1;
  457. foreach ($columns as $key=>$column) {
  458. $col = '';
  459. if ($colnum > $char) {
  460. $colplus += 1;
  461. $colnum = 1;
  462. $isPlus = true;
  463. }
  464. if ($isPlus) {
  465. $col .= chr(64+$colplus);
  466. }
  467. $col .= chr(64+$colnum);
  468. $header = '';
  469. if (is_array($column)) {
  470. if (isset($column['header'])) {
  471. $header = $column['header'];
  472. }elseif(isset($column['label'])){
  473. $header = $column['label'];
  474. } elseif (isset($column['attribute']) && isset($headers[$column['attribute']])) {
  475. $header = $headers[$column['attribute']];
  476. } elseif (isset($column['attribute'])) {
  477. $header = $model->getAttributeLabel($column['attribute']);
  478. } elseif (isset($column['cellFormat']) && is_array($column['cellFormat'])) {
  479. $activeSheet->getStyle($col.$row)->applyFromArray($column['cellFormat']);
  480. }
  481. } else {
  482. if(isset($headers[$column])) {
  483. $header = $headers[$column];
  484. } else {
  485. $header = $model->getAttributeLabel($column);
  486. }
  487. }
  488. if (isset($column['width'])) {
  489. $activeSheet->getColumnDimension(strtoupper($col))->setWidth($column['width']);
  490. }
  491. $activeSheet->setCellValue($col.$row,$header);
  492. $colnum++;
  493. }
  494. $hasHeader=true;
  495. $row++;
  496. }
  497. $isPlus = false;
  498. $colplus = 0;
  499. $colnum = 1;
  500. foreach ($columns as $key=>$column) {
  501. $col = '';
  502. if ($colnum > $char) {
  503. $colplus++;
  504. $colnum = 1;
  505. $isPlus = true;
  506. }
  507. if ($isPlus) {
  508. $col .= chr(64+$colplus);
  509. }
  510. $col .= chr(64+$colnum);
  511. if (is_array($column)) {
  512. $column_value = $this->executeGetColumnData($model, $column);
  513. if (isset($column['cellFormat']) && is_array($column['cellFormat'])) {
  514. $activeSheet->getStyle($col.$row)->applyFromArray($column['cellFormat']);
  515. }
  516. } else {
  517. $column_value = $this->executeGetColumnData($model, ['attribute' => $column]);
  518. }
  519. $activeSheet->setCellValue($col.$row,$column_value);
  520. $colnum++;
  521. }
  522. $row++;
  523. if($this->autoSize){
  524. foreach (range(0, $colnum) as $col) {
  525. $activeSheet->getColumnDimensionByColumn($col)->setAutoSize(true);
  526. }
  527. }
  528. }
  529. }
  530. /**
  531. * Setting label or keys on every record if setFirstRecordAsKeys is true.
  532. * @param array $sheetData
  533. * @return multitype:multitype:array
  534. */
  535. public function executeArrayLabel($sheetData)
  536. {
  537. $keys = ArrayHelper::remove($sheetData, '1');
  538. $new_data = [];
  539. foreach ($sheetData as $values)
  540. {
  541. $new_data[] = array_combine($keys, $values);
  542. }
  543. return $new_data;
  544. }
  545. /**
  546. * Leave record with same index number.
  547. * @param array $sheetData
  548. * @param array $index
  549. * @return array
  550. */
  551. public function executeLeaveRecords($sheetData = [], $index = [])
  552. {
  553. foreach ($sheetData as $key => $data)
  554. {
  555. if (in_array($key, $index))
  556. {
  557. unset($sheetData[$key]);
  558. }
  559. }
  560. return $sheetData;
  561. }
  562. /**
  563. * Read record with same index number.
  564. * @param array $sheetData
  565. * @param array $index
  566. * @return array
  567. */
  568. public function executeGetOnlyRecords($sheetData = [], $index = [])
  569. {
  570. foreach ($sheetData as $key => $data)
  571. {
  572. if (!in_array($key, $index))
  573. {
  574. unset($sheetData[$key]);
  575. }
  576. }
  577. return $sheetData;
  578. }
  579. /**
  580. * Getting column value.
  581. * @param Model $model
  582. * @param array $params
  583. * @return Ambigous <NULL, string, mixed>
  584. */
  585. public function executeGetColumnData($model, $params = [])
  586. {
  587. $value = null;
  588. if (isset($params['value']) && $params['value'] !== null) {
  589. if (is_string($params['value'])) {
  590. $value = ArrayHelper::getValue($model, $params['value']);
  591. } else {
  592. $value = call_user_func($params['value'], $model, $this);
  593. }
  594. } elseif (isset($params['attribute']) && $params['attribute'] !== null) {
  595. $value = ArrayHelper::getValue($model, $params['attribute']);
  596. }
  597. if (isset($params['format']) && $params['format'] != null)
  598. $value = $this->formatter()->format($value, $params['format']);
  599. return $value;
  600. }
  601. /**
  602. * Populating columns for checking the column is string or array. if is string this will be checking have a formatter or header.
  603. * @param array $columns
  604. * @throws InvalidParamException
  605. * @return multitype:multitype:array
  606. */
  607. public function populateColumns($columns = [])
  608. {
  609. $_columns = [];
  610. foreach ($columns as $key => $value)
  611. {
  612. if (is_string($value))
  613. {
  614. $value_log = explode(':', $value);
  615. $_columns[$key] = ['attribute' => $value_log[0]];
  616. if (isset($value_log[1]) && $value_log[1] !== null) {
  617. $_columns[$key]['format'] = $value_log[1];
  618. }
  619. if (isset($value_log[2]) && $value_log[2] !== null) {
  620. $_columns[$key]['header'] = $value_log[2];
  621. }
  622. } elseif (is_array($value)) {
  623. if(isset($value['class'])){
  624. continue;
  625. }
  626. if (!isset($value['attribute']) && !isset($value['value'])) {
  627. throw new \InvalidArgumentException('Attribute or Value must be defined.');
  628. }
  629. $_columns[$key] = $value;
  630. }
  631. }
  632. return $_columns;
  633. }
  634. /**
  635. * Formatter for i18n.
  636. * @return Formatter
  637. */
  638. public function formatter()
  639. {
  640. if (!isset($this->formatter))
  641. $this->formatter = \Yii::$app->getFormatter();
  642. return $this->formatter;
  643. }
  644. /**
  645. * Setting header to download generated file xls
  646. */
  647. public function setHeaders()
  648. {
  649. header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
  650. header('Content-Disposition: attachment;filename="' . $this->getFileName() .'"');
  651. header('Cache-Control: max-age=0');
  652. }
  653. /**
  654. * Getting the file name of exporting xls file
  655. * @return string
  656. */
  657. public function getFileName()
  658. {
  659. $fileName = 'exports.xlsx';
  660. if (isset($this->fileName)) {
  661. $fileName = $this->fileName;
  662. if (strpos($fileName, '.xlsx') === false)
  663. $fileName .= '.xlsx';
  664. }
  665. return $fileName;
  666. }
  667. /**
  668. * Setting properties for excel file
  669. * @param PHPExcel $objectExcel
  670. * @param array $properties
  671. */
  672. public function properties(&$objectExcel, $properties = [])
  673. {
  674. foreach ($properties as $key => $value)
  675. {
  676. $keyname = "set" . ucfirst($key);
  677. $objectExcel->getProperties()->{$keyname}($value);
  678. }
  679. }
  680. /**
  681. * saving the xls file to download or to path
  682. */
  683. public function writeFile($sheet)
  684. {
  685. if (!isset($this->format))
  686. $this->format = 'Xlsx';
  687. $objectwriter = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($sheet, $this->format);
  688. $path = 'php://output';
  689. if (isset($this->savePath) && $this->savePath != null) {
  690. $path = $this->savePath . '/' . $this->getFileName();
  691. }
  692. $objectwriter->setOffice2003Compatibility($this->compatibilityOffice2003);
  693. $objectwriter->setPreCalculateFormulas($this->preCalculationFormula);
  694. $objectwriter->save($path);
  695. if ($path == 'php://output')
  696. exit();
  697. return true;
  698. }
  699. /**
  700. * reading the xls file
  701. */
  702. public function readFile($fileName)
  703. {
  704. if (!isset($this->format))
  705. $this->format = \PhpOffice\PhpSpreadsheet\IOFactory::identify($fileName);
  706. $objectreader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($this->format);
  707. if ($this->format == "Csv") {
  708. $objectreader->setDelimiter($this->CSVDelimiter);
  709. $objectreader->setInputEncoding($this->CSVEncoding);
  710. }
  711. $objectPhpExcel = $objectreader->load($fileName);
  712. $sheetCount = $objectPhpExcel->getSheetCount();
  713. $sheetDatas = [];
  714. if ($sheetCount > 1) {
  715. foreach ($objectPhpExcel->getSheetNames() as $sheetIndex => $sheetName) {
  716. if (isset($this->getOnlySheet) && $this->getOnlySheet != null) {
  717. if(!$objectPhpExcel->getSheetByName($this->getOnlySheet)) {
  718. return $sheetDatas;
  719. }
  720. $objectPhpExcel->setActiveSheetIndexByName($this->getOnlySheet);
  721. $indexed = $this->getOnlySheet;
  722. $sheetDatas[$indexed] = $objectPhpExcel->getActiveSheet()->toArray(null, true, true, true);
  723. if ($this->setFirstRecordAsKeys) {
  724. $sheetDatas[$indexed] = $this->executeArrayLabel($sheetDatas[$indexed]);
  725. }
  726. if (!empty($this->getOnlyRecordByIndex)) {
  727. $sheetDatas[$indexed] = $this->executeGetOnlyRecords($sheetDatas[$indexed], $this->getOnlyRecordByIndex);
  728. }
  729. if (!empty($this->leaveRecordByIndex)) {
  730. $sheetDatas[$indexed] = $this->executeLeaveRecords($sheetDatas[$indexed], $this->leaveRecordByIndex);
  731. }
  732. return $sheetDatas[$indexed];
  733. } else {
  734. $objectPhpExcel->setActiveSheetIndexByName($sheetName);
  735. $indexed = $this->setIndexSheetByName==true ? $sheetName : $sheetIndex;
  736. $sheetDatas[$indexed] = $objectPhpExcel->getActiveSheet()->toArray(null, true, true, true);
  737. if ($this->setFirstRecordAsKeys) {
  738. $sheetDatas[$indexed] = $this->executeArrayLabel($sheetDatas[$indexed]);
  739. }
  740. if (!empty($this->getOnlyRecordByIndex) && isset($this->getOnlyRecordByIndex[$indexed]) && is_array($this->getOnlyRecordByIndex[$indexed])) {
  741. $sheetDatas = $this->executeGetOnlyRecords($sheetDatas, $this->getOnlyRecordByIndex[$indexed]);
  742. }
  743. if (!empty($this->leaveRecordByIndex) && isset($this->leaveRecordByIndex[$indexed]) && is_array($this->leaveRecordByIndex[$indexed])) {
  744. $sheetDatas[$indexed] = $this->executeLeaveRecords($sheetDatas[$indexed], $this->leaveRecordByIndex[$indexed]);
  745. }
  746. }
  747. }
  748. } else {
  749. $sheetDatas = $objectPhpExcel->getActiveSheet()->toArray(null, true, true, true);
  750. if ($this->setFirstRecordAsKeys) {
  751. $sheetDatas = $this->executeArrayLabel($sheetDatas);
  752. }
  753. if (!empty($this->getOnlyRecordByIndex)) {
  754. $sheetDatas = $this->executeGetOnlyRecords($sheetDatas, $this->getOnlyRecordByIndex);
  755. }
  756. if (!empty($this->leaveRecordByIndex)) {
  757. $sheetDatas = $this->executeLeaveRecords($sheetDatas, $this->leaveRecordByIndex);
  758. }
  759. }
  760. return $sheetDatas;
  761. }
  762. /**
  763. * (non-PHPdoc)
  764. * @see \yii\base\Widget::run()
  765. */
  766. public function run()
  767. {
  768. if ($this->mode == 'export')
  769. {
  770. $sheet = new Spreadsheet();
  771. if (!isset($this->models))
  772. throw new InvalidConfigException('Config models must be set');
  773. if (isset($this->properties))
  774. {
  775. $this->properties($sheet, $this->properties);
  776. }
  777. if ($this->isMultipleSheet) {
  778. $index = 0;
  779. $worksheet = [];
  780. foreach ($this->models as $title => $models) {
  781. $sheet->createSheet($index);
  782. $sheet->getSheet($index)->setTitle($title);
  783. $worksheet[$index] = $sheet->getSheet($index);
  784. $columns = isset($this->columns[$title]) ? $this->columns[$title] : [];
  785. $headers = isset($this->headers[$title]) ? $this->headers[$title] : [];
  786. $this->executeColumns($worksheet[$index], $models, $this->populateColumns($columns), $headers);
  787. $index++;
  788. }
  789. } else {
  790. $worksheet = $sheet->getActiveSheet();
  791. $this->executeColumns($worksheet, $this->models, isset($this->columns) ? $this->populateColumns($this->columns) : [], isset($this->headers) ? $this->headers : []);
  792. }
  793. if ($this->asAttachment) {
  794. $this->setHeaders();
  795. }
  796. $this->writeFile($sheet);
  797. $sheet->disconnectWorksheets();
  798. unset($sheet);
  799. }
  800. elseif ($this->mode == 'import')
  801. {
  802. if (is_array($this->fileName)) {
  803. $datas = [];
  804. foreach ($this->fileName as $key => $filename) {
  805. $datas[$key] = $this->readFile($filename);
  806. }
  807. return $datas;
  808. } else {
  809. return $this->readFile($this->fileName);
  810. }
  811. }
  812. }
  813. /**
  814. * Exporting data into an excel file.
  815. *
  816. * ~~~
  817. *
  818. * \moonland\phpexcel\Excel::export([
  819. * 'models' => $allModels,
  820. * 'columns' => ['column1','column2','column3'],
  821. * //without header working, because the header will be get label from attribute label.
  822. * 'header' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3'],
  823. * ]);
  824. *
  825. * ~~~
  826. *
  827. * New Feature for exporting data, you can use this if you familiar yii gridview.
  828. * That is same with gridview data column.
  829. * Columns in array mode valid params are 'attribute', 'header', 'format', 'value', and footer (TODO).
  830. * Columns in string mode valid layout are 'attribute:format:header:footer(TODO)'.
  831. *
  832. * ~~~
  833. *
  834. * \moonland\phpexcel\Excel::export([
  835. * 'models' => Post::find()->all(),
  836. * 'columns' => [
  837. * 'author.name:text:Author Name',
  838. * [
  839. * 'attribute' => 'content',
  840. * 'header' => 'Content Post',
  841. * 'format' => 'text',
  842. * 'value' => function($model) {
  843. * return ExampleClass::removeText('example', $model->content);
  844. * },
  845. * ],
  846. * 'like_it:text:Reader like this content',
  847. * 'created_at:datetime',
  848. * [
  849. * 'attribute' => 'updated_at',
  850. * 'format' => 'date',
  851. * ],
  852. * ],
  853. * 'headers' => [
  854. * 'created_at' => 'Date Created Content',
  855. * ],
  856. * ]);
  857. *
  858. * ~~~
  859. *
  860. * @param array $config
  861. * @return string
  862. */
  863. public static function export($config=[])
  864. {
  865. $config = ArrayHelper::merge(['mode' => 'export'], $config);
  866. return self::widget($config);
  867. }
  868. /**
  869. * Import file excel and return into an array.
  870. *
  871. * ~~~
  872. *
  873. * $data = \moonland\phpexcel\Excel::import($fileName, ['setFirstRecordAsKeys' => true]);
  874. *
  875. * ~~~
  876. *
  877. * @param string!array $fileName to load.
  878. * @param array $config is a more configuration.
  879. * @return string
  880. */
  881. public static function import($fileName, $config=[])
  882. {
  883. $config = ArrayHelper::merge(['mode' => 'import', 'fileName' => $fileName, 'asArray' => true], $config);
  884. return self::widget($config);
  885. }
  886. /**
  887. * @param array $config
  888. * @return string
  889. */
  890. public static function widget($config = [])
  891. {
  892. if ((isset($config['mode']) and $config['mode'] == 'import') && !isset($config['asArray'])) {
  893. $config['asArray'] = true;
  894. }
  895. if (isset($config['asArray']) && $config['asArray']==true)
  896. {
  897. $config['class'] = get_called_class();
  898. $widget = \Yii::createObject($config);
  899. return $widget->run();
  900. } else {
  901. return parent::widget($config);
  902. }
  903. }
  904. }