PageRenderTime 40ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/Library/Kumbia/Report/Adapters/Html.php

http://kumbia-enterprise.googlecode.com/
PHP | 680 lines | 431 code | 41 blank | 208 comment | 71 complexity | ed141e668374f80a1c9642ac637f23f8 MD5 | raw file
  1. <?php
  2. /**
  3. * Kumbia Enterprise Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the New BSD License that is bundled
  8. * with this package in the file docs/LICENSE.txt.
  9. *
  10. * If you did not receive a copy of the license and are unable to
  11. * obtain it through the world-wide-web, please send an email
  12. * to license@loudertechnology.com so we can send you a copy immediately.
  13. *
  14. * @category Kumbia
  15. * @package Report
  16. * @subpackage Adapters
  17. * @copyright Copyright (c) 2008-2010 Louder Technology COL. (http://www.loudertechnology.com)
  18. * @copyright Copyright (c) 2005-2009 Andres Felipe Gutierrez (gutierrezandresfelipe at gmail.com)
  19. * @license New BSD License
  20. * @version $Id: Html.php 150 2010-09-24 16:20:10Z gutierrezandresfelipe $
  21. */
  22. /**
  23. * HtmlReport
  24. *
  25. * Adaptador que permite generar reportes en HTML
  26. *
  27. * @category Kumbia
  28. * @package Report
  29. * @subpackage Adapters
  30. * @copyright Copyright (c) 2008-2010 Louder Technology COL. (http://www.loudertechnology.com)
  31. * @copyright Copyright (c) 2005-2009 Andres Felipe Gutierrez (gutierrezandresfelipe at gmail.com)
  32. * @license New BSD License
  33. * @abstract
  34. */
  35. class HtmlReport extends ReportAdapter implements ReportInterface {
  36. /**
  37. * Tabla de offset Ratios para Fuentes conocidas
  38. *
  39. * @var array
  40. */
  41. static private $_offsetRatio = array(
  42. 'Arial' => array(
  43. 9 => 1.25,
  44. 10 => 1.3,
  45. 11 => 1.25,
  46. 12 => 1.2,
  47. 13 => 1.2,
  48. 14 => 1.25,
  49. 15 => 1.3,
  50. 16 => 1.35,
  51. ),
  52. 'Verdana' => array(
  53. 9 => 1.25,
  54. 10 => 1.3,
  55. 11 => 1.25,
  56. 12 => 1.2,
  57. 13 => 1.2,
  58. 14 => 1.25,
  59. 15 => 1.3,
  60. 16 => 1.35,
  61. )
  62. );
  63. /**
  64. * Salida HTML
  65. *
  66. * @var string
  67. */
  68. private $_output;
  69. /**
  70. * Tamańo de texto predeterminado
  71. *
  72. * @var int
  73. * @static
  74. */
  75. private static $_defaultFontSize = 12;
  76. /**
  77. * Fuente de texto predeterminado
  78. *
  79. * @var int
  80. * @static
  81. */
  82. private static $_defaultFontFamily = "Arial";
  83. /**
  84. * Alto de cada fila
  85. *
  86. * @var int
  87. */
  88. private $_rowHeight = 0;
  89. /**
  90. * Altura del encabezado
  91. *
  92. * @var int
  93. */
  94. private $_headerHeight = 0;
  95. /**
  96. * Numero total de paginas del reporte
  97. *
  98. * @var int
  99. */
  100. private $_totalPages = 0;
  101. /**
  102. * Totales de columnas
  103. *
  104. * @var array
  105. */
  106. protected $_totalizeValues = array();
  107. /**
  108. * Formatos de Columnas
  109. *
  110. * @var array
  111. */
  112. private $_columnFormats = array();
  113. /**
  114. * Número de columnas del reporte
  115. *
  116. * @var int
  117. */
  118. private $_numberColumns = null;
  119. /**
  120. * Indica si el reporte debe ser volcado a disco en cuanto se agregan los datos
  121. *
  122. * @var unknown_type
  123. */
  124. protected $_implicitFlush = false;
  125. /**
  126. * Handler al archivo temporal donde se volca el reporte
  127. *
  128. * @var handler
  129. */
  130. private $_tempFile;
  131. /**
  132. * Nombre del archivo temporal donde se volca el reporte
  133. *
  134. * @var string
  135. */
  136. private $_tempFileName;
  137. /**
  138. * Indica si el volcado del reporte ha sido iniciado
  139. *
  140. * @var boolean
  141. */
  142. private $_started = false;
  143. /**
  144. * Genera la salida del reporte
  145. *
  146. * @return string
  147. */
  148. public function getOutput(){
  149. $this->_prepareHead();
  150. if($this->getDisplayMode()==Report::DISPLAY_PRINT_PREVIEW){
  151. $this->_appendToOutput("\t<table cellspacing='0' width='100%'><tr><td width='80%' id='preview' align='center'>
  152. <div style='overflow-y: scroll; height: 580px; padding:20px'>\n");
  153. $this->_renderPages();
  154. $this->_appendToOutput("\t</div></td>
  155. <td width='20%' id='right_pannel' valign='top' align='center'>
  156. <div style='overflow-y: scroll; height: 580px; padding: 10px;'>\n");
  157. for($i=1;$i<=$this->_totalPages;++$i){
  158. $this->_appendToOutput($this->_getPageThumbnail($i));
  159. }
  160. $this->_appendToOutput("</div></td></tr></table>\n");
  161. } else {
  162. $this->_renderPages();
  163. }
  164. $this->_prepareFooter();
  165. return $this->_output;
  166. }
  167. /**
  168. * Inicia el reporte
  169. *
  170. * @param boolean $implicitFlush
  171. */
  172. protected function _start($implicitFlush=false){
  173. $this->_implicitFlush = $implicitFlush;
  174. if($this->_implicitFlush==true){
  175. $this->_tempFileName = 'public/temp/'.uniqid();
  176. $this->_tempFile = fopen($this->_tempFileName, 'w');
  177. $this->_prepareHead();
  178. $this->_renderHeader();
  179. $this->_appendToOutput("\t<p><table cellspacing='0' align='center'>\n");
  180. $this->_renderColumnHeaders();
  181. }
  182. $this->_started = true;
  183. }
  184. /**
  185. * Finalizar el reporte
  186. *
  187. */
  188. protected function _finish(){
  189. if($this->_implicitFlush==true){
  190. $this->_renderTotals();
  191. $this->_appendToOutput("\t</table></p>\n");
  192. $this->_prepareFooter();
  193. }
  194. }
  195. /**
  196. * Genera el encabezado del reporte
  197. *
  198. */
  199. private function _prepareHead(){
  200. if($this->_started==false){
  201. $output = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
  202. $output.= "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
  203. $output.= "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n";
  204. $output.= "\t<head>\n";
  205. $output.= "\t\t<meta http-equiv='Content-type' content='text/html; charset=".$this->getEncoding()."' />\n";
  206. $output.= "\t\t<meta http-equiv='Pragma' CONTENT='no-cache' />\n";
  207. $output.= "\t\t<meta http-equiv='Cache-Control' CONTENT='no-cache' />\n";
  208. $output.= "\t\t<title>".$this->getDocumentTitle()."</title>\n";
  209. $output.= "\t\t<style type='text/css'>\n";
  210. $output.= "\t\t\tbody, div, td, th { font-family: \"".self::$_defaultFontFamily."\"; font-size: ".self::$_defaultFontSize."px; }\n";
  211. $output.= "\t\t\ttable { border-right: 1px solid #969696;border-top: 1px solid #969696;}\n";
  212. $output.= "\t\t\tth, td { border-left: 1px solid #969696;border-bottom: 1px solid #969696;}\n";
  213. if($this->getDisplayMode()==Report::DISPLAY_PRINT_PREVIEW){
  214. $output.= "\t\t\tbody { background: #333333; margin: 0px; padding: 0px; }\n";
  215. $output.= "\t\t\t.page { padding: 20px; width: 550px; height: 700px; border: 1px solid #fafafa; background: #ffffff; margin: 10px;}\n";
  216. $output.= "\t\t\t#preview { background: #676767; border: none; }\n";
  217. $output.= "\t\t\t#right_pannel { background: #fafafa; }\n";
  218. if(Browser::isWebKit()==true){
  219. $output.= "\t\t\t.thumbnail td { height: 2px; border: 1px solid #eaeaea; }\n";
  220. $output.= "\t\t\t.thumbnail th { height: 2px; border: 1px solid #eaeaea; }\n";
  221. } else {
  222. $output.= "\t\t\t.thumbnail td { height: 4px; border: 1px solid #eaeaea; }\n";
  223. $output.= "\t\t\t.thumbnail th { height: 4px; border: 1px solid #eaeaea; }\n";
  224. }
  225. $output.= "\t\t\t.thumbnail a { color: #676767; font-size: 11px; text-decoration:none; }\n";
  226. $output.= "\t\t\t.thumbnail { float: left; margin: 10px; border: 1px solid #eaeaea; background: #ffffff; padding: 5px; width: 50px; }\n";
  227. $this->setPagination(true);
  228. } else {
  229. $output.= "\t\t\tbody { margin: 0px; padding: 0px; }\n";
  230. $output.= "\t\t\t.page { padding: 20px; max-width: 850px; height: 700px; border: 1px solid #fafafa; background: #ffffff; }\n";
  231. }
  232. $this->_appendToOutput($output);
  233. $this->_prepareCellHeaderStyle();
  234. $this->_prepareColumnStyles();
  235. $this->_prepareColumnFormats();
  236. $this->_prepareTotalizedColumns();
  237. $this->_appendToOutput("\t\t</style>\n");
  238. $this->_appendToOutput("\t</head>\n");
  239. $this->_appendToOutput("\t<body>\n");
  240. }
  241. }
  242. protected function _prepareFooter(){
  243. $this->_appendToOutput("\t</body>\n");
  244. $this->_appendToOutput("</html>\n");
  245. }
  246. /**
  247. * Agrega una cadena al reporte
  248. *
  249. * @param string $output
  250. */
  251. private function _appendToOutput($output){
  252. if($this->_implicitFlush==true){
  253. fwrite($this->_tempFile, $output);
  254. } else {
  255. $this->_output = $output;
  256. }
  257. }
  258. /**
  259. * Escribe los estilos de los encabezados del reporte
  260. *
  261. * @access protected
  262. */
  263. protected function _prepareCellHeaderStyle(){
  264. $style = $this->getCellHeaderStyle();
  265. if($style!==null){
  266. $preparedStyle = $this->_prepareStyle($style->getStyles());
  267. $this->_appendToOutput("\t\t\tth { ".join(";", $preparedStyle)."; }\n");
  268. }
  269. }
  270. /**
  271. * Obtiene los formatos asignados a las columnas del reporte
  272. *
  273. * @access protected
  274. */
  275. protected function _prepareColumnFormats(){
  276. $this->_columnFormats = $this->getColumnFormats();
  277. }
  278. /**
  279. * Obtiene los valores base de totales de las columnas
  280. *
  281. * @access protected
  282. */
  283. protected function _prepareTotalizedColumns(){
  284. $this->_totalizeValues = $this->getTotalizeValues();
  285. }
  286. /**
  287. * Escribe los estilos de las columnas del reporte
  288. *
  289. * @access protected
  290. */
  291. protected function _prepareColumnStyles(){
  292. $styles = $this->getColumnStyles();
  293. $rowHeight = 0;
  294. $offsetRatio = $this->_getOffsetRatio(self::$_defaultFontFamily, self::$_defaultFontSize);
  295. $fontSizeRatio = ceil((self::$_defaultFontSize+4)*$offsetRatio);
  296. if(count($styles)){
  297. foreach($styles as $numberColumn => $style){
  298. $columnStyle = $style->getStyles();
  299. if(isset($columnStyle['fontSize'])){
  300. if(isset($columnStyle['fontFamily'])){
  301. $offsetRatio = $this->_getOffsetRatio($columnStyle['fontFamily'], $columnStyle['fontSize']);
  302. } else {
  303. $offsetRatio = $this->_getOffsetRatio(self::$_defaultFontFamily, $columnStyle['fontSize']);
  304. }
  305. $fontSize = ceil(($columnStyle['fontSize']+4)*$offsetRatio);
  306. if($rowHeight==0){
  307. $rowHeight = $fontSize;
  308. } else {
  309. if($fontSize>$rowHeight){
  310. $rowHeight = $fontSize;
  311. }
  312. }
  313. } else {
  314. $rowHeight = $fontSizeRatio;
  315. }
  316. $preparedStyle = $this->_prepareStyle($columnStyle);
  317. $this->_appendToOutput("\t\t\t.c$numberColumn { ".join(";", $preparedStyle)."; }\n");
  318. }
  319. if($this->_rowHeight==0){
  320. $this->_rowHeight = $rowHeight;
  321. } else {
  322. if($rowHeight>$this->_rowHeight){
  323. $this->_rowHeight = $rowHeight;
  324. }
  325. }
  326. } else {
  327. $this->_rowHeight = $fontSizeRatio;
  328. }
  329. }
  330. public function _prepareStyle($attributes){
  331. $style = array();
  332. foreach($attributes as $attributeName => $value){
  333. switch($attributeName){
  334. case 'fontSize':
  335. $style[] = 'font-size:'.$value.'px';
  336. break;
  337. case 'fontWeight':
  338. $style[] = 'font-weight:'.$value;
  339. break;
  340. case 'textAlign':
  341. $style[] = 'text-align:'.$value;
  342. break;
  343. case 'color':
  344. $style[] = 'color:'.$value;
  345. break;
  346. case 'borderColor':
  347. $style[] = 'border:1px solid '.$value;
  348. break;
  349. case 'backgroundColor':
  350. $style[] = 'background-color:'.$value;
  351. break;
  352. case 'paddingRight':
  353. $style[] = 'padding-right:'.$value;
  354. break;
  355. }
  356. }
  357. return $style;
  358. }
  359. /**
  360. * Renderiza el encabezado del documento
  361. *
  362. */
  363. protected function _renderHeader(){
  364. $header = $this->getHeader();
  365. $headerHeight = 0;
  366. if(is_array($header)){
  367. foreach($header as $item){
  368. $style = $this->_renderItem($item);
  369. if(isset($style['fontSize'])){
  370. $headerHeight+=$style['fontSize']+4;
  371. } else {
  372. $headerHeight+=15;
  373. }
  374. }
  375. } else {
  376. $style = $this->_renderItem($item);
  377. if(isset($style['fontSize'])){
  378. $headerHeight+=$style['fontSize']+4;
  379. } else {
  380. $headerHeight+=15;
  381. }
  382. }
  383. $this->_headerHeight = $headerHeight;
  384. }
  385. /**
  386. * Renderiza un item
  387. *
  388. * @param mixed $item
  389. * @return array
  390. */
  391. protected function _renderItem($item){
  392. if(is_string($item)){
  393. $this->_appendToOutput($item);
  394. return;
  395. }
  396. if(is_object($item)==true){
  397. if(get_class($item)=="ReportText"){
  398. $html = "\t\t\t<div ";
  399. $itemStyle = $item->getAttributes();
  400. $style = $this->_prepareStyle($itemStyle);
  401. if(count($style)){
  402. $html.="style='".join(";", $style)."'";
  403. }
  404. $html.=">".$this->_prepareText($item->getText())."</div>\n";
  405. $this->_appendToOutput($html);
  406. return $itemStyle;
  407. }
  408. }
  409. }
  410. /**
  411. * Escribe los encabezados del reporte
  412. *
  413. */
  414. private function _renderColumnHeaders(){
  415. $output = "\t\t\t<thead>\n";
  416. foreach($this->getColumnHeaders() as $header){
  417. $output.="\t\t\t\t<th>$header</th>\n";
  418. }
  419. $output.="\t\t\t</thead>\n";
  420. $this->_appendToOutput($output);
  421. }
  422. /**
  423. * Crea un thumbnail
  424. *
  425. * @param int $pageNumber
  426. * @return string
  427. */
  428. private function _getPageThumbnail($pageNumber){
  429. $numColumns = count($this->getColumnHeaders());
  430. if($numColumns==0||$numColumns>6){
  431. $numColumns = 4;
  432. }
  433. $code = "<div class='thumbnail' align='center'>
  434. <a href='#$pageNumber'>
  435. <table cellspacing='0' cellpadding='0' width='50'><tr>";
  436. for($i=0;$i<$numColumns;++$i){
  437. $code.="<th></th>";
  438. }
  439. $code.="</tr>";
  440. for($j=0;$j<9;++$j){
  441. $code.="<tr>";
  442. for($i=0;$i<$numColumns;++$i){
  443. $code.="<td></td>";
  444. }
  445. $code.="</tr>";
  446. }
  447. $code.="</table>$pageNumber</a></div>";
  448. return $code;
  449. }
  450. /**
  451. * Escribe las páginas del reporte
  452. *
  453. * @param array $rows
  454. */
  455. private function _renderRows($rows){
  456. foreach($rows as $row){
  457. $this->_renderRow($row);
  458. }
  459. }
  460. /**
  461. * Agrega una fila al reporte en implicit flush
  462. *
  463. * @param array $row
  464. */
  465. protected function _addRow($row){
  466. $this->_renderRow($row);
  467. }
  468. /**
  469. * Escribe una fila del reporte
  470. *
  471. * @param array $row
  472. */
  473. private function _renderRow($row){
  474. $output = "\t\t\t<tr>\n";
  475. if($row['_type']=='normal'){
  476. unset($row['_type']);
  477. if($this->_numberColumns===null){
  478. $this->_numberColumns = count($row);
  479. }
  480. foreach($row as $numberColumn => $value){
  481. if(isset($this->_totalizeColumns[$numberColumn])){
  482. if(!isset($this->_totalizeValues[$numberColumn])){
  483. $this->_totalizeValues[$numberColumn] = 0;
  484. }
  485. $this->_totalizeValues[$numberColumn]+=$value;
  486. }
  487. if(isset($this->_columnFormats[$numberColumn])){
  488. $value = $this->_columnFormats[$numberColumn]->apply($value);
  489. }
  490. $output.="\t\t\t\t<td class='c$numberColumn'>$value</td>\n";
  491. }
  492. } else {
  493. if($row['_type']=='raw'){
  494. unset($row['_type']);
  495. foreach($row as $numberColumn => $rawColumn){
  496. $output.="\t\t\t\t<td colspan='".$rawColumn->getSpan()."'";
  497. $styles = $rawColumn->getStyle();
  498. if($styles){
  499. $style = $this->_prepareStyle($styles);
  500. $output.=" style='".join(';', $style)."' ";
  501. }
  502. $output.=">".$rawColumn->getValue()."</td>\n";
  503. unset($rawColumn);
  504. }
  505. }
  506. }
  507. $output.="\t\t\t</tr>\n";
  508. $this->_appendToOutput($output);
  509. }
  510. /**
  511. * Escribe las páginas del reporte
  512. *
  513. */
  514. private function _renderPages(){
  515. $output = '';
  516. $data = $this->getRows();
  517. if($this->getPagination()==true){
  518. $calculatedOffset = $this->getRowsPerPage();
  519. $renderRows = 0;
  520. $numberRows = count($data);
  521. if($calculatedOffset!=0){
  522. $rowsToRender = $calculatedOffset;
  523. } else {
  524. $calculatedOffset = -1;
  525. $rowsToRender = 0;
  526. }
  527. $pageNumber = 1;
  528. if($numberRows>0){
  529. while($renderRows<$numberRows){
  530. $this->_appendToOutput("\t\t<div align='center'><div class='page'><a name='$pageNumber'>\n");
  531. $this->_renderHeader();
  532. if($calculatedOffset==-1){
  533. $calculatedOffset = ceil(($this->_rowHeight*$numberRows+$this->_headerHeight+20)/700);
  534. $rowsToRender = floor($numberRows/$calculatedOffset);
  535. }
  536. $this->_appendToOutput("\t<p><table cellspacing='0'>\n");
  537. $this->_renderColumnHeaders();
  538. $this->_renderRows(array_slice($data, $renderRows, $rowsToRender));
  539. $this->_renderTotals();
  540. $this->_appendToOutput("\t</table></p>\n");
  541. $this->_appendToOutput("\t\t</div></div>\n");
  542. $renderRows+=$rowsToRender;
  543. ++$pageNumber;
  544. $this->_setPageNumber($pageNumber);
  545. }
  546. } else {
  547. $this->_appendToOutput("\t\t<div align='center'><div class='page'><a name='$pageNumber'>\n");
  548. $this->_renderHeader();
  549. $this->_appendToOutput("\t<p><table cellspacing='0'>\n");
  550. $this->_renderColumnHeaders();
  551. $this->_appendToOutput("\t</table></p>\n");
  552. $this->_appendToOutput("\t\t</div></div>\n");
  553. ++$pageNumber;
  554. $this->_setPageNumber($pageNumber);
  555. }
  556. $this->_totalPages = $pageNumber;
  557. } else {
  558. $this->_renderHeader();
  559. $this->_appendToOutput("\t<p><table cellspacing='0' align='center'>\n");
  560. $this->_renderColumnHeaders();
  561. $this->_renderRows($data);
  562. $this->_renderTotals();
  563. $this->_appendToOutput("\t</table></p>\n");
  564. }
  565. }
  566. /**
  567. * Renombra el archivo temporal del volcado al nombre dado por el usuario
  568. *
  569. * @param string $path
  570. * @return boolean
  571. */
  572. protected function _moveOutputTo($path){
  573. if(rename($this->_tempFileName, $path)){
  574. return basename('/'.$path);
  575. }
  576. }
  577. /**
  578. * Visualiza los totales del reporte
  579. *
  580. */
  581. private function _renderTotals(){
  582. if(count($this->_totalizeValues)>0){
  583. $output = '<tr>';
  584. for($i=0;$i<$this->_numberColumns;++$i){
  585. if(isset($this->_totalizeValues[$i])){
  586. if(isset($this->_columnFormats[$i])){
  587. $this->_totalizeValues[$i] = $this->_columnFormats[$i]->apply($this->_totalizeValues[$i]);
  588. }
  589. $output.='<td class="c'.$i.'">'.$this->_totalizeValues[$i].'</td>';
  590. } else {
  591. $output.='<td class="c'.$i.'"></td>';
  592. }
  593. }
  594. $output.='</tr>';
  595. $this->_appendToOutput($output);
  596. }
  597. }
  598. /**
  599. * Busca el offsetRatio de la fuente y el tamańo
  600. *
  601. * @param string $fontFamily
  602. * @param int $fontSize
  603. */
  604. private function _getOffsetRatio($fontFamily, $fontSize){
  605. if(isset(self::$_offsetRatio[$fontFamily])==false){
  606. throw new ReportException("No existe el offsetRatio para la fuente '$fontFamily', debe establecer manualmente el número de registros por página");
  607. }
  608. if(isset(self::$_offsetRatio[$fontFamily][$fontSize])==false){
  609. for($i=$fontSize;$i>=0;--$i){
  610. if(isset(self::$_offsetRatio[$fontFamily][$i])){
  611. if($fontSize-$i<=5){
  612. return self::$_offsetRatio[$fontFamily][$i]-(0.1*($i-$fontSize));
  613. } else {
  614. throw new ReportException("No existe el offsetRatio para la fuente '$fontFamily' tamańo '$fontSize', debe establecer manualmente el número de registros por página");
  615. }
  616. }
  617. }
  618. for($i=$fontSize;$i<=128;++$i){
  619. if(isset(self::$_offsetRatio[$fontFamily][$i])){
  620. if($i-$fontSize<=6){
  621. return self::$_offsetRatio[$fontFamily][$i]+(0.1*($i-$fontSize));
  622. } else {
  623. throw new ReportException("No existe el offsetRatio para la fuente '$fontFamily' tamańo '$fontSize', debe establecer manualmente el número de registros por página");
  624. }
  625. }
  626. }
  627. } else {
  628. return self::$_offsetRatio[$fontFamily][$fontSize];
  629. }
  630. }
  631. /**
  632. * Devuelve la extension del archivo recomendada
  633. *
  634. * @return string
  635. */
  636. protected function getFileExtension(){
  637. return 'html';
  638. }
  639. }