ExportHtmlword.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. <?php
  2. /**
  3. * HTML-Word export code
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin\Plugins\Export;
  7. use PhpMyAdmin\DatabaseInterface;
  8. use PhpMyAdmin\Plugins\ExportPlugin;
  9. use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyMainGroup;
  10. use PhpMyAdmin\Properties\Options\Groups\OptionsPropertyRootGroup;
  11. use PhpMyAdmin\Properties\Options\Items\BoolPropertyItem;
  12. use PhpMyAdmin\Properties\Options\Items\RadioPropertyItem;
  13. use PhpMyAdmin\Properties\Options\Items\TextPropertyItem;
  14. use PhpMyAdmin\Properties\Plugins\ExportPluginProperties;
  15. use PhpMyAdmin\Util;
  16. use function htmlspecialchars;
  17. use function in_array;
  18. use function str_replace;
  19. use function stripslashes;
  20. /**
  21. * Handles the export for the HTML-Word format
  22. */
  23. class ExportHtmlword extends ExportPlugin
  24. {
  25. public function __construct()
  26. {
  27. parent::__construct();
  28. $this->setProperties();
  29. }
  30. /**
  31. * Sets the export HTML-Word properties
  32. *
  33. * @return void
  34. */
  35. protected function setProperties()
  36. {
  37. $exportPluginProperties = new ExportPluginProperties();
  38. $exportPluginProperties->setText('Microsoft Word 2000');
  39. $exportPluginProperties->setExtension('doc');
  40. $exportPluginProperties->setMimeType('application/vnd.ms-word');
  41. $exportPluginProperties->setForceFile(true);
  42. $exportPluginProperties->setOptionsText(__('Options'));
  43. // create the root group that will be the options field for
  44. // $exportPluginProperties
  45. // this will be shown as "Format specific options"
  46. $exportSpecificOptions = new OptionsPropertyRootGroup(
  47. 'Format Specific Options'
  48. );
  49. // what to dump (structure/data/both)
  50. $dumpWhat = new OptionsPropertyMainGroup(
  51. 'dump_what',
  52. __('Dump table')
  53. );
  54. // create primary items and add them to the group
  55. $leaf = new RadioPropertyItem('structure_or_data');
  56. $leaf->setValues(
  57. [
  58. 'structure' => __('structure'),
  59. 'data' => __('data'),
  60. 'structure_and_data' => __('structure and data'),
  61. ]
  62. );
  63. $dumpWhat->addProperty($leaf);
  64. // add the main group to the root group
  65. $exportSpecificOptions->addProperty($dumpWhat);
  66. // data options main group
  67. $dataOptions = new OptionsPropertyMainGroup(
  68. 'dump_what',
  69. __('Data dump options')
  70. );
  71. $dataOptions->setForce('structure');
  72. // create primary items and add them to the group
  73. $leaf = new TextPropertyItem(
  74. 'null',
  75. __('Replace NULL with:')
  76. );
  77. $dataOptions->addProperty($leaf);
  78. $leaf = new BoolPropertyItem(
  79. 'columns',
  80. __('Put columns names in the first row')
  81. );
  82. $dataOptions->addProperty($leaf);
  83. // add the main group to the root group
  84. $exportSpecificOptions->addProperty($dataOptions);
  85. // set the options for the export plugin property item
  86. $exportPluginProperties->setOptions($exportSpecificOptions);
  87. $this->properties = $exportPluginProperties;
  88. }
  89. /**
  90. * Outputs export header
  91. *
  92. * @return bool Whether it succeeded
  93. */
  94. public function exportHeader()
  95. {
  96. global $charset;
  97. return $this->export->outputHandler(
  98. '<html xmlns:o="urn:schemas-microsoft-com:office:office"
  99. xmlns:x="urn:schemas-microsoft-com:office:word"
  100. xmlns="http://www.w3.org/TR/REC-html40">
  101. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
  102. . ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  103. <html>
  104. <head>
  105. <meta http-equiv="Content-type" content="text/html;charset='
  106. . ($charset ?? 'utf-8') . '" />
  107. </head>
  108. <body>'
  109. );
  110. }
  111. /**
  112. * Outputs export footer
  113. *
  114. * @return bool Whether it succeeded
  115. */
  116. public function exportFooter()
  117. {
  118. return $this->export->outputHandler('</body></html>');
  119. }
  120. /**
  121. * Outputs database header
  122. *
  123. * @param string $db Database name
  124. * @param string $db_alias Aliases of db
  125. *
  126. * @return bool Whether it succeeded
  127. */
  128. public function exportDBHeader($db, $db_alias = '')
  129. {
  130. if (empty($db_alias)) {
  131. $db_alias = $db;
  132. }
  133. return $this->export->outputHandler(
  134. '<h1>' . __('Database') . ' ' . htmlspecialchars($db_alias) . '</h1>'
  135. );
  136. }
  137. /**
  138. * Outputs database footer
  139. *
  140. * @param string $db Database name
  141. *
  142. * @return bool Whether it succeeded
  143. */
  144. public function exportDBFooter($db)
  145. {
  146. return true;
  147. }
  148. /**
  149. * Outputs CREATE DATABASE statement
  150. *
  151. * @param string $db Database name
  152. * @param string $export_type 'server', 'database', 'table'
  153. * @param string $db_alias Aliases of db
  154. *
  155. * @return bool Whether it succeeded
  156. */
  157. public function exportDBCreate($db, $export_type, $db_alias = '')
  158. {
  159. return true;
  160. }
  161. /**
  162. * Outputs the content of a table in HTML-Word format
  163. *
  164. * @param string $db database name
  165. * @param string $table table name
  166. * @param string $crlf the end of line sequence
  167. * @param string $error_url the url to go back in case of error
  168. * @param string $sql_query SQL query for obtaining data
  169. * @param array $aliases Aliases of db/table/columns
  170. *
  171. * @return bool Whether it succeeded
  172. */
  173. public function exportData(
  174. $db,
  175. $table,
  176. $crlf,
  177. $error_url,
  178. $sql_query,
  179. array $aliases = []
  180. ) {
  181. global $what, $dbi;
  182. $db_alias = $db;
  183. $table_alias = $table;
  184. $this->initAlias($aliases, $db_alias, $table_alias);
  185. if (! $this->export->outputHandler(
  186. '<h2>'
  187. . __('Dumping data for table') . ' ' . htmlspecialchars($table_alias)
  188. . '</h2>'
  189. )
  190. ) {
  191. return false;
  192. }
  193. if (! $this->export->outputHandler(
  194. '<table class="pma-table w-100" cellspacing="1">'
  195. )
  196. ) {
  197. return false;
  198. }
  199. // Gets the data from the database
  200. $result = $dbi->query(
  201. $sql_query,
  202. DatabaseInterface::CONNECT_USER,
  203. DatabaseInterface::QUERY_UNBUFFERED
  204. );
  205. $fields_cnt = $dbi->numFields($result);
  206. // If required, get fields name at the first line
  207. if (isset($GLOBALS['htmlword_columns'])) {
  208. $schema_insert = '<tr class="print-category">';
  209. for ($i = 0; $i < $fields_cnt; $i++) {
  210. $col_as = $dbi->fieldName($result, $i);
  211. if (! empty($aliases[$db]['tables'][$table]['columns'][$col_as])) {
  212. $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as];
  213. }
  214. $col_as = stripslashes($col_as);
  215. $schema_insert .= '<td class="print"><strong>'
  216. . htmlspecialchars($col_as)
  217. . '</strong></td>';
  218. }
  219. $schema_insert .= '</tr>';
  220. if (! $this->export->outputHandler($schema_insert)) {
  221. return false;
  222. }
  223. }
  224. // Format the data
  225. while ($row = $dbi->fetchRow($result)) {
  226. $schema_insert = '<tr class="print-category">';
  227. for ($j = 0; $j < $fields_cnt; $j++) {
  228. if (! isset($row[$j]) || $row[$j] === null) {
  229. $value = $GLOBALS[$what . '_null'];
  230. } elseif ($row[$j] == '0' || $row[$j] != '') {
  231. $value = $row[$j];
  232. } else {
  233. $value = '';
  234. }
  235. $schema_insert .= '<td class="print">'
  236. . htmlspecialchars((string) $value)
  237. . '</td>';
  238. }
  239. $schema_insert .= '</tr>';
  240. if (! $this->export->outputHandler($schema_insert)) {
  241. return false;
  242. }
  243. }
  244. $dbi->freeResult($result);
  245. return $this->export->outputHandler('</table>');
  246. }
  247. /**
  248. * Returns a stand-in CREATE definition to resolve view dependencies
  249. *
  250. * @param string $db the database name
  251. * @param string $view the view name
  252. * @param string $crlf the end of line sequence
  253. * @param array $aliases Aliases of db/table/columns
  254. *
  255. * @return string resulting definition
  256. */
  257. public function getTableDefStandIn($db, $view, $crlf, $aliases = [])
  258. {
  259. global $dbi;
  260. $schema_insert = '<table class="pma-table w-100" cellspacing="1">'
  261. . '<tr class="print-category">'
  262. . '<th class="print">'
  263. . __('Column')
  264. . '</th>'
  265. . '<td class="print"><strong>'
  266. . __('Type')
  267. . '</strong></td>'
  268. . '<td class="print"><strong>'
  269. . __('Null')
  270. . '</strong></td>'
  271. . '<td class="print"><strong>'
  272. . __('Default')
  273. . '</strong></td>'
  274. . '</tr>';
  275. /**
  276. * Get the unique keys in the view
  277. */
  278. $unique_keys = [];
  279. $keys = $dbi->getTableIndexes($db, $view);
  280. foreach ($keys as $key) {
  281. if ($key['Non_unique'] != 0) {
  282. continue;
  283. }
  284. $unique_keys[] = $key['Column_name'];
  285. }
  286. $columns = $dbi->getColumns($db, $view);
  287. foreach ($columns as $column) {
  288. $col_as = $column['Field'];
  289. if (! empty($aliases[$db]['tables'][$view]['columns'][$col_as])) {
  290. $col_as = $aliases[$db]['tables'][$view]['columns'][$col_as];
  291. }
  292. $schema_insert .= $this->formatOneColumnDefinition(
  293. $column,
  294. $unique_keys,
  295. $col_as
  296. );
  297. $schema_insert .= '</tr>';
  298. }
  299. $schema_insert .= '</table>';
  300. return $schema_insert;
  301. }
  302. /**
  303. * Returns $table's CREATE definition
  304. *
  305. * @param string $db the database name
  306. * @param string $table the table name
  307. * @param bool $do_relation whether to include relation comments
  308. * @param bool $do_comments whether to include the pmadb-style column
  309. * comments as comments in the structure;
  310. * this is deprecated but the parameter is
  311. * left here because /export calls
  312. * PMA_exportStructure() also for other
  313. * export types which use this parameter
  314. * @param bool $do_mime whether to include mime comments
  315. * at the end
  316. * @param bool $view whether we're handling a view
  317. * @param array $aliases Aliases of db/table/columns
  318. *
  319. * @return string resulting schema
  320. */
  321. public function getTableDef(
  322. $db,
  323. $table,
  324. $do_relation,
  325. $do_comments,
  326. $do_mime,
  327. $view = false,
  328. array $aliases = []
  329. ) {
  330. global $dbi;
  331. // set $cfgRelation here, because there is a chance that it's modified
  332. // since the class initialization
  333. global $cfgRelation;
  334. $schema_insert = '';
  335. /**
  336. * Gets fields properties
  337. */
  338. $dbi->selectDb($db);
  339. // Check if we can use Relations
  340. [$res_rel, $have_rel] = $this->relation->getRelationsAndStatus(
  341. $do_relation && ! empty($cfgRelation['relation']),
  342. $db,
  343. $table
  344. );
  345. /**
  346. * Displays the table structure
  347. */
  348. $schema_insert .= '<table class="pma-table w-100" cellspacing="1">';
  349. $schema_insert .= '<tr class="print-category">';
  350. $schema_insert .= '<th class="print">'
  351. . __('Column')
  352. . '</th>';
  353. $schema_insert .= '<td class="print"><strong>'
  354. . __('Type')
  355. . '</strong></td>';
  356. $schema_insert .= '<td class="print"><strong>'
  357. . __('Null')
  358. . '</strong></td>';
  359. $schema_insert .= '<td class="print"><strong>'
  360. . __('Default')
  361. . '</strong></td>';
  362. if ($do_relation && $have_rel) {
  363. $schema_insert .= '<td class="print"><strong>'
  364. . __('Links to')
  365. . '</strong></td>';
  366. }
  367. if ($do_comments) {
  368. $schema_insert .= '<td class="print"><strong>'
  369. . __('Comments')
  370. . '</strong></td>';
  371. $comments = $this->relation->getComments($db, $table);
  372. }
  373. if ($do_mime && $cfgRelation['mimework']) {
  374. $schema_insert .= '<td class="print"><strong>'
  375. . __('Media type')
  376. . '</strong></td>';
  377. $mime_map = $this->transformations->getMime($db, $table, true);
  378. }
  379. $schema_insert .= '</tr>';
  380. $columns = $dbi->getColumns($db, $table);
  381. /**
  382. * Get the unique keys in the table
  383. */
  384. $unique_keys = [];
  385. $keys = $dbi->getTableIndexes($db, $table);
  386. foreach ($keys as $key) {
  387. if ($key['Non_unique'] != 0) {
  388. continue;
  389. }
  390. $unique_keys[] = $key['Column_name'];
  391. }
  392. foreach ($columns as $column) {
  393. $col_as = $column['Field'];
  394. if (! empty($aliases[$db]['tables'][$table]['columns'][$col_as])) {
  395. $col_as = $aliases[$db]['tables'][$table]['columns'][$col_as];
  396. }
  397. $schema_insert .= $this->formatOneColumnDefinition(
  398. $column,
  399. $unique_keys,
  400. $col_as
  401. );
  402. $field_name = $column['Field'];
  403. if ($do_relation && $have_rel) {
  404. $schema_insert .= '<td class="print">'
  405. . htmlspecialchars(
  406. $this->getRelationString(
  407. $res_rel,
  408. $field_name,
  409. $db,
  410. $aliases
  411. )
  412. )
  413. . '</td>';
  414. }
  415. if ($do_comments && $cfgRelation['commwork']) {
  416. $schema_insert .= '<td class="print">'
  417. . (isset($comments[$field_name])
  418. ? htmlspecialchars($comments[$field_name])
  419. : '') . '</td>';
  420. }
  421. if ($do_mime && $cfgRelation['mimework']) {
  422. $schema_insert .= '<td class="print">'
  423. . (isset($mime_map[$field_name]) ?
  424. htmlspecialchars(
  425. str_replace('_', '/', $mime_map[$field_name]['mimetype'])
  426. )
  427. : '') . '</td>';
  428. }
  429. $schema_insert .= '</tr>';
  430. }
  431. $schema_insert .= '</table>';
  432. return $schema_insert;
  433. }
  434. /**
  435. * Outputs triggers
  436. *
  437. * @param string $db database name
  438. * @param string $table table name
  439. *
  440. * @return string Formatted triggers list
  441. */
  442. protected function getTriggers($db, $table)
  443. {
  444. global $dbi;
  445. $dump = '<table class="pma-table w-100" cellspacing="1">';
  446. $dump .= '<tr class="print-category">';
  447. $dump .= '<th class="print">' . __('Name') . '</th>';
  448. $dump .= '<td class="print"><strong>' . __('Time') . '</strong></td>';
  449. $dump .= '<td class="print"><strong>' . __('Event') . '</strong></td>';
  450. $dump .= '<td class="print"><strong>' . __('Definition') . '</strong></td>';
  451. $dump .= '</tr>';
  452. $triggers = $dbi->getTriggers($db, $table);
  453. foreach ($triggers as $trigger) {
  454. $dump .= '<tr class="print-category">';
  455. $dump .= '<td class="print">'
  456. . htmlspecialchars($trigger['name'])
  457. . '</td>'
  458. . '<td class="print">'
  459. . htmlspecialchars($trigger['action_timing'])
  460. . '</td>'
  461. . '<td class="print">'
  462. . htmlspecialchars($trigger['event_manipulation'])
  463. . '</td>'
  464. . '<td class="print">'
  465. . htmlspecialchars($trigger['definition'])
  466. . '</td>'
  467. . '</tr>';
  468. }
  469. $dump .= '</table>';
  470. return $dump;
  471. }
  472. /**
  473. * Outputs table's structure
  474. *
  475. * @param string $db database name
  476. * @param string $table table name
  477. * @param string $crlf the end of line sequence
  478. * @param string $error_url the url to go back in case of error
  479. * @param string $export_mode 'create_table', 'triggers', 'create_view',
  480. * 'stand_in'
  481. * @param string $export_type 'server', 'database', 'table'
  482. * @param bool $do_relation whether to include relation comments
  483. * @param bool $do_comments whether to include the pmadb-style column
  484. * comments as comments in the structure;
  485. * this is deprecated but the parameter is
  486. * left here because /export calls
  487. * PMA_exportStructure() also for other
  488. * export types which use this parameter
  489. * @param bool $do_mime whether to include mime comments
  490. * @param bool $dates whether to include creation/update/check dates
  491. * @param array $aliases Aliases of db/table/columns
  492. *
  493. * @return bool Whether it succeeded
  494. */
  495. public function exportStructure(
  496. $db,
  497. $table,
  498. $crlf,
  499. $error_url,
  500. $export_mode,
  501. $export_type,
  502. $do_relation = false,
  503. $do_comments = false,
  504. $do_mime = false,
  505. $dates = false,
  506. array $aliases = []
  507. ) {
  508. global $dbi;
  509. $db_alias = $db;
  510. $table_alias = $table;
  511. $this->initAlias($aliases, $db_alias, $table_alias);
  512. $dump = '';
  513. switch ($export_mode) {
  514. case 'create_table':
  515. $dump .= '<h2>'
  516. . __('Table structure for table') . ' '
  517. . htmlspecialchars($table_alias)
  518. . '</h2>';
  519. $dump .= $this->getTableDef(
  520. $db,
  521. $table,
  522. $do_relation,
  523. $do_comments,
  524. $do_mime,
  525. false,
  526. $aliases
  527. );
  528. break;
  529. case 'triggers':
  530. $dump = '';
  531. $triggers = $dbi->getTriggers($db, $table);
  532. if ($triggers) {
  533. $dump .= '<h2>'
  534. . __('Triggers') . ' ' . htmlspecialchars($table_alias)
  535. . '</h2>';
  536. $dump .= $this->getTriggers($db, $table);
  537. }
  538. break;
  539. case 'create_view':
  540. $dump .= '<h2>'
  541. . __('Structure for view') . ' ' . htmlspecialchars($table_alias)
  542. . '</h2>';
  543. $dump .= $this->getTableDef(
  544. $db,
  545. $table,
  546. $do_relation,
  547. $do_comments,
  548. $do_mime,
  549. true,
  550. $aliases
  551. );
  552. break;
  553. case 'stand_in':
  554. $dump .= '<h2>'
  555. . __('Stand-in structure for view') . ' '
  556. . htmlspecialchars($table_alias)
  557. . '</h2>';
  558. // export a stand-in definition to resolve view dependencies
  559. $dump .= $this->getTableDefStandIn($db, $table, $crlf, $aliases);
  560. }
  561. return $this->export->outputHandler($dump);
  562. }
  563. /**
  564. * Formats the definition for one column
  565. *
  566. * @param array $column info about this column
  567. * @param array $unique_keys unique keys of the table
  568. * @param string $col_alias Column Alias
  569. *
  570. * @return string Formatted column definition
  571. */
  572. protected function formatOneColumnDefinition(
  573. array $column,
  574. array $unique_keys,
  575. $col_alias = ''
  576. ) {
  577. if (empty($col_alias)) {
  578. $col_alias = $column['Field'];
  579. }
  580. $definition = '<tr class="print-category">';
  581. $extracted_columnspec = Util::extractColumnSpec($column['Type']);
  582. $type = htmlspecialchars($extracted_columnspec['print_type']);
  583. if (empty($type)) {
  584. $type = '&nbsp;';
  585. }
  586. if (! isset($column['Default'])) {
  587. if ($column['Null'] !== 'NO') {
  588. $column['Default'] = 'NULL';
  589. }
  590. }
  591. $fmt_pre = '';
  592. $fmt_post = '';
  593. if (in_array($column['Field'], $unique_keys)) {
  594. $fmt_pre = '<strong>' . $fmt_pre;
  595. $fmt_post .= '</strong>';
  596. }
  597. if ($column['Key'] === 'PRI') {
  598. $fmt_pre = '<em>' . $fmt_pre;
  599. $fmt_post .= '</em>';
  600. }
  601. $definition .= '<td class="print">' . $fmt_pre
  602. . htmlspecialchars($col_alias) . $fmt_post . '</td>';
  603. $definition .= '<td class="print">' . htmlspecialchars($type) . '</td>';
  604. $definition .= '<td class="print">'
  605. . ($column['Null'] == '' || $column['Null'] === 'NO'
  606. ? __('No')
  607. : __('Yes'))
  608. . '</td>';
  609. $definition .= '<td class="print">'
  610. . htmlspecialchars($column['Default'] ?? '')
  611. . '</td>';
  612. return $definition;
  613. }
  614. }