1<?php 2/* vim: set expandtab sw=4 ts=4 sts=4: */ 3/** 4 * PDF schema handling 5 * 6 * @package PhpMyAdmin 7 */ 8namespace PhpMyAdmin\Plugins\Schema\Pdf; 9 10use PhpMyAdmin\Pdf as PdfLib; 11use PhpMyAdmin\Plugins\Schema\ExportRelationSchema; 12use PhpMyAdmin\Relation; 13use PhpMyAdmin\Transformations; 14use PhpMyAdmin\Util; 15 16/** 17 * Skip the plugin if TCPDF is not available. 18 */ 19if (! class_exists('TCPDF')) { 20 $GLOBALS['skip_import'] = true; 21 return; 22} 23 24/** 25 * block attempts to directly run this script 26 */ 27if (getcwd() == dirname(__FILE__)) { 28 die('Attack stopped'); 29} 30 31/** 32 * Pdf Relation Schema Class 33 * 34 * Purpose of this class is to generate the PDF Document. PDF is widely 35 * used format for documenting text,fonts,images and 3d vector graphics. 36 * 37 * This class inherits ExportRelationSchema class has common functionality added 38 * to this class 39 * 40 * @name Pdf_Relation_Schema 41 * @package PhpMyAdmin 42 */ 43class PdfRelationSchema extends ExportRelationSchema 44{ 45 /** 46 * Defines properties 47 */ 48 private $_showGrid; 49 private $_withDoc; 50 private $_tableOrder; 51 52 /** 53 * @var TableStatsPdf[] 54 */ 55 private $_tables = array(); 56 private $_ff = PdfLib::PMA_PDF_FONT; 57 private $_xMax = 0; 58 private $_yMax = 0; 59 private $_scale; 60 private $_xMin = 100000; 61 private $_yMin = 100000; 62 private $_topMargin = 10; 63 private $_bottomMargin = 10; 64 private $_leftMargin = 10; 65 private $_rightMargin = 10; 66 private $_tablewidth; 67 68 /** 69 * @var RelationStatsPdf[] 70 */ 71 protected $relations = array(); 72 73 /** 74 * The "PdfRelationSchema" constructor 75 * 76 * @param string $db database name 77 * 78 * @see PMA_Schema_PDF 79 */ 80 public function __construct($db) 81 { 82 $this->setShowGrid(isset($_REQUEST['pdf_show_grid'])); 83 $this->setShowColor(isset($_REQUEST['pdf_show_color'])); 84 $this->setShowKeys(isset($_REQUEST['pdf_show_keys'])); 85 $this->setTableDimension(isset($_REQUEST['pdf_show_table_dimension'])); 86 $this->setAllTablesSameWidth(isset($_REQUEST['pdf_all_tables_same_width'])); 87 $this->setWithDataDictionary(isset($_REQUEST['pdf_with_doc'])); 88 $this->setTableOrder($_REQUEST['pdf_table_order']); 89 $this->setOrientation($_REQUEST['pdf_orientation']); 90 $this->setPaper($_REQUEST['pdf_paper']); 91 92 // Initializes a new document 93 parent::__construct( 94 $db, 95 new Pdf( 96 $this->orientation, 'mm', $this->paper, 97 $this->pageNumber, $this->_withDoc, $db 98 ) 99 ); 100 $this->diagram->SetTitle( 101 sprintf( 102 __('Schema of the %s database'), 103 $this->db 104 ) 105 ); 106 $this->diagram->setCMargin(0); 107 $this->diagram->Open(); 108 $this->diagram->SetAutoPageBreak('auto'); 109 $this->diagram->setOffline($this->offline); 110 111 $alltables = $this->getTablesFromRequest(); 112 if ($this->getTableOrder() == 'name_asc') { 113 sort($alltables); 114 } elseif ($this->getTableOrder() == 'name_desc') { 115 rsort($alltables); 116 } 117 118 if ($this->_withDoc) { 119 $this->diagram->SetAutoPageBreak('auto', 15); 120 $this->diagram->setCMargin(1); 121 $this->dataDictionaryDoc($alltables); 122 $this->diagram->SetAutoPageBreak('auto'); 123 $this->diagram->setCMargin(0); 124 } 125 126 $this->diagram->Addpage(); 127 128 if ($this->_withDoc) { 129 $this->diagram->SetLink($this->diagram->PMA_links['RT']['-'], -1); 130 $this->diagram->Bookmark(__('Relational schema')); 131 $this->diagram->setAlias('{00}', $this->diagram->PageNo()); 132 $this->_topMargin = 28; 133 $this->_bottomMargin = 28; 134 } 135 136 /* snip */ 137 foreach ($alltables as $table) { 138 if (! isset($this->_tables[$table])) { 139 $this->_tables[$table] = new TableStatsPdf( 140 $this->diagram, 141 $this->db, 142 $table, 143 null, 144 $this->pageNumber, 145 $this->_tablewidth, 146 $this->showKeys, 147 $this->tableDimension, 148 $this->offline 149 ); 150 } 151 if ($this->sameWide) { 152 $this->_tables[$table]->width = $this->_tablewidth; 153 } 154 $this->_setMinMax($this->_tables[$table]); 155 } 156 157 // Defines the scale factor 158 $innerWidth = $this->diagram->getPageWidth() - $this->_rightMargin 159 - $this->_leftMargin; 160 $innerHeight = $this->diagram->getPageHeight() - $this->_topMargin 161 - $this->_bottomMargin; 162 $this->_scale = ceil( 163 max( 164 ($this->_xMax - $this->_xMin) / $innerWidth, 165 ($this->_yMax - $this->_yMin) / $innerHeight 166 ) * 100 167 ) / 100; 168 169 $this->diagram->setScale( 170 $this->_scale, 171 $this->_xMin, 172 $this->_yMin, 173 $this->_leftMargin, 174 $this->_topMargin 175 ); 176 // Builds and save the PDF document 177 $this->diagram->setLineWidthScale(0.1); 178 179 if ($this->_showGrid) { 180 $this->diagram->SetFontSize(10); 181 $this->_strokeGrid(); 182 } 183 $this->diagram->setFontSizeScale(14); 184 // previous logic was checking master tables and foreign tables 185 // but I think that looping on every table of the pdf page as a master 186 // and finding its foreigns is OK (then we can support innodb) 187 $seen_a_relation = false; 188 foreach ($alltables as $one_table) { 189 $exist_rel = $this->relation->getForeigners($this->db, $one_table, '', 'both'); 190 if (!$exist_rel) { 191 continue; 192 } 193 194 $seen_a_relation = true; 195 foreach ($exist_rel as $master_field => $rel) { 196 // put the foreign table on the schema only if selected 197 // by the user 198 // (do not use array_search() because we would have to 199 // to do a === false and this is not PHP3 compatible) 200 if ($master_field != 'foreign_keys_data') { 201 if (in_array($rel['foreign_table'], $alltables)) { 202 $this->_addRelation( 203 $one_table, 204 $master_field, 205 $rel['foreign_table'], 206 $rel['foreign_field'] 207 ); 208 } 209 continue; 210 } 211 212 foreach ($rel as $one_key) { 213 if (!in_array($one_key['ref_table_name'], $alltables)) { 214 continue; 215 } 216 217 foreach ($one_key['index_list'] 218 as $index => $one_field 219 ) { 220 $this->_addRelation( 221 $one_table, 222 $one_field, 223 $one_key['ref_table_name'], 224 $one_key['ref_index_list'][$index] 225 ); 226 } 227 } 228 } // end while 229 } // end while 230 231 if ($seen_a_relation) { 232 $this->_drawRelations(); 233 } 234 $this->_drawTables(); 235 } 236 237 /** 238 * Set Show Grid 239 * 240 * @param boolean $value show grid of the document or not 241 * 242 * @return void 243 */ 244 public function setShowGrid($value) 245 { 246 $this->_showGrid = $value; 247 } 248 249 /** 250 * Returns whether to show grid 251 * 252 * @return boolean whether to show grid 253 */ 254 public function isShowGrid() 255 { 256 return $this->_showGrid; 257 } 258 259 /** 260 * Set Data Dictionary 261 * 262 * @param boolean $value show selected database data dictionary or not 263 * 264 * @return void 265 */ 266 public function setWithDataDictionary($value) 267 { 268 $this->_withDoc = $value; 269 } 270 271 /** 272 * Return whether to show selected database data dictionary or not 273 * 274 * @return boolean whether to show selected database data dictionary or not 275 */ 276 public function isWithDataDictionary() 277 { 278 return $this->_withDoc; 279 } 280 281 /** 282 * Sets the order of the table in data dictionary 283 * 284 * @param string $value table order 285 * 286 * @return void 287 */ 288 public function setTableOrder($value) 289 { 290 $this->_tableOrder = $value; 291 } 292 293 /** 294 * Returns the order of the table in data dictionary 295 * 296 * @return string table order 297 */ 298 public function getTableOrder() 299 { 300 return $this->_tableOrder; 301 } 302 303 /** 304 * Output Pdf Document for download 305 * 306 * @return void 307 */ 308 public function showOutput() 309 { 310 $this->diagram->download($this->getFileName('.pdf')); 311 } 312 313 /** 314 * Sets X and Y minimum and maximum for a table cell 315 * 316 * @param TableStatsPdf $table The table name of which sets XY co-ordinates 317 * 318 * @return void 319 */ 320 private function _setMinMax($table) 321 { 322 $this->_xMax = max($this->_xMax, $table->x + $table->width); 323 $this->_yMax = max($this->_yMax, $table->y + $table->height); 324 $this->_xMin = min($this->_xMin, $table->x); 325 $this->_yMin = min($this->_yMin, $table->y); 326 } 327 328 /** 329 * Defines relation objects 330 * 331 * @param string $masterTable The master table name 332 * @param string $masterField The relation field in the master table 333 * @param string $foreignTable The foreign table name 334 * @param string $foreignField The relation field in the foreign table 335 * 336 * @return void 337 * 338 * @see _setMinMax 339 */ 340 private function _addRelation($masterTable, $masterField, $foreignTable, 341 $foreignField 342 ) { 343 if (! isset($this->_tables[$masterTable])) { 344 $this->_tables[$masterTable] = new TableStatsPdf( 345 $this->diagram, 346 $this->db, 347 $masterTable, 348 null, 349 $this->pageNumber, 350 $this->_tablewidth, 351 $this->showKeys, 352 $this->tableDimension 353 ); 354 $this->_setMinMax($this->_tables[$masterTable]); 355 } 356 if (! isset($this->_tables[$foreignTable])) { 357 $this->_tables[$foreignTable] = new TableStatsPdf( 358 $this->diagram, 359 $this->db, 360 $foreignTable, 361 null, 362 $this->pageNumber, 363 $this->_tablewidth, 364 $this->showKeys, 365 $this->tableDimension 366 ); 367 $this->_setMinMax($this->_tables[$foreignTable]); 368 } 369 $this->relations[] = new RelationStatsPdf( 370 $this->diagram, 371 $this->_tables[$masterTable], 372 $masterField, 373 $this->_tables[$foreignTable], 374 $foreignField 375 ); 376 } 377 378 /** 379 * Draws the grid 380 * 381 * @return void 382 * 383 * @see PMA_Schema_PDF 384 */ 385 private function _strokeGrid() 386 { 387 $gridSize = 10; 388 $labelHeight = 4; 389 $labelWidth = 5; 390 if ($this->_withDoc) { 391 $topSpace = 6; 392 $bottomSpace = 15; 393 } else { 394 $topSpace = 0; 395 $bottomSpace = 0; 396 } 397 398 $this->diagram->SetMargins(0, 0); 399 $this->diagram->SetDrawColor(200, 200, 200); 400 // Draws horizontal lines 401 $innerHeight = $this->diagram->getPageHeight() - $topSpace - $bottomSpace; 402 for ($l = 0, 403 $size = intval($innerHeight / $gridSize); 404 $l <= $size; 405 $l++ 406 ) { 407 $this->diagram->line( 408 0, $l * $gridSize + $topSpace, 409 $this->diagram->getPageWidth(), $l * $gridSize + $topSpace 410 ); 411 // Avoid duplicates 412 if ($l > 0 413 && $l <= intval(($innerHeight - $labelHeight) / $gridSize) 414 ) { 415 $this->diagram->SetXY(0, $l * $gridSize + $topSpace); 416 $label = (string) sprintf( 417 '%.0f', 418 ($l * $gridSize + $topSpace - $this->_topMargin) 419 * $this->_scale + $this->_yMin 420 ); 421 $this->diagram->Cell($labelWidth, $labelHeight, ' ' . $label); 422 } // end if 423 } // end for 424 // Draws vertical lines 425 for ( 426 $j = 0, $size = intval($this->diagram->getPageWidth() / $gridSize); 427 $j <= $size; 428 $j++ 429 ) { 430 $this->diagram->line( 431 $j * $gridSize, 432 $topSpace, 433 $j * $gridSize, 434 $this->diagram->getPageHeight() - $bottomSpace 435 ); 436 $this->diagram->SetXY($j * $gridSize, $topSpace); 437 $label = (string) sprintf( 438 '%.0f', 439 ($j * $gridSize - $this->_leftMargin) * $this->_scale + $this->_xMin 440 ); 441 $this->diagram->Cell($labelWidth, $labelHeight, $label); 442 } 443 } 444 445 /** 446 * Draws relation arrows 447 * 448 * @return void 449 * 450 * @see Relation_Stats_Pdf::relationdraw() 451 */ 452 private function _drawRelations() 453 { 454 $i = 0; 455 foreach ($this->relations as $relation) { 456 $relation->relationDraw($this->showColor, $i); 457 $i++; 458 } 459 } 460 461 /** 462 * Draws tables 463 * 464 * @return void 465 * 466 * @see Table_Stats_Pdf::tableDraw() 467 */ 468 private function _drawTables() 469 { 470 foreach ($this->_tables as $table) { 471 $table->tableDraw(null, $this->_withDoc, $this->showColor); 472 } 473 } 474 475 /** 476 * Generates data dictionary pages. 477 * 478 * @param array $alltables Tables to document. 479 * 480 * @return void 481 */ 482 public function dataDictionaryDoc(array $alltables) 483 { 484 // TOC 485 $this->diagram->addpage($this->orientation); 486 $this->diagram->Cell(0, 9, __('Table of contents'), 1, 0, 'C'); 487 $this->diagram->Ln(15); 488 $i = 1; 489 foreach ($alltables as $table) { 490 $this->diagram->PMA_links['doc'][$table]['-'] 491 = $this->diagram->AddLink(); 492 $this->diagram->SetX(10); 493 // $this->diagram->Ln(1); 494 $this->diagram->Cell( 495 0, 6, __('Page number:') . ' {' . sprintf("%02d", $i) . '}', 0, 0, 496 'R', 0, $this->diagram->PMA_links['doc'][$table]['-'] 497 ); 498 $this->diagram->SetX(10); 499 $this->diagram->Cell( 500 0, 6, $i . ' ' . $table, 0, 1, 501 'L', 0, $this->diagram->PMA_links['doc'][$table]['-'] 502 ); 503 // $this->diagram->Ln(1); 504 $fields = $GLOBALS['dbi']->getColumns($this->db, $table); 505 foreach ($fields as $row) { 506 $this->diagram->SetX(20); 507 $field_name = $row['Field']; 508 $this->diagram->PMA_links['doc'][$table][$field_name] 509 = $this->diagram->AddLink(); 510 //$this->diagram->Cell( 511 // 0, 6, $field_name, 0, 1, 512 // 'L', 0, $this->diagram->PMA_links['doc'][$table][$field_name] 513 //); 514 } 515 $i++; 516 } 517 $this->diagram->PMA_links['RT']['-'] = $this->diagram->AddLink(); 518 $this->diagram->SetX(10); 519 $this->diagram->Cell( 520 0, 6, __('Page number:') . ' {00}', 0, 0, 521 'R', 0, $this->diagram->PMA_links['RT']['-'] 522 ); 523 $this->diagram->SetX(10); 524 $this->diagram->Cell( 525 0, 6, $i . ' ' . __('Relational schema'), 0, 1, 526 'L', 0, $this->diagram->PMA_links['RT']['-'] 527 ); 528 $z = 0; 529 foreach ($alltables as $table) { 530 $z++; 531 $this->diagram->SetAutoPageBreak(true, 15); 532 $this->diagram->addpage($this->orientation); 533 $this->diagram->Bookmark($table); 534 $this->diagram->setAlias( 535 '{' . sprintf("%02d", $z) . '}', $this->diagram->PageNo() 536 ); 537 $this->diagram->PMA_links['RT'][$table]['-'] 538 = $this->diagram->AddLink(); 539 $this->diagram->SetLink( 540 $this->diagram->PMA_links['doc'][$table]['-'], -1 541 ); 542 $this->diagram->SetFont($this->_ff, 'B', 18); 543 $this->diagram->Cell( 544 0, 8, $z . ' ' . $table, 1, 1, 545 'C', 0, $this->diagram->PMA_links['RT'][$table]['-'] 546 ); 547 $this->diagram->SetFont($this->_ff, '', 8); 548 $this->diagram->ln(); 549 550 $cfgRelation = $this->relation->getRelationsParam(); 551 $comments = $this->relation->getComments($this->db, $table); 552 if ($cfgRelation['mimework']) { 553 $mime_map = Transformations::getMIME($this->db, $table, true); 554 } 555 556 /** 557 * Gets table information 558 */ 559 $showtable = $GLOBALS['dbi']->getTable($this->db, $table) 560 ->getStatusInfo(); 561 $show_comment = isset($showtable['Comment']) 562 ? $showtable['Comment'] 563 : ''; 564 $create_time = isset($showtable['Create_time']) 565 ? Util::localisedDate( 566 strtotime($showtable['Create_time']) 567 ) 568 : ''; 569 $update_time = isset($showtable['Update_time']) 570 ? Util::localisedDate( 571 strtotime($showtable['Update_time']) 572 ) 573 : ''; 574 $check_time = isset($showtable['Check_time']) 575 ? Util::localisedDate( 576 strtotime($showtable['Check_time']) 577 ) 578 : ''; 579 580 /** 581 * Gets fields properties 582 */ 583 $columns = $GLOBALS['dbi']->getColumns($this->db, $table); 584 585 // Find which tables are related with the current one and write it in 586 // an array 587 $res_rel = $this->relation->getForeigners($this->db, $table); 588 589 /** 590 * Displays the comments of the table if MySQL >= 3.23 591 */ 592 593 $break = false; 594 if (! empty($show_comment)) { 595 $this->diagram->Cell( 596 0, 3, __('Table comments:') . ' ' . $show_comment, 0, 1 597 ); 598 $break = true; 599 } 600 601 if (! empty($create_time)) { 602 $this->diagram->Cell( 603 0, 3, __('Creation:') . ' ' . $create_time, 0, 1 604 ); 605 $break = true; 606 } 607 608 if (! empty($update_time)) { 609 $this->diagram->Cell( 610 0, 3, __('Last update:') . ' ' . $update_time, 0, 1 611 ); 612 $break = true; 613 } 614 615 if (! empty($check_time)) { 616 $this->diagram->Cell( 617 0, 3, __('Last check:') . ' ' . $check_time, 0, 1 618 ); 619 $break = true; 620 } 621 622 if ($break == true) { 623 $this->diagram->Cell(0, 3, '', 0, 1); 624 $this->diagram->Ln(); 625 } 626 627 $this->diagram->SetFont($this->_ff, 'B'); 628 if (isset($this->orientation) && $this->orientation == 'L') { 629 $this->diagram->Cell(25, 8, __('Column'), 1, 0, 'C'); 630 $this->diagram->Cell(20, 8, __('Type'), 1, 0, 'C'); 631 $this->diagram->Cell(20, 8, __('Attributes'), 1, 0, 'C'); 632 $this->diagram->Cell(10, 8, __('Null'), 1, 0, 'C'); 633 $this->diagram->Cell(20, 8, __('Default'), 1, 0, 'C'); 634 $this->diagram->Cell(25, 8, __('Extra'), 1, 0, 'C'); 635 $this->diagram->Cell(45, 8, __('Links to'), 1, 0, 'C'); 636 637 if ($this->paper == 'A4') { 638 $comments_width = 67; 639 } else { 640 // this is really intended for 'letter' 641 /** 642 * @todo find optimal width for all formats 643 */ 644 $comments_width = 50; 645 } 646 $this->diagram->Cell($comments_width, 8, __('Comments'), 1, 0, 'C'); 647 $this->diagram->Cell(45, 8, 'MIME', 1, 1, 'C'); 648 $this->diagram->setWidths( 649 array(25, 20, 20, 10, 20, 25, 45, $comments_width, 45) 650 ); 651 } else { 652 $this->diagram->Cell(20, 8, __('Column'), 1, 0, 'C'); 653 $this->diagram->Cell(20, 8, __('Type'), 1, 0, 'C'); 654 $this->diagram->Cell(20, 8, __('Attributes'), 1, 0, 'C'); 655 $this->diagram->Cell(10, 8, __('Null'), 1, 0, 'C'); 656 $this->diagram->Cell(15, 8, __('Default'), 1, 0, 'C'); 657 $this->diagram->Cell(15, 8, __('Extra'), 1, 0, 'C'); 658 $this->diagram->Cell(30, 8, __('Links to'), 1, 0, 'C'); 659 $this->diagram->Cell(30, 8, __('Comments'), 1, 0, 'C'); 660 $this->diagram->Cell(30, 8, 'MIME', 1, 1, 'C'); 661 $this->diagram->setWidths(array(20, 20, 20, 10, 15, 15, 30, 30, 30)); 662 } 663 $this->diagram->SetFont($this->_ff, ''); 664 665 foreach ($columns as $row) { 666 $extracted_columnspec 667 = Util::extractColumnSpec($row['Type']); 668 $type = $extracted_columnspec['print_type']; 669 $attribute = $extracted_columnspec['attribute']; 670 if (! isset($row['Default'])) { 671 if ($row['Null'] != '' && $row['Null'] != 'NO') { 672 $row['Default'] = 'NULL'; 673 } 674 } 675 $field_name = $row['Field']; 676 // $this->diagram->Ln(); 677 $this->diagram->PMA_links['RT'][$table][$field_name] 678 = $this->diagram->AddLink(); 679 $this->diagram->Bookmark($field_name, 1, -1); 680 $this->diagram->SetLink( 681 $this->diagram->PMA_links['doc'][$table][$field_name], -1 682 ); 683 $foreigner = $this->relation->searchColumnInForeigners($res_rel, $field_name); 684 685 $linksTo = ''; 686 if ($foreigner) { 687 $linksTo = '-> '; 688 if ($foreigner['foreign_db'] != $this->db) { 689 $linksTo .= $foreigner['foreign_db'] . '.'; 690 } 691 $linksTo .= $foreigner['foreign_table'] 692 . '.' . $foreigner['foreign_field']; 693 694 if (isset($foreigner['on_update'])) { // not set for internal 695 $linksTo .= "\n" . 'ON UPDATE ' . $foreigner['on_update']; 696 $linksTo .= "\n" . 'ON DELETE ' . $foreigner['on_delete']; 697 } 698 } 699 700 $this->diagram_row = array( 701 $field_name, 702 $type, 703 $attribute, 704 (($row['Null'] == '' || $row['Null'] == 'NO') 705 ? __('No') 706 : __('Yes')), 707 (isset($row['Default']) ? $row['Default'] : ''), 708 $row['Extra'], 709 $linksTo, 710 (isset($comments[$field_name]) 711 ? $comments[$field_name] 712 : ''), 713 (isset($mime_map) && isset($mime_map[$field_name]) 714 ? str_replace('_', '/', $mime_map[$field_name]['mimetype']) 715 : '') 716 ); 717 $links = array(); 718 $links[0] = $this->diagram->PMA_links['RT'][$table][$field_name]; 719 if ($foreigner 720 && isset($this->diagram->PMA_links['doc'][$foreigner['foreign_table']][$foreigner['foreign_field']]) 721 ) { 722 $links[6] = $this->diagram->PMA_links['doc'] 723 [$foreigner['foreign_table']][$foreigner['foreign_field']]; 724 } else { 725 unset($links[6]); 726 } 727 $this->diagram->row($this->diagram_row, $links); 728 } // end foreach 729 $this->diagram->SetFont($this->_ff, '', 14); 730 } //end each 731 } 732} 733