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