Pdf.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909
  1. <?php
  2. /**
  3. * PhpMyAdmin\Plugins\Export\Helpers\Pdf class
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin\Plugins\Export\Helpers;
  7. use PhpMyAdmin\DatabaseInterface;
  8. use PhpMyAdmin\Pdf as PdfLib;
  9. use PhpMyAdmin\Relation;
  10. use PhpMyAdmin\Transformations;
  11. use PhpMyAdmin\Util;
  12. use TCPDF_STATIC;
  13. use function array_key_exists;
  14. use function count;
  15. use function ksort;
  16. use function stripos;
  17. /**
  18. * Adapted from a LGPL script by Philip Clarke
  19. */
  20. class Pdf extends PdfLib
  21. {
  22. /** @var array */
  23. public $tablewidths;
  24. /** @var array */
  25. public $headerset;
  26. /** @var int|float */
  27. private $dataY;
  28. /** @var int|float */
  29. private $cellFontSize;
  30. /** @var mixed */
  31. private $titleFontSize;
  32. /** @var mixed */
  33. private $titleText;
  34. /** @var mixed */
  35. private $dbAlias;
  36. /** @var mixed */
  37. private $tableAlias;
  38. /** @var mixed */
  39. private $purpose;
  40. /** @var array */
  41. private $colTitles;
  42. /** @var mixed */
  43. private $results;
  44. /** @var array */
  45. private $colAlign;
  46. /** @var mixed */
  47. private $titleWidth;
  48. /** @var mixed */
  49. private $colFits;
  50. /** @var array */
  51. private $displayColumn;
  52. /** @var int */
  53. private $numFields;
  54. /** @var array */
  55. private $fields;
  56. /** @var int|float */
  57. private $sColWidth;
  58. /** @var mixed */
  59. private $currentDb;
  60. /** @var mixed */
  61. private $currentTable;
  62. /** @var array */
  63. private $aliases;
  64. /** @var Relation */
  65. private $relation;
  66. /** @var Transformations */
  67. private $transformations;
  68. /**
  69. * Constructs PDF and configures standard parameters.
  70. *
  71. * @param string $orientation page orientation
  72. * @param string $unit unit
  73. * @param string $format the format used for pages
  74. * @param bool $unicode true means that the input text is unicode
  75. * @param string $encoding charset encoding; default is UTF-8.
  76. * @param bool $diskcache if true reduce the RAM memory usage by caching
  77. * temporary data on filesystem (slower).
  78. * @param bool $pdfa If TRUE set the document to PDF/A mode.
  79. *
  80. * @access public
  81. */
  82. public function __construct(
  83. $orientation = 'P',
  84. $unit = 'mm',
  85. $format = 'A4',
  86. $unicode = true,
  87. $encoding = 'UTF-8',
  88. $diskcache = false,
  89. $pdfa = false
  90. ) {
  91. global $dbi;
  92. parent::__construct(
  93. $orientation,
  94. $unit,
  95. $format,
  96. $unicode,
  97. $encoding,
  98. $diskcache,
  99. $pdfa
  100. );
  101. $this->relation = new Relation($dbi);
  102. $this->transformations = new Transformations();
  103. }
  104. /**
  105. * Add page if needed.
  106. *
  107. * @param float|int $h cell height. Default value: 0
  108. * @param mixed $y starting y position, leave empty for current
  109. * position
  110. * @param bool $addpage if true add a page, otherwise only return
  111. * the true/false state
  112. *
  113. * @return bool true in case of page break, false otherwise.
  114. */
  115. public function checkPageBreak($h = 0, $y = '', $addpage = true)
  116. {
  117. if (TCPDF_STATIC::empty_string($y)) {
  118. $y = $this->y;
  119. }
  120. $current_page = $this->page;
  121. if (($y + $h > $this->PageBreakTrigger)
  122. && (! $this->InFooter)
  123. && $this->AcceptPageBreak()
  124. ) {
  125. if ($addpage) {
  126. //Automatic page break
  127. $x = $this->x;
  128. $this->AddPage($this->CurOrientation);
  129. $this->y = $this->dataY;
  130. $oldpage = $this->page - 1;
  131. $this_page_orm = $this->pagedim[$this->page]['orm'];
  132. $old_page_orm = $this->pagedim[$oldpage]['orm'];
  133. $this_page_olm = $this->pagedim[$this->page]['olm'];
  134. $old_page_olm = $this->pagedim[$oldpage]['olm'];
  135. if ($this->rtl) {
  136. if ($this_page_orm != $old_page_orm) {
  137. $this->x = $x - ($this_page_orm - $old_page_orm);
  138. } else {
  139. $this->x = $x;
  140. }
  141. } else {
  142. if ($this_page_olm != $old_page_olm) {
  143. $this->x = $x + $this_page_olm - $old_page_olm;
  144. } else {
  145. $this->x = $x;
  146. }
  147. }
  148. }
  149. return true;
  150. }
  151. // account for columns mode
  152. return $current_page != $this->page;
  153. }
  154. /**
  155. * This method is used to render the page header.
  156. *
  157. * @return void
  158. */
  159. // @codingStandardsIgnoreLine
  160. public function Header()
  161. {
  162. global $maxY;
  163. // We don't want automatic page breaks while generating header
  164. // as this can lead to infinite recursion as auto generated page
  165. // will want header as well causing another page break
  166. // FIXME: Better approach might be to try to compact the content
  167. $this->SetAutoPageBreak(false);
  168. // Check if header for this page already exists
  169. if (! isset($this->headerset[$this->page])) {
  170. $this->SetY($this->tMargin - ($this->FontSizePt / $this->k) * 5);
  171. $this->cellFontSize = $this->FontSizePt;
  172. $this->SetFont(
  173. PdfLib::PMA_PDF_FONT,
  174. '',
  175. ($this->titleFontSize
  176. ?: $this->FontSizePt)
  177. );
  178. $this->Cell(0, $this->FontSizePt, $this->titleText, 0, 1, 'C');
  179. $this->SetFont(PdfLib::PMA_PDF_FONT, '', $this->cellFontSize);
  180. $this->SetY($this->tMargin - ($this->FontSizePt / $this->k) * 2.5);
  181. $this->Cell(
  182. 0,
  183. $this->FontSizePt,
  184. __('Database:') . ' ' . $this->dbAlias . ', '
  185. . __('Table:') . ' ' . $this->tableAlias . ', '
  186. . __('Purpose:') . ' ' . $this->purpose,
  187. 0,
  188. 1,
  189. 'L'
  190. );
  191. $l = $this->lMargin;
  192. foreach ($this->colTitles as $col => $txt) {
  193. $this->SetXY($l, $this->tMargin);
  194. $this->MultiCell(
  195. $this->tablewidths[$col],
  196. $this->FontSizePt,
  197. $txt
  198. );
  199. $l += $this->tablewidths[$col];
  200. $maxY = $maxY < $this->GetY() ? $this->GetY() : $maxY;
  201. }
  202. $this->SetXY($this->lMargin, $this->tMargin);
  203. $this->SetFillColor(200, 200, 200);
  204. $l = $this->lMargin;
  205. foreach ($this->colTitles as $col => $txt) {
  206. $this->SetXY($l, $this->tMargin);
  207. $this->Cell(
  208. $this->tablewidths[$col],
  209. $maxY - $this->tMargin,
  210. '',
  211. 1,
  212. 0,
  213. 'L',
  214. 1
  215. );
  216. $this->SetXY($l, $this->tMargin);
  217. $this->MultiCell(
  218. $this->tablewidths[$col],
  219. $this->FontSizePt,
  220. $txt,
  221. 0,
  222. 'C'
  223. );
  224. $l += $this->tablewidths[$col];
  225. }
  226. $this->SetFillColor(255, 255, 255);
  227. // set headerset
  228. $this->headerset[$this->page] = 1;
  229. }
  230. $this->dataY = $maxY;
  231. $this->SetAutoPageBreak(true);
  232. }
  233. /**
  234. * Generate table
  235. *
  236. * @param int $lineheight Height of line
  237. *
  238. * @return void
  239. */
  240. public function morepagestable($lineheight = 8)
  241. {
  242. global $dbi;
  243. // some things to set and 'remember'
  244. $l = $this->lMargin;
  245. $startheight = $h = $this->dataY;
  246. $startpage = $currpage = $this->page;
  247. // calculate the whole width
  248. $fullwidth = 0;
  249. foreach ($this->tablewidths as $width) {
  250. $fullwidth += $width;
  251. }
  252. // Now let's start to write the table
  253. $row = 0;
  254. $tmpheight = [];
  255. $maxpage = $this->page;
  256. while ($data = $dbi->fetchRow($this->results)) {
  257. $this->page = $currpage;
  258. // write the horizontal borders
  259. $this->Line($l, $h, $fullwidth + $l, $h);
  260. // write the content and remember the height of the highest col
  261. foreach ($data as $col => $txt) {
  262. $this->page = $currpage;
  263. $this->SetXY($l, $h);
  264. if ($this->tablewidths[$col] > 0) {
  265. $this->MultiCell(
  266. $this->tablewidths[$col],
  267. $lineheight,
  268. $txt,
  269. 0,
  270. $this->colAlign[$col]
  271. );
  272. $l += $this->tablewidths[$col];
  273. }
  274. if (! isset($tmpheight[$row . '-' . $this->page])) {
  275. $tmpheight[$row . '-' . $this->page] = 0;
  276. }
  277. if ($tmpheight[$row . '-' . $this->page] < $this->GetY()) {
  278. $tmpheight[$row . '-' . $this->page] = $this->GetY();
  279. }
  280. if ($this->page > $maxpage) {
  281. $maxpage = $this->page;
  282. }
  283. unset($data[$col]);
  284. }
  285. // get the height we were in the last used page
  286. $h = $tmpheight[$row . '-' . $maxpage];
  287. // set the "pointer" to the left margin
  288. $l = $this->lMargin;
  289. // set the $currpage to the last page
  290. $currpage = $maxpage;
  291. unset($data[$row]);
  292. $row++;
  293. }
  294. // draw the borders
  295. // we start adding a horizontal line on the last page
  296. $this->page = $maxpage;
  297. $this->Line($l, $h, $fullwidth + $l, $h);
  298. // now we start at the top of the document and walk down
  299. for ($i = $startpage; $i <= $maxpage; $i++) {
  300. $this->page = $i;
  301. $l = $this->lMargin;
  302. $t = $i == $startpage ? $startheight : $this->tMargin;
  303. $lh = $i == $maxpage ? $h : $this->h - $this->bMargin;
  304. $this->Line($l, $t, $l, $lh);
  305. foreach ($this->tablewidths as $width) {
  306. $l += $width;
  307. $this->Line($l, $t, $l, $lh);
  308. }
  309. }
  310. // set it to the last page, if not it'll cause some problems
  311. $this->page = $maxpage;
  312. }
  313. /**
  314. * Sets a set of attributes.
  315. *
  316. * @param array $attr array containing the attributes
  317. *
  318. * @return void
  319. */
  320. public function setAttributes(array $attr = [])
  321. {
  322. foreach ($attr as $key => $val) {
  323. $this->$key = $val;
  324. }
  325. }
  326. /**
  327. * Defines the top margin.
  328. * The method can be called before creating the first page.
  329. *
  330. * @param float $topMargin the margin
  331. *
  332. * @return void
  333. */
  334. public function setTopMargin($topMargin)
  335. {
  336. $this->tMargin = $topMargin;
  337. }
  338. /**
  339. * Prints triggers
  340. *
  341. * @param string $db database name
  342. * @param string $table table name
  343. *
  344. * @return void
  345. */
  346. public function getTriggers($db, $table)
  347. {
  348. global $dbi;
  349. $triggers = $dbi->getTriggers($db, $table);
  350. if ($triggers === []) {
  351. return; //prevents printing blank trigger list for any table
  352. }
  353. unset(
  354. $this->tablewidths,
  355. $this->colTitles,
  356. $this->titleWidth,
  357. $this->colFits,
  358. $this->displayColumn,
  359. $this->colAlign
  360. );
  361. /**
  362. * Making table heading
  363. * Keeping column width constant
  364. */
  365. $this->colTitles[0] = __('Name');
  366. $this->tablewidths[0] = 90;
  367. $this->colTitles[1] = __('Time');
  368. $this->tablewidths[1] = 80;
  369. $this->colTitles[2] = __('Event');
  370. $this->tablewidths[2] = 40;
  371. $this->colTitles[3] = __('Definition');
  372. $this->tablewidths[3] = 240;
  373. for ($columns_cnt = 0; $columns_cnt < 4; $columns_cnt++) {
  374. $this->colAlign[$columns_cnt] = 'L';
  375. $this->displayColumn[$columns_cnt] = true;
  376. }
  377. // Starting to fill table with required info
  378. $this->SetY($this->tMargin);
  379. $this->AddPage();
  380. $this->SetFont(PdfLib::PMA_PDF_FONT, '', 9);
  381. $l = $this->lMargin;
  382. $startheight = $h = $this->dataY;
  383. $startpage = $currpage = $this->page;
  384. // calculate the whole width
  385. $fullwidth = 0;
  386. foreach ($this->tablewidths as $width) {
  387. $fullwidth += $width;
  388. }
  389. $row = 0;
  390. $tmpheight = [];
  391. $maxpage = $this->page;
  392. $data = [];
  393. foreach ($triggers as $trigger) {
  394. $data[] = $trigger['name'];
  395. $data[] = $trigger['action_timing'];
  396. $data[] = $trigger['event_manipulation'];
  397. $data[] = $trigger['definition'];
  398. $this->page = $currpage;
  399. // write the horizontal borders
  400. $this->Line($l, $h, $fullwidth + $l, $h);
  401. // write the content and remember the height of the highest col
  402. foreach ($data as $col => $txt) {
  403. $this->page = $currpage;
  404. $this->SetXY($l, $h);
  405. if ($this->tablewidths[$col] > 0) {
  406. $this->MultiCell(
  407. $this->tablewidths[$col],
  408. $this->FontSizePt,
  409. $txt,
  410. 0,
  411. $this->colAlign[$col]
  412. );
  413. $l += $this->tablewidths[$col];
  414. }
  415. if (! isset($tmpheight[$row . '-' . $this->page])) {
  416. $tmpheight[$row . '-' . $this->page] = 0;
  417. }
  418. if ($tmpheight[$row . '-' . $this->page] < $this->GetY()) {
  419. $tmpheight[$row . '-' . $this->page] = $this->GetY();
  420. }
  421. if ($this->page <= $maxpage) {
  422. continue;
  423. }
  424. $maxpage = $this->page;
  425. }
  426. // get the height we were in the last used page
  427. $h = $tmpheight[$row . '-' . $maxpage];
  428. // set the "pointer" to the left margin
  429. $l = $this->lMargin;
  430. // set the $currpage to the last page
  431. $currpage = $maxpage;
  432. unset($data);
  433. $row++;
  434. }
  435. // draw the borders
  436. // we start adding a horizontal line on the last page
  437. $this->page = $maxpage;
  438. $this->Line($l, $h, $fullwidth + $l, $h);
  439. // now we start at the top of the document and walk down
  440. for ($i = $startpage; $i <= $maxpage; $i++) {
  441. $this->page = $i;
  442. $l = $this->lMargin;
  443. $t = $i == $startpage ? $startheight : $this->tMargin;
  444. $lh = $i == $maxpage ? $h : $this->h - $this->bMargin;
  445. $this->Line($l, $t, $l, $lh);
  446. foreach ($this->tablewidths as $width) {
  447. $l += $width;
  448. $this->Line($l, $t, $l, $lh);
  449. }
  450. }
  451. // set it to the last page, if not it'll cause some problems
  452. $this->page = $maxpage;
  453. }
  454. /**
  455. * Print $table's CREATE definition
  456. *
  457. * @param string $db the database name
  458. * @param string $table the table name
  459. * @param bool $do_relation whether to include relation comments
  460. * @param bool $do_comments whether to include the pmadb-style column
  461. * comments as comments in the structure;
  462. * this is deprecated but the parameter is
  463. * left here because /export calls
  464. * PMA_exportStructure() also for other
  465. * export types which use this parameter
  466. * @param bool $do_mime whether to include mime comments
  467. * @param bool $view whether we're handling a view
  468. * @param array $aliases aliases of db/table/columns
  469. *
  470. * @return void
  471. */
  472. public function getTableDef(
  473. $db,
  474. $table,
  475. $do_relation,
  476. $do_comments,
  477. $do_mime,
  478. $view = false,
  479. array $aliases = []
  480. ) {
  481. global $dbi;
  482. // set $cfgRelation here, because there is a chance that it's modified
  483. // since the class initialization
  484. global $cfgRelation;
  485. unset(
  486. $this->tablewidths,
  487. $this->colTitles,
  488. $this->titleWidth,
  489. $this->colFits,
  490. $this->displayColumn,
  491. $this->colAlign
  492. );
  493. /**
  494. * Gets fields properties
  495. */
  496. $dbi->selectDb($db);
  497. /**
  498. * All these three checks do_relation, do_comment and do_mime is
  499. * not required. As presently all are set true by default.
  500. * But when, methods to take user input will be developed,
  501. * it will be of use
  502. */
  503. // Check if we can use Relations
  504. if ($do_relation) {
  505. // Find which tables are related with the current one and write it in
  506. // an array
  507. $res_rel = $this->relation->getForeigners($db, $table);
  508. $have_rel = ! empty($res_rel);
  509. } else {
  510. $have_rel = false;
  511. }
  512. //column count and table heading
  513. $this->colTitles[0] = __('Column');
  514. $this->tablewidths[0] = 90;
  515. $this->colTitles[1] = __('Type');
  516. $this->tablewidths[1] = 80;
  517. $this->colTitles[2] = __('Null');
  518. $this->tablewidths[2] = 40;
  519. $this->colTitles[3] = __('Default');
  520. $this->tablewidths[3] = 120;
  521. for ($columns_cnt = 0; $columns_cnt < 4; $columns_cnt++) {
  522. $this->colAlign[$columns_cnt] = 'L';
  523. $this->displayColumn[$columns_cnt] = true;
  524. }
  525. if ($do_relation && $have_rel) {
  526. $this->colTitles[$columns_cnt] = __('Links to');
  527. $this->displayColumn[$columns_cnt] = true;
  528. $this->colAlign[$columns_cnt] = 'L';
  529. $this->tablewidths[$columns_cnt] = 120;
  530. $columns_cnt++;
  531. }
  532. if ($do_comments /*&& $cfgRelation['commwork']*/) {
  533. $this->colTitles[$columns_cnt] = __('Comments');
  534. $this->displayColumn[$columns_cnt] = true;
  535. $this->colAlign[$columns_cnt] = 'L';
  536. $this->tablewidths[$columns_cnt] = 120;
  537. $columns_cnt++;
  538. }
  539. if ($do_mime && $cfgRelation['mimework']) {
  540. $this->colTitles[$columns_cnt] = __('Media type');
  541. $this->displayColumn[$columns_cnt] = true;
  542. $this->colAlign[$columns_cnt] = 'L';
  543. $this->tablewidths[$columns_cnt] = 120;
  544. $columns_cnt++;
  545. }
  546. // Starting to fill table with required info
  547. $this->SetY($this->tMargin);
  548. $this->AddPage();
  549. $this->SetFont(PdfLib::PMA_PDF_FONT, '', 9);
  550. // Now let's start to write the table structure
  551. if ($do_comments) {
  552. $comments = $this->relation->getComments($db, $table);
  553. }
  554. if ($do_mime && $cfgRelation['mimework']) {
  555. $mime_map = $this->transformations->getMime($db, $table, true);
  556. }
  557. $columns = $dbi->getColumns($db, $table);
  558. // some things to set and 'remember'
  559. $l = $this->lMargin;
  560. $startheight = $h = $this->dataY;
  561. $startpage = $currpage = $this->page;
  562. // calculate the whole width
  563. $fullwidth = 0;
  564. foreach ($this->tablewidths as $width) {
  565. $fullwidth += $width;
  566. }
  567. $row = 0;
  568. $tmpheight = [];
  569. $maxpage = $this->page;
  570. $data = [];
  571. // fun begin
  572. foreach ($columns as $column) {
  573. $extracted_columnspec
  574. = Util::extractColumnSpec($column['Type']);
  575. $type = $extracted_columnspec['print_type'];
  576. if (empty($type)) {
  577. $type = ' ';
  578. }
  579. if (! isset($column['Default'])) {
  580. if ($column['Null'] !== 'NO') {
  581. $column['Default'] = 'NULL';
  582. }
  583. }
  584. $data[] = $column['Field'];
  585. $data[] = $type;
  586. $data[] = $column['Null'] == '' || $column['Null'] === 'NO'
  587. ? 'No'
  588. : 'Yes';
  589. $data[] = $column['Default'] ?? '';
  590. $field_name = $column['Field'];
  591. if ($do_relation && $have_rel) {
  592. $data[] = isset($res_rel[$field_name])
  593. ? $res_rel[$field_name]['foreign_table']
  594. . ' (' . $res_rel[$field_name]['foreign_field']
  595. . ')'
  596. : '';
  597. }
  598. if ($do_comments) {
  599. $data[] = $comments[$field_name] ?? '';
  600. }
  601. if ($do_mime) {
  602. $data[] = isset($mime_map[$field_name])
  603. ? $mime_map[$field_name]['mimetype']
  604. : '';
  605. }
  606. $this->page = $currpage;
  607. // write the horizontal borders
  608. $this->Line($l, $h, $fullwidth + $l, $h);
  609. // write the content and remember the height of the highest col
  610. foreach ($data as $col => $txt) {
  611. $this->page = $currpage;
  612. $this->SetXY($l, $h);
  613. if ($this->tablewidths[$col] > 0) {
  614. $this->MultiCell(
  615. $this->tablewidths[$col],
  616. $this->FontSizePt,
  617. $txt,
  618. 0,
  619. $this->colAlign[$col]
  620. );
  621. $l += $this->tablewidths[$col];
  622. }
  623. if (! isset($tmpheight[$row . '-' . $this->page])) {
  624. $tmpheight[$row . '-' . $this->page] = 0;
  625. }
  626. if ($tmpheight[$row . '-' . $this->page] < $this->GetY()) {
  627. $tmpheight[$row . '-' . $this->page] = $this->GetY();
  628. }
  629. if ($this->page <= $maxpage) {
  630. continue;
  631. }
  632. $maxpage = $this->page;
  633. }
  634. // get the height we were in the last used page
  635. $h = $tmpheight[$row . '-' . $maxpage];
  636. // set the "pointer" to the left margin
  637. $l = $this->lMargin;
  638. // set the $currpage to the last page
  639. $currpage = $maxpage;
  640. unset($data);
  641. $row++;
  642. }
  643. // draw the borders
  644. // we start adding a horizontal line on the last page
  645. $this->page = $maxpage;
  646. $this->Line($l, $h, $fullwidth + $l, $h);
  647. // now we start at the top of the document and walk down
  648. for ($i = $startpage; $i <= $maxpage; $i++) {
  649. $this->page = $i;
  650. $l = $this->lMargin;
  651. $t = $i == $startpage ? $startheight : $this->tMargin;
  652. $lh = $i == $maxpage ? $h : $this->h - $this->bMargin;
  653. $this->Line($l, $t, $l, $lh);
  654. foreach ($this->tablewidths as $width) {
  655. $l += $width;
  656. $this->Line($l, $t, $l, $lh);
  657. }
  658. }
  659. // set it to the last page, if not it'll cause some problems
  660. $this->page = $maxpage;
  661. }
  662. /**
  663. * MySQL report
  664. *
  665. * @param string $query Query to execute
  666. *
  667. * @return void
  668. */
  669. public function mysqlReport($query)
  670. {
  671. global $dbi;
  672. unset(
  673. $this->tablewidths,
  674. $this->colTitles,
  675. $this->titleWidth,
  676. $this->colFits,
  677. $this->displayColumn,
  678. $this->colAlign
  679. );
  680. /**
  681. * Pass 1 for column widths
  682. */
  683. $this->results = $dbi->query(
  684. $query,
  685. DatabaseInterface::CONNECT_USER,
  686. DatabaseInterface::QUERY_UNBUFFERED
  687. );
  688. $this->numFields = $dbi->numFields($this->results);
  689. $this->fields = $dbi->getFieldsMeta($this->results);
  690. // sColWidth = starting col width (an average size width)
  691. $availableWidth = $this->w - $this->lMargin - $this->rMargin;
  692. $this->sColWidth = $availableWidth / $this->numFields;
  693. $totalTitleWidth = 0;
  694. // loop through results header and set initial
  695. // col widths/ titles/ alignment
  696. // if a col title is less than the starting col width,
  697. // reduce that column size
  698. $colFits = [];
  699. $titleWidth = [];
  700. for ($i = 0; $i < $this->numFields; $i++) {
  701. $col_as = $this->fields[$i]->name;
  702. $db = $this->currentDb;
  703. $table = $this->currentTable;
  704. if (! empty($this->aliases[$db]['tables'][$table]['columns'][$col_as])) {
  705. $col_as = $this->aliases[$db]['tables'][$table]['columns'][$col_as];
  706. }
  707. $stringWidth = $this->GetStringWidth($col_as) + 6;
  708. // save the real title's width
  709. $titleWidth[$i] = $stringWidth;
  710. $totalTitleWidth += $stringWidth;
  711. // set any column titles less than the start width to
  712. // the column title width
  713. if ($stringWidth < $this->sColWidth) {
  714. $colFits[$i] = $stringWidth;
  715. }
  716. $this->colTitles[$i] = $col_as;
  717. $this->displayColumn[$i] = true;
  718. switch ($this->fields[$i]->type) {
  719. case 'int':
  720. $this->colAlign[$i] = 'R';
  721. break;
  722. case 'blob':
  723. case 'tinyblob':
  724. case 'mediumblob':
  725. case 'longblob':
  726. /**
  727. * @todo do not deactivate completely the display
  728. * but show the field's name and [BLOB]
  729. */
  730. if (stripos($this->fields[$i]->flags, 'BINARY') !== false) {
  731. $this->displayColumn[$i] = false;
  732. unset($this->colTitles[$i]);
  733. }
  734. $this->colAlign[$i] = 'L';
  735. break;
  736. default:
  737. $this->colAlign[$i] = 'L';
  738. }
  739. }
  740. // title width verification
  741. if ($totalTitleWidth > $availableWidth) {
  742. $adjustingMode = true;
  743. } else {
  744. $adjustingMode = false;
  745. // we have enough space for all the titles at their
  746. // original width so use the true title's width
  747. foreach ($titleWidth as $key => $val) {
  748. $colFits[$key] = $val;
  749. }
  750. }
  751. // loop through the data; any column whose contents
  752. // is greater than the column size is resized
  753. /**
  754. * @todo force here a LIMIT to avoid reading all rows
  755. */
  756. while ($row = $dbi->fetchRow($this->results)) {
  757. foreach ($colFits as $key => $val) {
  758. $stringWidth = $this->GetStringWidth($row[$key]) + 6;
  759. if ($adjustingMode && ($stringWidth > $this->sColWidth)) {
  760. // any column whose data's width is bigger than
  761. // the start width is now discarded
  762. unset($colFits[$key]);
  763. } else {
  764. // if data's width is bigger than the current column width,
  765. // enlarge the column (but avoid enlarging it if the
  766. // data's width is very big)
  767. if ($stringWidth > $val
  768. && $stringWidth < $this->sColWidth * 3
  769. ) {
  770. $colFits[$key] = $stringWidth;
  771. }
  772. }
  773. }
  774. }
  775. $totAlreadyFitted = 0;
  776. foreach ($colFits as $key => $val) {
  777. // set fitted columns to smallest size
  778. $this->tablewidths[$key] = $val;
  779. // to work out how much (if any) space has been freed up
  780. $totAlreadyFitted += $val;
  781. }
  782. if ($adjustingMode) {
  783. $surplus = (count($colFits) * $this->sColWidth) - $totAlreadyFitted;
  784. $surplusToAdd = $surplus / ($this->numFields - count($colFits));
  785. } else {
  786. $surplusToAdd = 0;
  787. }
  788. for ($i = 0; $i < $this->numFields; $i++) {
  789. if (! array_key_exists($i, $colFits)) {
  790. $this->tablewidths[$i] = $this->sColWidth + $surplusToAdd;
  791. }
  792. if ($this->displayColumn[$i] != false) {
  793. continue;
  794. }
  795. $this->tablewidths[$i] = 0;
  796. }
  797. ksort($this->tablewidths);
  798. $dbi->freeResult($this->results);
  799. // Pass 2
  800. $this->results = $dbi->query(
  801. $query,
  802. DatabaseInterface::CONNECT_USER,
  803. DatabaseInterface::QUERY_UNBUFFERED
  804. );
  805. $this->SetY($this->tMargin);
  806. $this->AddPage();
  807. $this->SetFont(PdfLib::PMA_PDF_FONT, '', 9);
  808. $this->morepagestable($this->FontSizePt);
  809. $dbi->freeResult($this->results);
  810. }
  811. }