ExcelExport.class.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. <?php
  2. require_once (LIB_PATH.'XLSXWriter'.SLASH.'XLSXWriter.class.php');
  3. class ExcelExport
  4. {
  5. public static $mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
  6. public static $extension = 'xlsx';
  7. public static $description = 'Fichier classeur de données Excel';
  8. public static function sample($dataset){
  9. foreach($dataset as $macro => $infos) {
  10. $data[]['Macros disponibles :'] = ($infos['type']=='list') ? '{{#'.$macro.'}}{{/'.$macro.'}} : '.$infos['desc'] : '{{'.$macro.'}} : '.$infos['desc'];
  11. }
  12. $stream = Excel::exportArray($data, null ,'Sans titre');
  13. return $stream;
  14. }
  15. //Remplacement du tag d'image par l'image concernée
  16. //dans le fichier modèle
  17. public static function add_image($worksheet, $macro, $value, $cellCoord, $scale=2){
  18. $value = substr($value,2);
  19. //On supprime le tag
  20. $cellVal = str_replace('{{'.$macro.'}}', '', $worksheet->getCell($cellCoord)->getValue());
  21. $worksheet->getCell($cellCoord)->setValue($cellVal);
  22. preg_match_all('~[A-Z]+|\d+~', $cellCoord, $cellMatches);
  23. $cellMatches = reset($cellMatches);
  24. $cellIndex = $cellMatches[0];
  25. $rowIndex = $cellMatches[1];
  26. //On récupère les infos de l'image
  27. list($width, $height) = getimagesize($value);
  28. //On définit la taille de la cellule à celle de l'image
  29. $cellWidth = $worksheet->getColumnDimension($cellIndex);
  30. $rowHeight = $worksheet->getRowDimension($rowIndex);
  31. //La largeur d'une colonne est définie en unité (Microsoft). 1px correpond à peu près à 0.2 unité
  32. $cellWidth->setWidth(($width/$scale)*0.2);
  33. //0.75 correspond à 1px en pt, et la hauteur d'une ligne est définie en pt
  34. $rowHeight->setRowHeight(($height/$scale)*0.75);
  35. //On ajoute l'image dans la feuille
  36. $drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
  37. $drawing->setName($macro);
  38. $drawing->setPath($value);
  39. $drawing->setResizeProportional(true);
  40. $drawing->setWidthAndHeight($width/$scale,$height/$scale);
  41. $drawing->setCoordinates($cellCoord);
  42. $drawing->setWorksheet($worksheet);
  43. }
  44. //Remplace les données d'un jeu de données en fonction
  45. //des tags rencontrés et fournis par le jeu de données
  46. public static function replace_data($worksheet, $data=array(), $cellCoord, $cellContent, $imgScale=2){
  47. //Pour chaque élément de l'entité
  48. foreach ($data as $tag => $value) {
  49. if(strpos($cellContent, '{{'.$tag.'}}') !== false){
  50. $cellVal = $worksheet->getCell($cellCoord)->getValue();
  51. //Ajout des images
  52. if(substr($value,0,2)=='::'){
  53. ExcelExport::add_image($worksheet, $tag, $value, $cellCoord, $imgScale);
  54. continue;
  55. }
  56. if(is_numeric($value) || preg_match("/\d+(?:\.\d{1,2})? [€,$,£,₽,¥]/",$value))
  57. $worksheet->getStyle($cellCoord)->getNumberFormat()->setFormatCode(PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER);
  58. $cellVal = str_replace('{{'.$tag.'}}', $value, $cellVal);
  59. $worksheet->getCell($cellCoord)->setValue($cellVal);
  60. }
  61. }
  62. }
  63. //Récupère et gère la structure du remplacement
  64. //des données dans le fichier template fourni
  65. public static function from_template($source, $data, $return){
  66. require(LIB_PATH.'PhpSpreadsheet'.SLASH.'vendor'.SLASH.'autoload.php');
  67. $destination = File::dir().'tmp'.SLASH.'template.'.time().'-'.rand(0,100).'.xlsx';
  68. $spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load(File::dir().$source);
  69. $spreadsheet->getActiveSheet()->getPageMargins()->setTop(0.2);
  70. $spreadsheet->getActiveSheet()->getPageMargins()->setRight(0.2);
  71. $spreadsheet->getActiveSheet()->getPageMargins()->setLeft(0.2);
  72. $spreadsheet->getActiveSheet()->getPageMargins()->setBottom(0.2);
  73. $spreadsheet->getActiveSheet()->getPageMargins()->setFooter(0.5);
  74. $spreadsheet->getActiveSheet()->getPageMargins()->setHeader(0.5);
  75. $spreadsheet->getActiveSheet()->getPageSetup()->setHorizontalCentered(true);
  76. //Pour chaque feuille dans le classeur Excel
  77. foreach ($spreadsheet->getAllSheets() as $wrkSheetIdx => $worksheet) {
  78. //On récupère la zone de travail (pour ne pas se perdre dans des cellules vide)
  79. //Avec la colonne max et la ligne max
  80. $maxCol = 'A';
  81. $maxRow = 0;
  82. foreach ($worksheet->getCoordinates() as $coord) {
  83. preg_match_all('~[A-Z]+|\d+~', $coord, $matches);
  84. $matches = reset($matches);
  85. $currCol = PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($matches[0]);
  86. $currRow = $matches[1];
  87. if($maxCol < $currCol)
  88. $maxCol = $currCol;
  89. if($maxRow < $currRow)
  90. $maxRow = $currRow;
  91. }
  92. //On parcours une fois le contenu du la feuille
  93. //pour avoir les différents bords des boucles, si
  94. //il y en a dans le fichier.
  95. $rows = $worksheet->toArray('', true, true, true);
  96. $loopDatas = array('startLine' => 0,'endLine' => 0);
  97. $finalValues = array();
  98. $loopStart = $loopEnd = false;
  99. foreach ($rows as $rowIdx => $cell) {
  100. foreach ($cell as $cellIdx => $content) {
  101. if(empty($content) && PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($cellIdx)>$maxCol) continue;
  102. foreach ($data as $macro => $value) {
  103. if(!is_array($value)) continue;
  104. $entityCount = count($value);
  105. if(strpos($content, '{{/'.$macro.'}}') !== false) {
  106. $loopDatas['endLine'] = $rowIdx;
  107. $loopDatas['endColumn'] = $cellIdx;
  108. $loopDatas['endCoord'] = $cellIdx.$rowIdx;
  109. $loopDatas['totalRow'] = $loopDatas['endLine'] - $loopDatas['startLine'];
  110. $loopDatas['totalCol'] = letters_to_numbers($loopDatas['endColumn']) - letters_to_numbers($loopDatas['startColumn']);
  111. $loopEnd = true;
  112. }
  113. if(!$loopEnd && strpos($content, '{{#'.$macro.'}}') !== false) {
  114. $loopDatas['startLine'] = $rowIdx;
  115. $loopDatas['startColumn'] = $cellIdx;
  116. $loopDatas['startCoord'] = $cellIdx.$rowIdx;
  117. $loopStart = true;
  118. }
  119. }
  120. }
  121. }
  122. //Définition type de boucle
  123. if($loopDatas['startLine'] != 0 && $loopDatas['endLine'] != 0)
  124. $loopDatas['loopType'] = ($loopDatas['startLine'] != $loopDatas['endLine']) ? 'vertical' : 'horizontal';
  125. //Récupération des infos en fonction du type de boucle
  126. if(isset($loopDatas['loopType'])){
  127. if($loopDatas['loopType'] == 'vertical'){
  128. //On parcourt par ligne puis par colonne
  129. foreach ($rows as $rowIdx => $cell) {
  130. foreach ($cell as $cellIdx => $content) {
  131. if($rowIdx > $loopDatas['startLine'] && $rowIdx < $loopDatas['endLine'])
  132. $finalValues[$rowIdx][$cellIdx][] = $content;
  133. }
  134. }
  135. } else {
  136. //On parcourt par colonne puis par ligne
  137. for($col = $loopDatas['startColumn']; $col <= $loopDatas['endColumn']; ++$col){
  138. if($col == $loopDatas['startColumn'] || $col == $loopDatas['endColumn']) continue;
  139. for($row = $loopDatas['startLine']; $row <= $maxRow; ++$row) {
  140. $content = $worksheet->getCell($col.$row)->getValue();
  141. $finalValues[$col][$row][] = $content;
  142. }
  143. }
  144. }
  145. }
  146. //On remplace les données à l'intérieur
  147. //des boucles si des boucles sont présentes
  148. if(isset($finalValues) && !empty($finalValues)){
  149. //Pour chaque value du jeu de données
  150. foreach ($data as $macro => $value) {
  151. if(!is_array($value)) continue;
  152. $worksheet->getCell($loopDatas['startCoord'])->setValue('');
  153. $worksheet->getCell($loopDatas['endCoord'])->setValue('');
  154. //Pour chaque entité
  155. foreach ($value as $i => $entity) {
  156. $rowIterator = $colIterator = 0;
  157. if($loopDatas['loopType'] == 'vertical'){
  158. unset($finalValues[$loopDatas['endLine']]);
  159. //Pour chaque ligne
  160. foreach ($finalValues as $rIdx => $cell) {
  161. $lineIdx = ($loopDatas['startLine']+1)+(($loopDatas['totalRow']-1)*$i)+$rowIterator;
  162. //On ajoute 1 car on insère AVANT la ligne, et non APRÈS
  163. $rowWhereInsert = $i==0 ? $loopDatas['endLine']+1 : $rowWhereInsert += 1;
  164. $worksheet->insertNewRowBefore($rowWhereInsert, 1);
  165. //Pour chaque cellule
  166. foreach ($cell as $cIdx => $content) {
  167. $currCoord = $cIdx.$lineIdx;
  168. $lineRef = $lineIdx-($loopDatas['totalRow']-1);
  169. //Dans le cas où $i vaut 0, cas particulier, on remplace directement les données dans la feuille
  170. $referentCell = $i==0 ? $currCoord : $cIdx.$lineRef;
  171. $worksheet->setCellValue($currCoord, $content[0]);
  172. $worksheet->duplicateStyle($worksheet->getStyle($referentCell),$currCoord);
  173. //On remplace les données
  174. ExcelExport::replace_data($worksheet, $entity, $currCoord, $content[0], 4);
  175. }
  176. $rowIterator++;
  177. }
  178. } else {
  179. //Pour chaque colonne
  180. foreach ($finalValues as $cIdx => $col) {
  181. $colIdx = numbers_to_letters((letters_to_numbers($loopDatas['startColumn'])+1)+(($loopDatas['totalCol']-1)*$i)+$colIterator);
  182. //On ajoute 1 car on insère AVANT la ligne, et non APRÈS
  183. $colWhereInsert = $i==0 ? numbers_to_letters(letters_to_numbers($loopDatas['endColumn'])+1) : numbers_to_letters(letters_to_numbers($colWhereInsert)+1);
  184. $worksheet->insertNewColumnBefore($colWhereInsert, 1);
  185. //Pour chaque cellule
  186. foreach ($col as $rIdx => $content) {
  187. $currCoord = $colIdx.$rIdx;
  188. //Dans le cas où $i vaut 0, cas particulier, on remplace directement les données dans la feuille
  189. $referentCell = $i==0 ? $currCoord : $cIdx.$rIdx;
  190. $worksheet->setCellValue($currCoord, $content[0]);
  191. $worksheet->duplicateStyle($worksheet->getStyle($referentCell),$currCoord);
  192. //On remplace les données
  193. ExcelExport::replace_data($worksheet, $entity, $currCoord, $content[0], 4);
  194. }
  195. $colIterator++;
  196. }
  197. }
  198. }
  199. }
  200. }
  201. //On remplace le reste des tag présents
  202. //sur la feuille de calcul du fichier template
  203. foreach ($worksheet->getRowIterator() as $row) {
  204. $cellIterator = $row->getCellIterator();
  205. foreach ($cellIterator as $cell) {
  206. $cellVal = $cell->getValue();
  207. $cellIndex = $cell->getColumn();
  208. if(empty($cellVal) && PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($cellIndex)>$maxCol) continue;
  209. foreach ($data as $macro => $value) {
  210. if(is_array($value)) continue;
  211. //Ajout des valeurs
  212. if(strpos($cellVal, '{{'.$macro.'}}') !== false){
  213. //Ajout des images
  214. if(substr($value,0,2)=='::'){
  215. ExcelExport::add_image($worksheet, $macro, $value, $cell->getCoordinate(), 2);
  216. continue;
  217. }
  218. if(is_numeric($value))
  219. $worksheet->getStyle($cell->getCoordinate())->getNumberFormat()->setFormatCode(PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_00);
  220. $cellVal = str_replace('{{'.$macro.'}}', $value, $cellVal);
  221. $cell->setValue($cellVal);
  222. }
  223. }
  224. }
  225. }
  226. }
  227. $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx');
  228. $writer->save($destination);
  229. if($return!='stream') return $destination;
  230. $stream = file_get_contents($destination);
  231. unlink($destination);
  232. return $stream;
  233. }
  234. //Copie l'intégralité de la ligne depuis des
  235. //positions données à l'endroit voulu
  236. public static function copy_full_row(&$ws_from, &$ws_to, $row_from, $row_to) {
  237. $ws_to->getRowDimension($row_to)->setRowHeight($ws_from->getRowDimension($row_from)->getRowHeight());
  238. $lastColumn = $ws_from->getHighestColumn();
  239. ++$lastColumn;
  240. for($c = 'A'; $c != $lastColumn; ++$c) {
  241. $cell_from = $ws_from->getCell($c.$row_from);
  242. $cell_to = $ws_to->getCell($c.$row_to);
  243. $cell_to->setXfIndex($cell_from->getXfIndex()); // black magic here
  244. $cell_to->setValue($cell_from->getValue());
  245. }
  246. }
  247. }