Pdf.php 29 KB

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