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