1<?php 2/* vim: set expandtab sw=4 ts=4 sts=4: */ 3/** 4 * set of functions with the insert/edit features in pma 5 * 6 * @package PhpMyAdmin 7 */ 8namespace PhpMyAdmin; 9 10use PhpMyAdmin\DatabaseInterface; 11use PhpMyAdmin\FileListing; 12use PhpMyAdmin\Message; 13use PhpMyAdmin\Plugins\TransformationsPlugin; 14use PhpMyAdmin\Relation; 15use PhpMyAdmin\Response; 16use PhpMyAdmin\Sanitize; 17use PhpMyAdmin\Template; 18use PhpMyAdmin\Transformations; 19use PhpMyAdmin\Url; 20use PhpMyAdmin\Util; 21use PhpMyAdmin\Core; 22 23/** 24 * PhpMyAdmin\InsertEdit class 25 * 26 * @package PhpMyAdmin 27 */ 28class InsertEdit 29{ 30 /** 31 * DatabaseInterface instance 32 * 33 * @var DatabaseInterface 34 */ 35 private $dbi; 36 37 /** 38 * @var Relation $relation 39 */ 40 private $relation; 41 42 /** 43 * Constructor 44 * 45 * @param DatabaseInterface $dbi DatabaseInterface instance 46 */ 47 public function __construct(DatabaseInterface $dbi) 48 { 49 $this->dbi = $dbi; 50 $this->relation = new Relation(); 51 } 52 53 /** 54 * Retrieve form parameters for insert/edit form 55 * 56 * @param string $db name of the database 57 * @param string $table name of the table 58 * @param array|null $where_clauses where clauses 59 * @param array $where_clause_array array of where clauses 60 * @param string $err_url error url 61 * 62 * @return array $form_params array of insert/edit form parameters 63 */ 64 public function getFormParametersForInsertForm( 65 $db, 66 $table, 67 $where_clauses, 68 array $where_clause_array, 69 $err_url 70 ) { 71 $_form_params = array( 72 'db' => $db, 73 'table' => $table, 74 'goto' => $GLOBALS['goto'], 75 'err_url' => $err_url, 76 'sql_query' => $_POST['sql_query'], 77 ); 78 if (isset($where_clauses)) { 79 foreach ($where_clause_array as $key_id => $where_clause) { 80 $_form_params['where_clause[' . $key_id . ']'] = trim($where_clause); 81 } 82 } 83 if (isset($_POST['clause_is_unique'])) { 84 $_form_params['clause_is_unique'] = $_POST['clause_is_unique']; 85 } 86 return $_form_params; 87 } 88 89 /** 90 * Creates array of where clauses 91 * 92 * @param array|string|null $where_clause where clause 93 * 94 * @return array whereClauseArray array of where clauses 95 */ 96 private function getWhereClauseArray($where_clause) 97 { 98 if (!isset($where_clause)) { 99 return array(); 100 } 101 102 if (is_array($where_clause)) { 103 return $where_clause; 104 } 105 106 return array(0 => $where_clause); 107 } 108 109 /** 110 * Analysing where clauses array 111 * 112 * @param array $where_clause_array array of where clauses 113 * @param string $table name of the table 114 * @param string $db name of the database 115 * 116 * @return array $where_clauses, $result, $rows 117 */ 118 private function analyzeWhereClauses( 119 array $where_clause_array, 120 $table, 121 $db 122 ) { 123 $rows = array(); 124 $result = array(); 125 $where_clauses = array(); 126 $found_unique_key = false; 127 foreach ($where_clause_array as $key_id => $where_clause) { 128 $local_query = 'SELECT * FROM ' 129 . Util::backquote($db) . '.' 130 . Util::backquote($table) 131 . ' WHERE ' . $where_clause . ';'; 132 $result[$key_id] = $this->dbi->query( 133 $local_query, 134 DatabaseInterface::CONNECT_USER, 135 DatabaseInterface::QUERY_STORE 136 ); 137 $rows[$key_id] = $this->dbi->fetchAssoc($result[$key_id]); 138 139 $where_clauses[$key_id] = str_replace('\\', '\\\\', $where_clause); 140 $has_unique_condition = $this->showEmptyResultMessageOrSetUniqueCondition( 141 $rows, 142 $key_id, 143 $where_clause_array, 144 $local_query, 145 $result 146 ); 147 if ($has_unique_condition) { 148 $found_unique_key = true; 149 } 150 } 151 return array($where_clauses, $result, $rows, $found_unique_key); 152 } 153 154 /** 155 * Show message for empty result or set the unique_condition 156 * 157 * @param array $rows MySQL returned rows 158 * @param string $key_id ID in current key 159 * @param array $where_clause_array array of where clauses 160 * @param string $local_query query performed 161 * @param array $result MySQL result handle 162 * 163 * @return boolean $has_unique_condition 164 */ 165 private function showEmptyResultMessageOrSetUniqueCondition( 166 array $rows, 167 $key_id, 168 array $where_clause_array, 169 $local_query, 170 array $result 171 ) { 172 $has_unique_condition = false; 173 174 // No row returned 175 if (! $rows[$key_id]) { 176 unset($rows[$key_id], $where_clause_array[$key_id]); 177 Response::getInstance()->addHtml( 178 Util::getMessage( 179 __('MySQL returned an empty result set (i.e. zero rows).'), 180 $local_query 181 ) 182 ); 183 /** 184 * @todo not sure what should be done at this point, but we must not 185 * exit if we want the message to be displayed 186 */ 187 } else {// end if (no row returned) 188 $meta = $this->dbi->getFieldsMeta($result[$key_id]); 189 190 list($unique_condition, $tmp_clause_is_unique) 191 = Util::getUniqueCondition( 192 $result[$key_id], // handle 193 count($meta), // fields_cnt 194 $meta, // fields_meta 195 $rows[$key_id], // row 196 true, // force_unique 197 false, // restrict_to_table 198 null // analyzed_sql_results 199 ); 200 201 if (! empty($unique_condition)) { 202 $has_unique_condition = true; 203 } 204 unset($unique_condition, $tmp_clause_is_unique); 205 } 206 return $has_unique_condition; 207 } 208 209 /** 210 * No primary key given, just load first row 211 * 212 * @param string $table name of the table 213 * @param string $db name of the database 214 * 215 * @return array containing $result and $rows arrays 216 */ 217 private function loadFirstRow($table, $db) 218 { 219 $result = $this->dbi->query( 220 'SELECT * FROM ' . Util::backquote($db) 221 . '.' . Util::backquote($table) . ' LIMIT 1;', 222 DatabaseInterface::CONNECT_USER, 223 DatabaseInterface::QUERY_STORE 224 ); 225 $rows = array_fill(0, $GLOBALS['cfg']['InsertRows'], false); 226 return array($result, $rows); 227 } 228 229 /** 230 * Add some url parameters 231 * 232 * @param array $url_params containing $db and $table as url parameters 233 * @param array $where_clause_array where clauses array 234 * 235 * @return array Add some url parameters to $url_params array and return it 236 */ 237 public function urlParamsInEditMode( 238 array $url_params, 239 array $where_clause_array 240 ) { 241 if (isset($where_clause_array)) { 242 foreach ($where_clause_array as $where_clause) { 243 $url_params['where_clause'] = trim($where_clause); 244 } 245 } 246 if (! empty($_POST['sql_query'])) { 247 $url_params['sql_query'] = $_POST['sql_query']; 248 } 249 return $url_params; 250 } 251 252 /** 253 * Show type information or function selectors in Insert/Edit 254 * 255 * @param string $which function|type 256 * @param array $url_params containing url parameters 257 * @param boolean $is_show whether to show the element in $which 258 * 259 * @return string an HTML snippet 260 */ 261 public function showTypeOrFunction($which, array $url_params, $is_show) 262 { 263 $params = array(); 264 265 switch ($which) { 266 case 'function': 267 $params['ShowFunctionFields'] = ($is_show ? 0 : 1); 268 $params['ShowFieldTypesInDataEditView'] 269 = $GLOBALS['cfg']['ShowFieldTypesInDataEditView']; 270 break; 271 case 'type': 272 $params['ShowFieldTypesInDataEditView'] = ($is_show ? 0 : 1); 273 $params['ShowFunctionFields'] 274 = $GLOBALS['cfg']['ShowFunctionFields']; 275 break; 276 } 277 278 $params['goto'] = 'sql.php'; 279 $this_url_params = array_merge($url_params, $params); 280 281 if (! $is_show) { 282 return ' : <a href="tbl_change.php" data-post="' 283 . Url::getCommon($this_url_params, '') . '">' 284 . $this->showTypeOrFunctionLabel($which) 285 . '</a>'; 286 } 287 return '<th><a href="tbl_change.php" data-post="' 288 . Url::getCommon($this_url_params, '') 289 . '" title="' . __('Hide') . '">' 290 . $this->showTypeOrFunctionLabel($which) 291 . '</a></th>'; 292 } 293 294 /** 295 * Show type information or function selectors labels in Insert/Edit 296 * 297 * @param string $which function|type 298 * 299 * @return string an HTML snippet 300 */ 301 private function showTypeOrFunctionLabel($which) 302 { 303 switch ($which) { 304 case 'function': 305 return __('Function'); 306 case 'type': 307 return __('Type'); 308 } 309 310 return null; 311 } 312 313 /** 314 * Analyze the table column array 315 * 316 * @param array $column description of column in given table 317 * @param array $comments_map comments for every column that has a comment 318 * @param boolean $timestamp_seen whether a timestamp has been seen 319 * 320 * @return array description of column in given table 321 */ 322 private function analyzeTableColumnsArray( 323 array $column, 324 array $comments_map, 325 $timestamp_seen 326 ) { 327 $column['Field_html'] = htmlspecialchars($column['Field']); 328 $column['Field_md5'] = md5($column['Field']); 329 // True_Type contains only the type (stops at first bracket) 330 $column['True_Type'] = preg_replace('@\(.*@s', '', $column['Type']); 331 $column['len'] = preg_match('@float|double@', $column['Type']) ? 100 : -1; 332 $column['Field_title'] = $this->getColumnTitle($column, $comments_map); 333 $column['is_binary'] = $this->isColumn( 334 $column, 335 array('binary', 'varbinary') 336 ); 337 $column['is_blob'] = $this->isColumn( 338 $column, 339 array('blob', 'tinyblob', 'mediumblob', 'longblob') 340 ); 341 $column['is_char'] = $this->isColumn( 342 $column, 343 array('char', 'varchar') 344 ); 345 346 list($column['pma_type'], $column['wrap'], $column['first_timestamp']) 347 = $this->getEnumSetAndTimestampColumns($column, $timestamp_seen); 348 349 return $column; 350 } 351 352 /** 353 * Retrieve the column title 354 * 355 * @param array $column description of column in given table 356 * @param array $comments_map comments for every column that has a comment 357 * 358 * @return string column title 359 */ 360 private function getColumnTitle(array $column, array $comments_map) 361 { 362 if (isset($comments_map[$column['Field']])) { 363 return '<span style="border-bottom: 1px dashed black;" title="' 364 . htmlspecialchars($comments_map[$column['Field']]) . '">' 365 . $column['Field_html'] . '</span>'; 366 } 367 368 return $column['Field_html']; 369 } 370 371 /** 372 * check whether the column is of a certain type 373 * the goal is to ensure that types such as "enum('one','two','binary',..)" 374 * or "enum('one','two','varbinary',..)" are not categorized as binary 375 * 376 * @param array $column description of column in given table 377 * @param array $types the types to verify 378 * 379 * @return boolean whether the column's type if one of the $types 380 */ 381 public function isColumn(array $column, array $types) 382 { 383 foreach ($types as $one_type) { 384 if (mb_stripos($column['Type'], $one_type) === 0) { 385 return true; 386 } 387 } 388 return false; 389 } 390 391 /** 392 * Retrieve set, enum, timestamp table columns 393 * 394 * @param array $column description of column in given table 395 * @param boolean $timestamp_seen whether a timestamp has been seen 396 * 397 * @return array $column['pma_type'], $column['wrap'], $column['first_timestamp'] 398 */ 399 private function getEnumSetAndTimestampColumns(array $column, $timestamp_seen) 400 { 401 $column['first_timestamp'] = false; 402 switch ($column['True_Type']) { 403 case 'set': 404 $column['pma_type'] = 'set'; 405 $column['wrap'] = ''; 406 break; 407 case 'enum': 408 $column['pma_type'] = 'enum'; 409 $column['wrap'] = ''; 410 break; 411 case 'timestamp': 412 if (! $timestamp_seen) { // can only occur once per table 413 $column['first_timestamp'] = true; 414 } 415 $column['pma_type'] = $column['Type']; 416 $column['wrap'] = ' nowrap'; 417 break; 418 419 default: 420 $column['pma_type'] = $column['Type']; 421 $column['wrap'] = ' nowrap'; 422 break; 423 } 424 return array($column['pma_type'], $column['wrap'], $column['first_timestamp']); 425 } 426 427 /** 428 * The function column 429 * We don't want binary data to be destroyed 430 * Note: from the MySQL manual: "BINARY doesn't affect how the column is 431 * stored or retrieved" so it does not mean that the contents is binary 432 * 433 * @param array $column description of column in given table 434 * @param boolean $is_upload upload or no 435 * @param string $column_name_appendix the name attribute 436 * @param string $onChangeClause onchange clause for fields 437 * @param array $no_support_types list of datatypes that are not (yet) 438 * handled by PMA 439 * @param integer $tabindex_for_function +3000 440 * @param integer $tabindex tab index 441 * @param integer $idindex id index 442 * @param boolean $insert_mode insert mode or edit mode 443 * @param boolean $readOnly is column read only or not 444 * @param array $foreignData foreign key data 445 * 446 * @return string an html snippet 447 */ 448 private function getFunctionColumn( 449 array $column, 450 $is_upload, 451 $column_name_appendix, 452 $onChangeClause, 453 array $no_support_types, 454 $tabindex_for_function, 455 $tabindex, 456 $idindex, 457 $insert_mode, 458 $readOnly, 459 array $foreignData 460 ) { 461 $html_output = ''; 462 if (($GLOBALS['cfg']['ProtectBinary'] === 'blob' 463 && $column['is_blob'] && !$is_upload) 464 || ($GLOBALS['cfg']['ProtectBinary'] === 'all' 465 && $column['is_binary']) 466 || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' 467 && $column['is_binary']) 468 ) { 469 $html_output .= '<td class="center">' . __('Binary') . '</td>' . "\n"; 470 } elseif ($readOnly 471 || mb_strstr($column['True_Type'], 'enum') 472 || mb_strstr($column['True_Type'], 'set') 473 || in_array($column['pma_type'], $no_support_types) 474 ) { 475 $html_output .= '<td class="center">--</td>' . "\n"; 476 } else { 477 $html_output .= '<td>' . "\n"; 478 479 $html_output .= '<select name="funcs' . $column_name_appendix . '"' 480 . ' ' . $onChangeClause 481 . ' tabindex="' . ($tabindex + $tabindex_for_function) . '"' 482 . ' id="field_' . $idindex . '_1">'; 483 $html_output .= Util::getFunctionsForField( 484 $column, 485 $insert_mode, 486 $foreignData 487 ) . "\n"; 488 489 $html_output .= '</select>' . "\n"; 490 $html_output .= '</td>' . "\n"; 491 } 492 return $html_output; 493 } 494 495 /** 496 * The null column 497 * 498 * @param array $column description of column in given table 499 * @param string $column_name_appendix the name attribute 500 * @param boolean $real_null_value is column value null or not null 501 * @param integer $tabindex tab index 502 * @param integer $tabindex_for_null +6000 503 * @param integer $idindex id index 504 * @param string $vkey [multi_edit]['row_id'] 505 * @param array $foreigners keys into foreign fields 506 * @param array $foreignData data about the foreign keys 507 * @param boolean $readOnly is column read only or not 508 * 509 * @return string an html snippet 510 */ 511 private function getNullColumn( 512 array $column, 513 $column_name_appendix, 514 $real_null_value, 515 $tabindex, 516 $tabindex_for_null, 517 $idindex, 518 $vkey, 519 array $foreigners, 520 array $foreignData, 521 $readOnly 522 ) { 523 if ($column['Null'] != 'YES' || $readOnly) { 524 return "<td></td>\n"; 525 } 526 $html_output = ''; 527 $html_output .= '<td>' . "\n"; 528 $html_output .= '<input type="hidden" name="fields_null_prev' 529 . $column_name_appendix . '"'; 530 if ($real_null_value && !$column['first_timestamp']) { 531 $html_output .= ' value="on"'; 532 } 533 $html_output .= ' />' . "\n"; 534 535 $html_output .= '<input type="checkbox" class="checkbox_null" tabindex="' 536 . ($tabindex + $tabindex_for_null) . '"' 537 . ' name="fields_null' . $column_name_appendix . '"'; 538 if ($real_null_value) { 539 $html_output .= ' checked="checked"'; 540 } 541 $html_output .= ' id="field_' . ($idindex) . '_2" />'; 542 543 // nullify_code is needed by the js nullify() function 544 $nullify_code = $this->getNullifyCodeForNullColumn( 545 $column, 546 $foreigners, 547 $foreignData 548 ); 549 // to be able to generate calls to nullify() in jQuery 550 $html_output .= '<input type="hidden" class="nullify_code" name="nullify_code' 551 . $column_name_appendix . '" value="' . $nullify_code . '" />'; 552 $html_output .= '<input type="hidden" class="hashed_field" name="hashed_field' 553 . $column_name_appendix . '" value="' . $column['Field_md5'] . '" />'; 554 $html_output .= '<input type="hidden" class="multi_edit" name="multi_edit' 555 . $column_name_appendix . '" value="' . Sanitize::escapeJsString($vkey) . '" />'; 556 $html_output .= '</td>' . "\n"; 557 558 return $html_output; 559 } 560 561 /** 562 * Retrieve the nullify code for the null column 563 * 564 * @param array $column description of column in given table 565 * @param array $foreigners keys into foreign fields 566 * @param array $foreignData data about the foreign keys 567 * 568 * @return integer $nullify_code 569 */ 570 private function getNullifyCodeForNullColumn( 571 array $column, 572 array $foreigners, 573 array $foreignData 574 ) { 575 $foreigner = $this->relation->searchColumnInForeigners($foreigners, $column['Field']); 576 if (mb_strstr($column['True_Type'], 'enum')) { 577 if (mb_strlen($column['Type']) > 20) { 578 $nullify_code = '1'; 579 } else { 580 $nullify_code = '2'; 581 } 582 } elseif (mb_strstr($column['True_Type'], 'set')) { 583 $nullify_code = '3'; 584 } elseif (!empty($foreigners) 585 && !empty($foreigner) 586 && $foreignData['foreign_link'] == false 587 ) { 588 // foreign key in a drop-down 589 $nullify_code = '4'; 590 } elseif (!empty($foreigners) 591 && !empty($foreigner) 592 && $foreignData['foreign_link'] == true 593 ) { 594 // foreign key with a browsing icon 595 $nullify_code = '6'; 596 } else { 597 $nullify_code = '5'; 598 } 599 return $nullify_code; 600 } 601 602 /** 603 * Get the HTML elements for value column in insert form 604 * (here, "column" is used in the sense of HTML column in HTML table) 605 * 606 * @param array $column description of column in given table 607 * @param string $backup_field hidden input field 608 * @param string $column_name_appendix the name attribute 609 * @param string $onChangeClause onchange clause for fields 610 * @param integer $tabindex tab index 611 * @param integer $tabindex_for_value offset for the values tabindex 612 * @param integer $idindex id index 613 * @param string $data description of the column field 614 * @param string $special_chars special characters 615 * @param array $foreignData data about the foreign keys 616 * @param array $paramTableDbArray array containing $table and $db 617 * @param integer $rownumber the row number 618 * @param array $titles An HTML IMG tag for a particular icon from 619 * a theme, which may be an actual file or 620 * an icon from a sprite 621 * @param string $text_dir text direction 622 * @param string $special_chars_encoded replaced char if the string starts 623 * with a \r\n pair (0x0d0a) add an extra \n 624 * @param string $vkey [multi_edit]['row_id'] 625 * @param boolean $is_upload is upload or not 626 * @param integer $biggest_max_file_size 0 integer 627 * @param string $default_char_editing default char editing mode which is stored 628 * in the config.inc.php script 629 * @param array $no_support_types list of datatypes that are not (yet) 630 * handled by PMA 631 * @param array $gis_data_types list of GIS data types 632 * @param array $extracted_columnspec associative array containing type, 633 * spec_in_brackets and possibly 634 * enum_set_values (another array) 635 * @param boolean $readOnly is column read only or not 636 * 637 * @return string an html snippet 638 */ 639 private function getValueColumn( 640 array $column, 641 $backup_field, 642 $column_name_appendix, 643 $onChangeClause, 644 $tabindex, 645 $tabindex_for_value, 646 $idindex, 647 $data, 648 $special_chars, 649 array $foreignData, 650 array $paramTableDbArray, 651 $rownumber, 652 array $titles, 653 $text_dir, 654 $special_chars_encoded, 655 $vkey, 656 $is_upload, 657 $biggest_max_file_size, 658 $default_char_editing, 659 array $no_support_types, 660 array $gis_data_types, 661 array $extracted_columnspec, 662 $readOnly 663 ) { 664 // HTML5 data-* attribute data-type 665 $data_type = $this->dbi->types->getTypeClass($column['True_Type']); 666 $html_output = ''; 667 668 if ($foreignData['foreign_link'] == true) { 669 $html_output .= $this->getForeignLink( 670 $column, 671 $backup_field, 672 $column_name_appendix, 673 $onChangeClause, 674 $tabindex, 675 $tabindex_for_value, 676 $idindex, 677 $data, 678 $paramTableDbArray, 679 $rownumber, 680 $titles, 681 $readOnly 682 ); 683 } elseif (is_array($foreignData['disp_row'])) { 684 $html_output .= $this->dispRowForeignData( 685 $backup_field, 686 $column_name_appendix, 687 $onChangeClause, 688 $tabindex, 689 $tabindex_for_value, 690 $idindex, 691 $data, 692 $foreignData, 693 $readOnly 694 ); 695 } elseif ($GLOBALS['cfg']['LongtextDoubleTextarea'] 696 && mb_strstr($column['pma_type'], 'longtext') 697 ) { 698 $html_output .= $this->getTextarea( 699 $column, 700 $backup_field, 701 $column_name_appendix, 702 $onChangeClause, 703 $tabindex, 704 $tabindex_for_value, 705 $idindex, 706 $text_dir, 707 $special_chars_encoded, 708 $data_type, 709 $readOnly 710 ); 711 } elseif (mb_strstr($column['pma_type'], 'text')) { 712 $html_output .= $this->getTextarea( 713 $column, 714 $backup_field, 715 $column_name_appendix, 716 $onChangeClause, 717 $tabindex, 718 $tabindex_for_value, 719 $idindex, 720 $text_dir, 721 $special_chars_encoded, 722 $data_type, 723 $readOnly 724 ); 725 $html_output .= "\n"; 726 if (mb_strlen($special_chars) > 32000) { 727 $html_output .= "</td>\n"; 728 $html_output .= '<td>' . __( 729 'Because of its length,<br /> this column might not be editable.' 730 ); 731 } 732 } elseif ($column['pma_type'] == 'enum') { 733 $html_output .= $this->getPmaTypeEnum( 734 $column, 735 $backup_field, 736 $column_name_appendix, 737 $extracted_columnspec, 738 $onChangeClause, 739 $tabindex, 740 $tabindex_for_value, 741 $idindex, 742 $data, 743 $readOnly 744 ); 745 } elseif ($column['pma_type'] == 'set') { 746 $html_output .= $this->getPmaTypeSet( 747 $column, 748 $extracted_columnspec, 749 $backup_field, 750 $column_name_appendix, 751 $onChangeClause, 752 $tabindex, 753 $tabindex_for_value, 754 $idindex, 755 $data, 756 $readOnly 757 ); 758 } elseif ($column['is_binary'] || $column['is_blob']) { 759 $html_output .= $this->getBinaryAndBlobColumn( 760 $column, 761 $data, 762 $special_chars, 763 $biggest_max_file_size, 764 $backup_field, 765 $column_name_appendix, 766 $onChangeClause, 767 $tabindex, 768 $tabindex_for_value, 769 $idindex, 770 $text_dir, 771 $special_chars_encoded, 772 $vkey, 773 $is_upload, 774 $readOnly 775 ); 776 } elseif (! in_array($column['pma_type'], $no_support_types)) { 777 $html_output .= $this->getValueColumnForOtherDatatypes( 778 $column, 779 $default_char_editing, 780 $backup_field, 781 $column_name_appendix, 782 $onChangeClause, 783 $tabindex, 784 $special_chars, 785 $tabindex_for_value, 786 $idindex, 787 $text_dir, 788 $special_chars_encoded, 789 $data, 790 $extracted_columnspec, 791 $readOnly 792 ); 793 } 794 795 if (in_array($column['pma_type'], $gis_data_types)) { 796 $html_output .= $this->getHtmlForGisDataTypes(); 797 } 798 799 return $html_output; 800 } 801 802 /** 803 * Get HTML for foreign link in insert form 804 * 805 * @param array $column description of column in given table 806 * @param string $backup_field hidden input field 807 * @param string $column_name_appendix the name attribute 808 * @param string $onChangeClause onchange clause for fields 809 * @param integer $tabindex tab index 810 * @param integer $tabindex_for_value offset for the values tabindex 811 * @param integer $idindex id index 812 * @param string $data data to edit 813 * @param array $paramTableDbArray array containing $table and $db 814 * @param integer $rownumber the row number 815 * @param array $titles An HTML IMG tag for a particular icon from 816 * a theme, which may be an actual file or 817 * an icon from a sprite 818 * @param boolean $readOnly is column read only or not 819 * 820 * @return string an html snippet 821 */ 822 private function getForeignLink( 823 array $column, 824 $backup_field, 825 $column_name_appendix, 826 $onChangeClause, 827 $tabindex, 828 $tabindex_for_value, 829 $idindex, 830 $data, 831 array $paramTableDbArray, 832 $rownumber, 833 array $titles, 834 $readOnly 835 ) { 836 list($table, $db) = $paramTableDbArray; 837 $html_output = ''; 838 $html_output .= $backup_field . "\n"; 839 840 $html_output .= '<input type="hidden" name="fields_type' 841 . $column_name_appendix . '" value="foreign" />'; 842 843 $html_output .= '<input type="text" name="fields' . $column_name_appendix . '" ' 844 . 'class="textfield" ' 845 . $onChangeClause . ' ' 846 . ($readOnly ? 'readonly="readonly" ' : '') 847 . 'tabindex="' . ($tabindex + $tabindex_for_value) . '" ' 848 . 'id="field_' . ($idindex) . '_3" ' 849 . 'value="' . htmlspecialchars($data) . '" />'; 850 851 $html_output .= '<a class="ajax browse_foreign" href="browse_foreigners.php" data-post="' 852 . Url::getCommon( 853 array( 854 'db' => $db, 855 'table' => $table, 856 'field' => $column['Field'], 857 'rownumber' => $rownumber, 858 'data' => $data 859 ), 860 '' 861 ) . '">' 862 . str_replace("'", "\'", $titles['Browse']) . '</a>'; 863 return $html_output; 864 } 865 866 /** 867 * Get HTML to display foreign data 868 * 869 * @param string $backup_field hidden input field 870 * @param string $column_name_appendix the name attribute 871 * @param string $onChangeClause onchange clause for fields 872 * @param integer $tabindex tab index 873 * @param integer $tabindex_for_value offset for the values tabindex 874 * @param integer $idindex id index 875 * @param string $data data to edit 876 * @param array $foreignData data about the foreign keys 877 * @param boolean $readOnly is display read only or not 878 * 879 * @return string an html snippet 880 */ 881 private function dispRowForeignData( 882 $backup_field, 883 $column_name_appendix, 884 $onChangeClause, 885 $tabindex, 886 $tabindex_for_value, 887 $idindex, 888 $data, 889 array $foreignData, 890 $readOnly 891 ) { 892 $html_output = ''; 893 $html_output .= $backup_field . "\n"; 894 $html_output .= '<input type="hidden"' 895 . ' name="fields_type' . $column_name_appendix . '"' 896 . ' value="foreign" />'; 897 898 $html_output .= '<select name="fields' . $column_name_appendix . '"' 899 . ' ' . $onChangeClause 900 . ' class="textfield"' 901 . ($readOnly ? ' disabled' : '') 902 . ' tabindex="' . ($tabindex + $tabindex_for_value) . '"' 903 . ' id="field_' . $idindex . '_3">'; 904 $html_output .= $this->relation->foreignDropdown( 905 $foreignData['disp_row'], 906 $foreignData['foreign_field'], 907 $foreignData['foreign_display'], 908 $data, 909 $GLOBALS['cfg']['ForeignKeyMaxLimit'] 910 ); 911 $html_output .= '</select>'; 912 913 //Add hidden input, as disabled <select> input does not included in POST. 914 if ($readOnly) { 915 $html_output .= '<input name="fields' . $column_name_appendix . '"' 916 . ' type="hidden" value="' . htmlspecialchars($data) . '">'; 917 } 918 919 return $html_output; 920 } 921 922 /** 923 * Get HTML textarea for insert form 924 * 925 * @param array $column column information 926 * @param string $backup_field hidden input field 927 * @param string $column_name_appendix the name attribute 928 * @param string $onChangeClause onchange clause for fields 929 * @param integer $tabindex tab index 930 * @param integer $tabindex_for_value offset for the values tabindex 931 * @param integer $idindex id index 932 * @param string $text_dir text direction 933 * @param string $special_chars_encoded replaced char if the string starts 934 * with a \r\n pair (0x0d0a) add an extra \n 935 * @param string $data_type the html5 data-* attribute type 936 * @param boolean $readOnly is column read only or not 937 * 938 * @return string an html snippet 939 */ 940 private function getTextarea( 941 array $column, 942 $backup_field, 943 $column_name_appendix, 944 $onChangeClause, 945 $tabindex, 946 $tabindex_for_value, 947 $idindex, 948 $text_dir, 949 $special_chars_encoded, 950 $data_type, 951 $readOnly 952 ) { 953 $the_class = ''; 954 $textAreaRows = $GLOBALS['cfg']['TextareaRows']; 955 $textareaCols = $GLOBALS['cfg']['TextareaCols']; 956 957 if ($column['is_char']) { 958 /** 959 * @todo clarify the meaning of the "textfield" class and explain 960 * why character columns have the "char" class instead 961 */ 962 $the_class = 'char'; 963 $textAreaRows = $GLOBALS['cfg']['CharTextareaRows']; 964 $textareaCols = $GLOBALS['cfg']['CharTextareaCols']; 965 $extracted_columnspec = Util::extractColumnSpec( 966 $column['Type'] 967 ); 968 $maxlength = $extracted_columnspec['spec_in_brackets']; 969 } elseif ($GLOBALS['cfg']['LongtextDoubleTextarea'] 970 && mb_strstr($column['pma_type'], 'longtext') 971 ) { 972 $textAreaRows = $GLOBALS['cfg']['TextareaRows'] * 2; 973 $textareaCols = $GLOBALS['cfg']['TextareaCols'] * 2; 974 } 975 $html_output = $backup_field . "\n" 976 . '<textarea name="fields' . $column_name_appendix . '"' 977 . ' class="' . $the_class . '"' 978 . ($readOnly ? ' readonly="readonly"' : '') 979 . (isset($maxlength) ? ' data-maxlength="' . $maxlength . '"' : '') 980 . ' rows="' . $textAreaRows . '"' 981 . ' cols="' . $textareaCols . '"' 982 . ' dir="' . $text_dir . '"' 983 . ' id="field_' . ($idindex) . '_3"' 984 . (! empty($onChangeClause) ? ' ' . $onChangeClause : '') 985 . ' tabindex="' . ($tabindex + $tabindex_for_value) . '"' 986 . ' data-type="' . $data_type . '">' 987 . $special_chars_encoded 988 . '</textarea>'; 989 990 return $html_output; 991 } 992 993 /** 994 * Get HTML for enum type 995 * 996 * @param array $column description of column in given table 997 * @param string $backup_field hidden input field 998 * @param string $column_name_appendix the name attribute 999 * @param array $extracted_columnspec associative array containing type, 1000 * spec_in_brackets and possibly 1001 * enum_set_values (another array) 1002 * @param string $onChangeClause onchange clause for fields 1003 * @param integer $tabindex tab index 1004 * @param integer $tabindex_for_value offset for the values tabindex 1005 * @param integer $idindex id index 1006 * @param mixed $data data to edit 1007 * @param boolean $readOnly is column read only or not 1008 * 1009 * @return string an html snippet 1010 */ 1011 private function getPmaTypeEnum( 1012 array $column, 1013 $backup_field, 1014 $column_name_appendix, 1015 array $extracted_columnspec, 1016 $onChangeClause, 1017 $tabindex, 1018 $tabindex_for_value, 1019 $idindex, 1020 $data, 1021 $readOnly 1022 ) { 1023 $html_output = ''; 1024 if (! isset($column['values'])) { 1025 $column['values'] = $this->getColumnEnumValues( 1026 $column, 1027 $extracted_columnspec 1028 ); 1029 } 1030 $column_enum_values = $column['values']; 1031 $html_output .= '<input type="hidden" name="fields_type' 1032 . $column_name_appendix . '" value="enum" />'; 1033 $html_output .= "\n" . ' ' . $backup_field . "\n"; 1034 if (mb_strlen($column['Type']) > 20) { 1035 $html_output .= $this->getDropDownDependingOnLength( 1036 $column, 1037 $column_name_appendix, 1038 $onChangeClause, 1039 $tabindex, 1040 $tabindex_for_value, 1041 $idindex, 1042 $data, 1043 $column_enum_values, 1044 $readOnly 1045 ); 1046 } else { 1047 $html_output .= $this->getRadioButtonDependingOnLength( 1048 $column_name_appendix, 1049 $onChangeClause, 1050 $tabindex, 1051 $column, 1052 $tabindex_for_value, 1053 $idindex, 1054 $data, 1055 $column_enum_values, 1056 $readOnly 1057 ); 1058 } 1059 return $html_output; 1060 } 1061 1062 /** 1063 * Get column values 1064 * 1065 * @param array $column description of column in given table 1066 * @param array $extracted_columnspec associative array containing type, 1067 * spec_in_brackets and possibly enum_set_values 1068 * (another array) 1069 * 1070 * @return array column values as an associative array 1071 */ 1072 private function getColumnEnumValues(array $column, array $extracted_columnspec) 1073 { 1074 $column['values'] = array(); 1075 foreach ($extracted_columnspec['enum_set_values'] as $val) { 1076 $column['values'][] = array( 1077 'plain' => $val, 1078 'html' => htmlspecialchars($val), 1079 ); 1080 } 1081 return $column['values']; 1082 } 1083 1084 /** 1085 * Get HTML drop down for more than 20 string length 1086 * 1087 * @param array $column description of column in given table 1088 * @param string $column_name_appendix the name attribute 1089 * @param string $onChangeClause onchange clause for fields 1090 * @param integer $tabindex tab index 1091 * @param integer $tabindex_for_value offset for the values tabindex 1092 * @param integer $idindex id index 1093 * @param string $data data to edit 1094 * @param array $column_enum_values $column['values'] 1095 * @param boolean $readOnly is column read only or not 1096 * 1097 * @return string an html snippet 1098 */ 1099 private function getDropDownDependingOnLength( 1100 array $column, 1101 $column_name_appendix, 1102 $onChangeClause, 1103 $tabindex, 1104 $tabindex_for_value, 1105 $idindex, 1106 $data, 1107 array $column_enum_values, 1108 $readOnly 1109 ) { 1110 $html_output = '<select name="fields' . $column_name_appendix . '"' 1111 . ' ' . $onChangeClause 1112 . ' class="textfield"' 1113 . ' tabindex="' . ($tabindex + $tabindex_for_value) . '"' 1114 . ($readOnly ? ' disabled' : '') 1115 . ' id="field_' . ($idindex) . '_3">'; 1116 $html_output .= '<option value=""> </option>' . "\n"; 1117 1118 $selected_html = ''; 1119 foreach ($column_enum_values as $enum_value) { 1120 $html_output .= '<option value="' . $enum_value['html'] . '"'; 1121 if ($data == $enum_value['plain'] 1122 || ($data == '' 1123 && (! isset($_POST['where_clause']) || $column['Null'] != 'YES') 1124 && isset($column['Default']) 1125 && $enum_value['plain'] == $column['Default']) 1126 ) { 1127 $html_output .= ' selected="selected"'; 1128 $selected_html = $enum_value['html']; 1129 } 1130 $html_output .= '>' . $enum_value['html'] . '</option>' . "\n"; 1131 } 1132 $html_output .= '</select>'; 1133 1134 //Add hidden input, as disabled <select> input does not included in POST. 1135 if ($readOnly) { 1136 $html_output .= '<input name="fields' . $column_name_appendix . '"' 1137 . ' type="hidden" value="' . $selected_html . '">'; 1138 } 1139 return $html_output; 1140 } 1141 1142 /** 1143 * Get HTML radio button for less than 20 string length 1144 * 1145 * @param string $column_name_appendix the name attribute 1146 * @param string $onChangeClause onchange clause for fields 1147 * @param integer $tabindex tab index 1148 * @param array $column description of column in given table 1149 * @param integer $tabindex_for_value offset for the values tabindex 1150 * @param integer $idindex id index 1151 * @param string $data data to edit 1152 * @param array $column_enum_values $column['values'] 1153 * @param boolean $readOnly is column read only or not 1154 * 1155 * @return string an html snippet 1156 */ 1157 private function getRadioButtonDependingOnLength( 1158 $column_name_appendix, 1159 $onChangeClause, 1160 $tabindex, 1161 array $column, 1162 $tabindex_for_value, 1163 $idindex, 1164 $data, 1165 array $column_enum_values, 1166 $readOnly 1167 ) { 1168 $j = 0; 1169 $html_output = ''; 1170 foreach ($column_enum_values as $enum_value) { 1171 $html_output .= ' ' 1172 . '<input type="radio" name="fields' . $column_name_appendix . '"' 1173 . ' class="textfield"' 1174 . ' value="' . $enum_value['html'] . '"' 1175 . ' id="field_' . ($idindex) . '_3_' . $j . '"' 1176 . ' ' . $onChangeClause; 1177 if ($data == $enum_value['plain'] 1178 || ($data == '' 1179 && (! isset($_POST['where_clause']) || $column['Null'] != 'YES') 1180 && isset($column['Default']) 1181 && $enum_value['plain'] == $column['Default']) 1182 ) { 1183 $html_output .= ' checked="checked"'; 1184 } elseif ($readOnly) { 1185 $html_output .= ' disabled'; 1186 } 1187 $html_output .= ' tabindex="' . ($tabindex + $tabindex_for_value) . '" />'; 1188 $html_output .= '<label for="field_' . $idindex . '_3_' . $j . '">' 1189 . $enum_value['html'] . '</label>' . "\n"; 1190 $j++; 1191 } 1192 return $html_output; 1193 } 1194 1195 /** 1196 * Get the HTML for 'set' pma type 1197 * 1198 * @param array $column description of column in given table 1199 * @param array $extracted_columnspec associative array containing type, 1200 * spec_in_brackets and possibly 1201 * enum_set_values (another array) 1202 * @param string $backup_field hidden input field 1203 * @param string $column_name_appendix the name attribute 1204 * @param string $onChangeClause onchange clause for fields 1205 * @param integer $tabindex tab index 1206 * @param integer $tabindex_for_value offset for the values tabindex 1207 * @param integer $idindex id index 1208 * @param string $data description of the column field 1209 * @param boolean $readOnly is column read only or not 1210 * 1211 * @return string an html snippet 1212 */ 1213 private function getPmaTypeSet( 1214 array $column, 1215 array $extracted_columnspec, 1216 $backup_field, 1217 $column_name_appendix, 1218 $onChangeClause, 1219 $tabindex, 1220 $tabindex_for_value, 1221 $idindex, 1222 $data, 1223 $readOnly 1224 ) { 1225 list($column_set_values, $select_size) = $this->getColumnSetValueAndSelectSize( 1226 $column, 1227 $extracted_columnspec 1228 ); 1229 $vset = array_flip(explode(',', $data)); 1230 $html_output = $backup_field . "\n"; 1231 $html_output .= '<input type="hidden" name="fields_type' 1232 . $column_name_appendix . '" value="set" />'; 1233 $html_output .= '<select name="fields' . $column_name_appendix . '[]' . '"' 1234 . ' class="textfield"' 1235 . ($readOnly ? ' disabled' : '') 1236 . ' size="' . $select_size . '"' 1237 . ' multiple="multiple"' 1238 . ' ' . $onChangeClause 1239 . ' tabindex="' . ($tabindex + $tabindex_for_value) . '"' 1240 . ' id="field_' . ($idindex) . '_3">'; 1241 1242 $selected_html = ''; 1243 foreach ($column_set_values as $column_set_value) { 1244 $html_output .= '<option value="' . $column_set_value['html'] . '"'; 1245 if (isset($vset[$column_set_value['plain']])) { 1246 $html_output .= ' selected="selected"'; 1247 $selected_html = $column_set_value['html']; 1248 } 1249 $html_output .= '>' . $column_set_value['html'] . '</option>' . "\n"; 1250 } 1251 $html_output .= '</select>'; 1252 1253 //Add hidden input, as disabled <select> input does not included in POST. 1254 if ($readOnly) { 1255 $html_output .= '<input name="fields' . $column_name_appendix . '[]' . '"' 1256 . ' type="hidden" value="' . $selected_html . '">'; 1257 } 1258 return $html_output; 1259 } 1260 1261 /** 1262 * Retrieve column 'set' value and select size 1263 * 1264 * @param array $column description of column in given table 1265 * @param array $extracted_columnspec associative array containing type, 1266 * spec_in_brackets and possibly enum_set_values 1267 * (another array) 1268 * 1269 * @return array $column['values'], $column['select_size'] 1270 */ 1271 private function getColumnSetValueAndSelectSize( 1272 array $column, 1273 array $extracted_columnspec 1274 ) { 1275 if (! isset($column['values'])) { 1276 $column['values'] = array(); 1277 foreach ($extracted_columnspec['enum_set_values'] as $val) { 1278 $column['values'][] = array( 1279 'plain' => $val, 1280 'html' => htmlspecialchars($val), 1281 ); 1282 } 1283 $column['select_size'] = min(4, count($column['values'])); 1284 } 1285 return array($column['values'], $column['select_size']); 1286 } 1287 1288 /** 1289 * Get HTML for binary and blob column 1290 * 1291 * @param array $column description of column in given table 1292 * @param string $data data to edit 1293 * @param string $special_chars special characters 1294 * @param integer $biggest_max_file_size biggest max file size for uploading 1295 * @param string $backup_field hidden input field 1296 * @param string $column_name_appendix the name attribute 1297 * @param string $onChangeClause onchange clause for fields 1298 * @param integer $tabindex tab index 1299 * @param integer $tabindex_for_value offset for the values tabindex 1300 * @param integer $idindex id index 1301 * @param string $text_dir text direction 1302 * @param string $special_chars_encoded replaced char if the string starts 1303 * with a \r\n pair (0x0d0a) add an extra \n 1304 * @param string $vkey [multi_edit]['row_id'] 1305 * @param boolean $is_upload is upload or not 1306 * @param boolean $readOnly is column read only or not 1307 * 1308 * @return string an html snippet 1309 */ 1310 private function getBinaryAndBlobColumn( 1311 array $column, 1312 $data, 1313 $special_chars, 1314 $biggest_max_file_size, 1315 $backup_field, 1316 $column_name_appendix, 1317 $onChangeClause, 1318 $tabindex, 1319 $tabindex_for_value, 1320 $idindex, 1321 $text_dir, 1322 $special_chars_encoded, 1323 $vkey, 1324 $is_upload, 1325 $readOnly 1326 ) { 1327 $html_output = ''; 1328 // Add field type : Protected or Hexadecimal 1329 $fields_type_html = '<input type="hidden" name="fields_type' 1330 . $column_name_appendix . '" value="%s" />'; 1331 // Default value : hex 1332 $fields_type_val = 'hex'; 1333 if (($GLOBALS['cfg']['ProtectBinary'] === 'blob' && $column['is_blob']) 1334 || ($GLOBALS['cfg']['ProtectBinary'] === 'all') 1335 || ($GLOBALS['cfg']['ProtectBinary'] === 'noblob' && !$column['is_blob']) 1336 ) { 1337 $html_output .= __('Binary - do not edit'); 1338 if (isset($data)) { 1339 $data_size = Util::formatByteDown( 1340 mb_strlen(stripslashes($data)), 1341 3, 1342 1 1343 ); 1344 $html_output .= ' (' . $data_size[0] . ' ' . $data_size[1] . ')'; 1345 unset($data_size); 1346 } 1347 $fields_type_val = 'protected'; 1348 $html_output .= '<input type="hidden" name="fields' 1349 . $column_name_appendix . '" value="" />'; 1350 } elseif ($column['is_blob'] 1351 || ($column['len'] > $GLOBALS['cfg']['LimitChars']) 1352 ) { 1353 $html_output .= "\n" . $this->getTextarea( 1354 $column, 1355 $backup_field, 1356 $column_name_appendix, 1357 $onChangeClause, 1358 $tabindex, 1359 $tabindex_for_value, 1360 $idindex, 1361 $text_dir, 1362 $special_chars_encoded, 1363 'HEX', 1364 $readOnly 1365 ); 1366 } else { 1367 // field size should be at least 4 and max $GLOBALS['cfg']['LimitChars'] 1368 $fieldsize = min(max($column['len'], 4), $GLOBALS['cfg']['LimitChars']); 1369 $html_output .= "\n" . $backup_field . "\n" . $this->getHtmlInput( 1370 $column, 1371 $column_name_appendix, 1372 $special_chars, 1373 $fieldsize, 1374 $onChangeClause, 1375 $tabindex, 1376 $tabindex_for_value, 1377 $idindex, 1378 'HEX', 1379 $readOnly 1380 ); 1381 } 1382 $html_output .= sprintf($fields_type_html, $fields_type_val); 1383 1384 if ($is_upload && $column['is_blob'] && !$readOnly) { 1385 // We don't want to prevent users from using 1386 // browser's default drag-drop feature on some page(s), 1387 // so we add noDragDrop class to the input 1388 $html_output .= '<br />' 1389 . '<input type="file"' 1390 . ' name="fields_upload' . $vkey . '[' . $column['Field_md5'] . ']"' 1391 . ' class="textfield noDragDrop" id="field_' . $idindex . '_3" size="10"' 1392 . ' ' . $onChangeClause . '/> '; 1393 list($html_out,) = $this->getMaxUploadSize( 1394 $column, 1395 $biggest_max_file_size 1396 ); 1397 $html_output .= $html_out; 1398 } 1399 1400 if (!empty($GLOBALS['cfg']['UploadDir']) && !$readOnly) { 1401 $html_output .= $this->getSelectOptionForUpload($vkey, $column); 1402 } 1403 1404 return $html_output; 1405 } 1406 1407 /** 1408 * Get HTML input type 1409 * 1410 * @param array $column description of column in given table 1411 * @param string $column_name_appendix the name attribute 1412 * @param string $special_chars special characters 1413 * @param integer $fieldsize html field size 1414 * @param string $onChangeClause onchange clause for fields 1415 * @param integer $tabindex tab index 1416 * @param integer $tabindex_for_value offset for the values tabindex 1417 * @param integer $idindex id index 1418 * @param string $data_type the html5 data-* attribute type 1419 * @param boolean $readOnly is column read only or not 1420 * 1421 * @return string an html snippet 1422 */ 1423 private function getHtmlInput( 1424 array $column, 1425 $column_name_appendix, 1426 $special_chars, 1427 $fieldsize, 1428 $onChangeClause, 1429 $tabindex, 1430 $tabindex_for_value, 1431 $idindex, 1432 $data_type, 1433 $readOnly 1434 ) { 1435 $input_type = 'text'; 1436 // do not use the 'date' or 'time' types here; they have no effect on some 1437 // browsers and create side effects (see bug #4218) 1438 1439 $the_class = 'textfield'; 1440 // verify True_Type which does not contain the parentheses and length 1441 if ($readOnly) { 1442 //NOOP. Disable date/timepicker 1443 } elseif ($column['True_Type'] === 'date') { 1444 $the_class .= ' datefield'; 1445 } elseif ($column['True_Type'] === 'time') { 1446 $the_class .= ' timefield'; 1447 } elseif ($column['True_Type'] === 'datetime' 1448 || $column['True_Type'] === 'timestamp' 1449 ) { 1450 $the_class .= ' datetimefield'; 1451 } 1452 $input_min_max = false; 1453 if (in_array($column['True_Type'], $this->dbi->types->getIntegerTypes())) { 1454 $extracted_columnspec = Util::extractColumnSpec( 1455 $column['Type'] 1456 ); 1457 $is_unsigned = $extracted_columnspec['unsigned']; 1458 $min_max_values = $this->dbi->types->getIntegerRange( 1459 $column['True_Type'], 1460 ! $is_unsigned 1461 ); 1462 $input_min_max = 'min="' . $min_max_values[0] . '" ' 1463 . 'max="' . $min_max_values[1] . '"'; 1464 $data_type = 'INT'; 1465 } 1466 return '<input type="' . $input_type . '"' 1467 . ' name="fields' . $column_name_appendix . '"' 1468 . ' value="' . $special_chars . '" size="' . $fieldsize . '"' 1469 . ((isset($column['is_char']) && $column['is_char']) 1470 ? ' data-maxlength="' . $fieldsize . '"' 1471 : '') 1472 . ($readOnly ? ' readonly="readonly"' : '') 1473 . ($input_min_max !== false ? ' ' . $input_min_max : '') 1474 . ' data-type="' . $data_type . '"' 1475 . ($input_type === 'time' ? ' step="1"' : '') 1476 . ' class="' . $the_class . '" ' . $onChangeClause 1477 . ' tabindex="' . ($tabindex + $tabindex_for_value) . '"' 1478 . ' id="field_' . ($idindex) . '_3" />'; 1479 } 1480 1481 /** 1482 * Get HTML select option for upload 1483 * 1484 * @param string $vkey [multi_edit]['row_id'] 1485 * @param array $column description of column in given table 1486 * 1487 * @return string|void an html snippet 1488 */ 1489 private function getSelectOptionForUpload($vkey, array $column) 1490 { 1491 $files = FileListing::getFileSelectOptions( 1492 Util::userDir($GLOBALS['cfg']['UploadDir']) 1493 ); 1494 1495 if ($files === false) { 1496 return '<span style="color:red">' . __('Error') . '</span><br />' . "\n" 1497 . __('The directory you set for upload work cannot be reached.') . "\n"; 1498 } elseif (!empty($files)) { 1499 return "<br />\n" 1500 . '<i>' . __('Or') . '</i>' . ' ' 1501 . __('web server upload directory:') . '<br />' . "\n" 1502 . '<select size="1" name="fields_uploadlocal' 1503 . $vkey . '[' . $column['Field_md5'] . ']">' . "\n" 1504 . '<option value="" selected="selected"></option>' . "\n" 1505 . $files 1506 . '</select>' . "\n"; 1507 } 1508 1509 return null; 1510 } 1511 1512 /** 1513 * Retrieve the maximum upload file size 1514 * 1515 * @param array $column description of column in given table 1516 * @param integer $biggest_max_file_size biggest max file size for uploading 1517 * 1518 * @return array an html snippet and $biggest_max_file_size 1519 */ 1520 private function getMaxUploadSize(array $column, $biggest_max_file_size) 1521 { 1522 // find maximum upload size, based on field type 1523 /** 1524 * @todo with functions this is not so easy, as you can basically 1525 * process any data with function like MD5 1526 */ 1527 global $max_upload_size; 1528 $max_field_sizes = array( 1529 'tinyblob' => '256', 1530 'blob' => '65536', 1531 'mediumblob' => '16777216', 1532 'longblob' => '4294967296' // yeah, really 1533 ); 1534 1535 $this_field_max_size = $max_upload_size; // from PHP max 1536 if ($this_field_max_size > $max_field_sizes[$column['pma_type']]) { 1537 $this_field_max_size = $max_field_sizes[$column['pma_type']]; 1538 } 1539 $html_output 1540 = Util::getFormattedMaximumUploadSize( 1541 $this_field_max_size 1542 ) . "\n"; 1543 // do not generate here the MAX_FILE_SIZE, because we should 1544 // put only one in the form to accommodate the biggest field 1545 if ($this_field_max_size > $biggest_max_file_size) { 1546 $biggest_max_file_size = $this_field_max_size; 1547 } 1548 return array($html_output, $biggest_max_file_size); 1549 } 1550 1551 /** 1552 * Get HTML for the Value column of other datatypes 1553 * (here, "column" is used in the sense of HTML column in HTML table) 1554 * 1555 * @param array $column description of column in given table 1556 * @param string $default_char_editing default char editing mode which is stored 1557 * in the config.inc.php script 1558 * @param string $backup_field hidden input field 1559 * @param string $column_name_appendix the name attribute 1560 * @param string $onChangeClause onchange clause for fields 1561 * @param integer $tabindex tab index 1562 * @param string $special_chars special characters 1563 * @param integer $tabindex_for_value offset for the values tabindex 1564 * @param integer $idindex id index 1565 * @param string $text_dir text direction 1566 * @param string $special_chars_encoded replaced char if the string starts 1567 * with a \r\n pair (0x0d0a) add an extra \n 1568 * @param string $data data to edit 1569 * @param array $extracted_columnspec associative array containing type, 1570 * spec_in_brackets and possibly 1571 * enum_set_values (another array) 1572 * @param boolean $readOnly is column read only or not 1573 * 1574 * @return string an html snippet 1575 */ 1576 private function getValueColumnForOtherDatatypes( 1577 array $column, 1578 $default_char_editing, 1579 $backup_field, 1580 $column_name_appendix, 1581 $onChangeClause, 1582 $tabindex, 1583 $special_chars, 1584 $tabindex_for_value, 1585 $idindex, 1586 $text_dir, 1587 $special_chars_encoded, 1588 $data, 1589 array $extracted_columnspec, 1590 $readOnly 1591 ) { 1592 // HTML5 data-* attribute data-type 1593 $data_type = $this->dbi->types->getTypeClass($column['True_Type']); 1594 $fieldsize = $this->getColumnSize($column, $extracted_columnspec); 1595 $html_output = $backup_field . "\n"; 1596 if ($column['is_char'] 1597 && ($GLOBALS['cfg']['CharEditing'] == 'textarea' 1598 || mb_strpos($data, "\n") !== false) 1599 ) { 1600 $html_output .= "\n"; 1601 $GLOBALS['cfg']['CharEditing'] = $default_char_editing; 1602 $html_output .= $this->getTextarea( 1603 $column, 1604 $backup_field, 1605 $column_name_appendix, 1606 $onChangeClause, 1607 $tabindex, 1608 $tabindex_for_value, 1609 $idindex, 1610 $text_dir, 1611 $special_chars_encoded, 1612 $data_type, 1613 $readOnly 1614 ); 1615 } else { 1616 $html_output .= $this->getHtmlInput( 1617 $column, 1618 $column_name_appendix, 1619 $special_chars, 1620 $fieldsize, 1621 $onChangeClause, 1622 $tabindex, 1623 $tabindex_for_value, 1624 $idindex, 1625 $data_type, 1626 $readOnly 1627 ); 1628 1629 if (preg_match('/(VIRTUAL|PERSISTENT|GENERATED)/', $column['Extra']) && $column['Extra'] !== 'DEFAULT_GENERATED') { 1630 $html_output .= '<input type="hidden" name="virtual' 1631 . $column_name_appendix . '" value="1" />'; 1632 } 1633 if ($column['Extra'] == 'auto_increment') { 1634 $html_output .= '<input type="hidden" name="auto_increment' 1635 . $column_name_appendix . '" value="1" />'; 1636 } 1637 if (substr($column['pma_type'], 0, 9) == 'timestamp') { 1638 $html_output .= '<input type="hidden" name="fields_type' 1639 . $column_name_appendix . '" value="timestamp" />'; 1640 } 1641 if (substr($column['pma_type'], 0, 8) == 'datetime') { 1642 $html_output .= '<input type="hidden" name="fields_type' 1643 . $column_name_appendix . '" value="datetime" />'; 1644 } 1645 if ($column['True_Type'] == 'bit') { 1646 $html_output .= '<input type="hidden" name="fields_type' 1647 . $column_name_appendix . '" value="bit" />'; 1648 } 1649 if ($column['pma_type'] == 'date' 1650 || $column['pma_type'] == 'datetime' 1651 || substr($column['pma_type'], 0, 9) == 'timestamp' 1652 ) { 1653 // the _3 suffix points to the date field 1654 // the _2 suffix points to the corresponding NULL checkbox 1655 // in dateFormat, 'yy' means the year with 4 digits 1656 } 1657 } 1658 return $html_output; 1659 } 1660 1661 /** 1662 * Get the field size 1663 * 1664 * @param array $column description of column in given table 1665 * @param array $extracted_columnspec associative array containing type, 1666 * spec_in_brackets and possibly enum_set_values 1667 * (another array) 1668 * 1669 * @return integer field size 1670 */ 1671 private function getColumnSize(array $column, array $extracted_columnspec) 1672 { 1673 if ($column['is_char']) { 1674 $fieldsize = $extracted_columnspec['spec_in_brackets']; 1675 if ($fieldsize > $GLOBALS['cfg']['MaxSizeForInputField']) { 1676 /** 1677 * This case happens for CHAR or VARCHAR columns which have 1678 * a size larger than the maximum size for input field. 1679 */ 1680 $GLOBALS['cfg']['CharEditing'] = 'textarea'; 1681 } 1682 } else { 1683 /** 1684 * This case happens for example for INT or DATE columns; 1685 * in these situations, the value returned in $column['len'] 1686 * seems appropriate. 1687 */ 1688 $fieldsize = $column['len']; 1689 } 1690 return min( 1691 max($fieldsize, $GLOBALS['cfg']['MinSizeForInputField']), 1692 $GLOBALS['cfg']['MaxSizeForInputField'] 1693 ); 1694 } 1695 1696 /** 1697 * Get HTML for gis data types 1698 * 1699 * @return string an html snippet 1700 */ 1701 private function getHtmlForGisDataTypes() 1702 { 1703 $edit_str = Util::getIcon('b_edit', __('Edit/Insert')); 1704 return '<span class="open_gis_editor">' 1705 . Util::linkOrButton( 1706 '#', 1707 $edit_str, 1708 array(), 1709 '_blank' 1710 ) 1711 . '</span>'; 1712 } 1713 1714 /** 1715 * get html for continue insertion form 1716 * 1717 * @param string $table name of the table 1718 * @param string $db name of the database 1719 * @param array $where_clause_array array of where clauses 1720 * @param string $err_url error url 1721 * 1722 * @return string an html snippet 1723 */ 1724 public function getContinueInsertionForm( 1725 $table, 1726 $db, 1727 array $where_clause_array, 1728 $err_url 1729 ) { 1730 return Template::get('table/insert/continue_insertion_form')->render([ 1731 'db' => $db, 1732 'table' => $table, 1733 'where_clause_array' => $where_clause_array, 1734 'err_url' => $err_url, 1735 'goto' => $GLOBALS['goto'], 1736 'sql_query' => isset($_POST['sql_query']) ? $_POST['sql_query'] : null, 1737 'has_where_clause' => isset($_POST['where_clause']), 1738 'insert_rows_default' => $GLOBALS['cfg']['InsertRows'], 1739 ]); 1740 } 1741 1742 /** 1743 * Get action panel 1744 * 1745 * @param array|null $where_clause where clause 1746 * @param string $after_insert insert mode, e.g. new_insert, same_insert 1747 * @param integer $tabindex tab index 1748 * @param integer $tabindex_for_value offset for the values tabindex 1749 * @param boolean $found_unique_key boolean variable for unique key 1750 * 1751 * @return string an html snippet 1752 */ 1753 public function getActionsPanel( 1754 $where_clause, 1755 $after_insert, 1756 $tabindex, 1757 $tabindex_for_value, 1758 $found_unique_key 1759 ) { 1760 $html_output = '<fieldset id="actions_panel">' 1761 . '<table cellpadding="5" cellspacing="0" class="tdblock width100">' 1762 . '<tr>' 1763 . '<td class="nowrap vmiddle">' 1764 . $this->getSubmitTypeDropDown($where_clause, $tabindex, $tabindex_for_value) 1765 . "\n"; 1766 1767 $html_output .= '</td>' 1768 . '<td class="vmiddle">' 1769 . ' <strong>' 1770 . __('and then') . '</strong> ' 1771 . '</td>' 1772 . '<td class="nowrap vmiddle">' 1773 . $this->getAfterInsertDropDown( 1774 $where_clause, 1775 $after_insert, 1776 $found_unique_key 1777 ) 1778 . '</td>' 1779 . '</tr>'; 1780 $html_output .='<tr>' 1781 . $this->getSubmitAndResetButtonForActionsPanel($tabindex, $tabindex_for_value) 1782 . '</tr>' 1783 . '</table>' 1784 . '</fieldset>'; 1785 return $html_output; 1786 } 1787 1788 /** 1789 * Get a HTML drop down for submit types 1790 * 1791 * @param array|null $where_clause where clause 1792 * @param integer $tabindex tab index 1793 * @param integer $tabindex_for_value offset for the values tabindex 1794 * 1795 * @return string an html snippet 1796 */ 1797 private function getSubmitTypeDropDown( 1798 $where_clause, 1799 $tabindex, 1800 $tabindex_for_value 1801 ) { 1802 $html_output = '<select name="submit_type" class="control_at_footer" tabindex="' 1803 . ($tabindex + $tabindex_for_value + 1) . '">'; 1804 if (isset($where_clause)) { 1805 $html_output .= '<option value="save">' . __('Save') . '</option>'; 1806 } 1807 $html_output .= '<option value="insert">' 1808 . __('Insert as new row') 1809 . '</option>' 1810 . '<option value="insertignore">' 1811 . __('Insert as new row and ignore errors') 1812 . '</option>' 1813 . '<option value="showinsert">' 1814 . __('Show insert query') 1815 . '</option>' 1816 . '</select>'; 1817 return $html_output; 1818 } 1819 1820 /** 1821 * Get HTML drop down for after insert 1822 * 1823 * @param array|null $where_clause where clause 1824 * @param string $after_insert insert mode, e.g. new_insert, same_insert 1825 * @param boolean $found_unique_key boolean variable for unique key 1826 * 1827 * @return string an html snippet 1828 */ 1829 private function getAfterInsertDropDown($where_clause, $after_insert, $found_unique_key) 1830 { 1831 $html_output = '<select name="after_insert" class="control_at_footer">' 1832 . '<option value="back" ' 1833 . ($after_insert == 'back' ? 'selected="selected"' : '') . '>' 1834 . __('Go back to previous page') . '</option>' 1835 . '<option value="new_insert" ' 1836 . ($after_insert == 'new_insert' ? 'selected="selected"' : '') . '>' 1837 . __('Insert another new row') . '</option>'; 1838 1839 if (isset($where_clause)) { 1840 $html_output .= '<option value="same_insert" ' 1841 . ($after_insert == 'same_insert' ? 'selected="selected"' : '') . '>' 1842 . __('Go back to this page') . '</option>'; 1843 1844 // If we have just numeric primary key, we can also edit next 1845 // in 2.8.2, we were looking for `field_name` = numeric_value 1846 //if (preg_match('@^[\s]*`[^`]*` = [0-9]+@', $where_clause)) { 1847 // in 2.9.0, we are looking for `table_name`.`field_name` = numeric_value 1848 $is_numeric = false; 1849 if (! is_array($where_clause)) { 1850 $where_clause = array($where_clause); 1851 } 1852 for ($i = 0, $nb = count($where_clause); $i < $nb; $i++) { 1853 // preg_match() returns 1 if there is a match 1854 $is_numeric = (preg_match( 1855 '@^[\s]*`[^`]*`[\.]`[^`]*` = [0-9]+@', 1856 $where_clause[$i] 1857 ) == 1); 1858 if ($is_numeric === true) { 1859 break; 1860 } 1861 } 1862 if ($found_unique_key && $is_numeric) { 1863 $html_output .= '<option value="edit_next" ' 1864 . ($after_insert == 'edit_next' ? 'selected="selected"' : '') . '>' 1865 . __('Edit next row') . '</option>'; 1866 } 1867 } 1868 $html_output .= '</select>'; 1869 return $html_output; 1870 } 1871 1872 /** 1873 * get Submit button and Reset button for action panel 1874 * 1875 * @param integer $tabindex tab index 1876 * @param integer $tabindex_for_value offset for the values tabindex 1877 * 1878 * @return string an html snippet 1879 */ 1880 private function getSubmitAndResetButtonForActionsPanel($tabindex, $tabindex_for_value) 1881 { 1882 return '<td>' 1883 . Util::showHint( 1884 __( 1885 'Use TAB key to move from value to value,' 1886 . ' or CTRL+arrows to move anywhere.' 1887 ) 1888 ) 1889 . '</td>' 1890 . '<td colspan="3" class="right vmiddle">' 1891 . '<input type="submit" class="control_at_footer" value="' . __('Go') . '"' 1892 . ' tabindex="' . ($tabindex + $tabindex_for_value + 6) . '" id="buttonYes" />' 1893 . '<input type="button" class="preview_sql" value="' . __('Preview SQL') . '"' 1894 . ' tabindex="' . ($tabindex + $tabindex_for_value + 7) . '" />' 1895 . '<input type="reset" class="control_at_footer" value="' . __('Reset') . '"' 1896 . ' tabindex="' . ($tabindex + $tabindex_for_value + 8) . '" />' 1897 . '</td>'; 1898 } 1899 1900 /** 1901 * Get table head and table foot for insert row table 1902 * 1903 * @param array $url_params url parameters 1904 * 1905 * @return string an html snippet 1906 */ 1907 private function getHeadAndFootOfInsertRowTable(array $url_params) 1908 { 1909 $html_output = '<div class="responsivetable">' 1910 . '<table class="insertRowTable topmargin">' 1911 . '<thead>' 1912 . '<tr>' 1913 . '<th>' . __('Column') . '</th>'; 1914 1915 if ($GLOBALS['cfg']['ShowFieldTypesInDataEditView']) { 1916 $html_output .= $this->showTypeOrFunction('type', $url_params, true); 1917 } 1918 if ($GLOBALS['cfg']['ShowFunctionFields']) { 1919 $html_output .= $this->showTypeOrFunction('function', $url_params, true); 1920 } 1921 1922 $html_output .= '<th>' . __('Null') . '</th>' 1923 . '<th>' . __('Value') . '</th>' 1924 . '</tr>' 1925 . '</thead>' 1926 . ' <tfoot>' 1927 . '<tr>' 1928 . '<th colspan="5" class="tblFooters right">' 1929 . '<input type="submit" value="' . __('Go') . '" />' 1930 . '</th>' 1931 . '</tr>' 1932 . '</tfoot>'; 1933 return $html_output; 1934 } 1935 1936 /** 1937 * Prepares the field value and retrieve special chars, backup field and data array 1938 * 1939 * @param array $current_row a row of the table 1940 * @param array $column description of column in given table 1941 * @param array $extracted_columnspec associative array containing type, 1942 * spec_in_brackets and possibly 1943 * enum_set_values (another array) 1944 * @param boolean $real_null_value whether column value null or not null 1945 * @param array $gis_data_types list of GIS data types 1946 * @param string $column_name_appendix string to append to column name in input 1947 * @param bool $as_is use the data as is, used in repopulating 1948 * 1949 * @return array $real_null_value, $data, $special_chars, $backup_field, 1950 * $special_chars_encoded 1951 */ 1952 private function getSpecialCharsAndBackupFieldForExistingRow( 1953 array $current_row, 1954 array $column, 1955 array $extracted_columnspec, 1956 $real_null_value, 1957 array $gis_data_types, 1958 $column_name_appendix, 1959 $as_is 1960 ) { 1961 $special_chars_encoded = ''; 1962 $data = null; 1963 // (we are editing) 1964 if (!isset($current_row[$column['Field']])) { 1965 $real_null_value = true; 1966 $current_row[$column['Field']] = ''; 1967 $special_chars = ''; 1968 $data = $current_row[$column['Field']]; 1969 } elseif ($column['True_Type'] == 'bit') { 1970 $special_chars = $as_is 1971 ? $current_row[$column['Field']] 1972 : Util::printableBitValue( 1973 $current_row[$column['Field']], 1974 $extracted_columnspec['spec_in_brackets'] 1975 ); 1976 } elseif ((substr($column['True_Type'], 0, 9) == 'timestamp' 1977 || $column['True_Type'] == 'datetime' 1978 || $column['True_Type'] == 'time') 1979 && (mb_strpos($current_row[$column['Field']], ".") !== false) 1980 ) { 1981 $current_row[$column['Field']] = $as_is 1982 ? $current_row[$column['Field']] 1983 : Util::addMicroseconds( 1984 $current_row[$column['Field']] 1985 ); 1986 $special_chars = htmlspecialchars($current_row[$column['Field']]); 1987 } elseif (in_array($column['True_Type'], $gis_data_types)) { 1988 // Convert gis data to Well Know Text format 1989 $current_row[$column['Field']] = $as_is 1990 ? $current_row[$column['Field']] 1991 : Util::asWKT( 1992 $current_row[$column['Field']], 1993 true 1994 ); 1995 $special_chars = htmlspecialchars($current_row[$column['Field']]); 1996 } else { 1997 // special binary "characters" 1998 if ($column['is_binary'] 1999 || ($column['is_blob'] && $GLOBALS['cfg']['ProtectBinary'] !== 'all') 2000 ) { 2001 $current_row[$column['Field']] = $as_is 2002 ? $current_row[$column['Field']] 2003 : bin2hex( 2004 $current_row[$column['Field']] 2005 ); 2006 } // end if 2007 $special_chars = htmlspecialchars($current_row[$column['Field']]); 2008 2009 //We need to duplicate the first \n or otherwise we will lose 2010 //the first newline entered in a VARCHAR or TEXT column 2011 $special_chars_encoded 2012 = Util::duplicateFirstNewline($special_chars); 2013 2014 $data = $current_row[$column['Field']]; 2015 } // end if... else... 2016 2017 //when copying row, it is useful to empty auto-increment column 2018 // to prevent duplicate key error 2019 if (isset($_POST['default_action']) 2020 && $_POST['default_action'] === 'insert' 2021 ) { 2022 if ($column['Key'] === 'PRI' 2023 && mb_strpos($column['Extra'], 'auto_increment') !== false 2024 ) { 2025 $data = $special_chars_encoded = $special_chars = null; 2026 } 2027 } 2028 // If a timestamp field value is not included in an update 2029 // statement MySQL auto-update it to the current timestamp; 2030 // however, things have changed since MySQL 4.1, so 2031 // it's better to set a fields_prev in this situation 2032 $backup_field = '<input type="hidden" name="fields_prev' 2033 . $column_name_appendix . '" value="' 2034 . htmlspecialchars($current_row[$column['Field']]) . '" />'; 2035 2036 return array( 2037 $real_null_value, 2038 $special_chars_encoded, 2039 $special_chars, 2040 $data, 2041 $backup_field 2042 ); 2043 } 2044 2045 /** 2046 * display default values 2047 * 2048 * @param array $column description of column in given table 2049 * @param boolean $real_null_value whether column value null or not null 2050 * 2051 * @return array $real_null_value, $data, $special_chars, 2052 * $backup_field, $special_chars_encoded 2053 */ 2054 private function getSpecialCharsAndBackupFieldForInsertingMode( 2055 array $column, 2056 $real_null_value 2057 ) { 2058 if (! isset($column['Default'])) { 2059 $column['Default'] = ''; 2060 $real_null_value = true; 2061 $data = ''; 2062 } else { 2063 $data = $column['Default']; 2064 } 2065 2066 $trueType = $column['True_Type']; 2067 2068 if ($trueType == 'bit') { 2069 $special_chars = Util::convertBitDefaultValue( 2070 $column['Default'] 2071 ); 2072 } elseif (substr($trueType, 0, 9) == 'timestamp' 2073 || $trueType == 'datetime' 2074 || $trueType == 'time' 2075 ) { 2076 $special_chars = Util::addMicroseconds($column['Default']); 2077 } elseif ($trueType == 'binary' || $trueType == 'varbinary') { 2078 $special_chars = bin2hex($column['Default']); 2079 } elseif ('text' === substr($trueType, -4)) { 2080 $textDefault = substr($column['Default'], 1, -1); 2081 $special_chars = stripcslashes($textDefault !== false ? $textDefault : $column['Default']); 2082 } else { 2083 $special_chars = htmlspecialchars($column['Default']); 2084 } 2085 $backup_field = ''; 2086 $special_chars_encoded = Util::duplicateFirstNewline( 2087 $special_chars 2088 ); 2089 return array( 2090 $real_null_value, $data, $special_chars, 2091 $backup_field, $special_chars_encoded 2092 ); 2093 } 2094 2095 /** 2096 * Prepares the update/insert of a row 2097 * 2098 * @return array $loop_array, $using_key, $is_insert, $is_insertignore 2099 */ 2100 public function getParamsForUpdateOrInsert() 2101 { 2102 if (isset($_POST['where_clause'])) { 2103 // we were editing something => use the WHERE clause 2104 $loop_array = is_array($_POST['where_clause']) 2105 ? $_POST['where_clause'] 2106 : array($_POST['where_clause']); 2107 $using_key = true; 2108 $is_insert = isset($_POST['submit_type']) 2109 && ($_POST['submit_type'] == 'insert' 2110 || $_POST['submit_type'] == 'showinsert' 2111 || $_POST['submit_type'] == 'insertignore'); 2112 } else { 2113 // new row => use indexes 2114 $loop_array = array(); 2115 if (! empty($_POST['fields'])) { 2116 foreach ($_POST['fields']['multi_edit'] as $key => $dummy) { 2117 $loop_array[] = $key; 2118 } 2119 } 2120 $using_key = false; 2121 $is_insert = true; 2122 } 2123 $is_insertignore = isset($_POST['submit_type']) 2124 && $_POST['submit_type'] == 'insertignore'; 2125 return array($loop_array, $using_key, $is_insert, $is_insertignore); 2126 } 2127 2128 /** 2129 * Check wether insert row mode and if so include tbl_changen script and set 2130 * global variables. 2131 * 2132 * @return void 2133 */ 2134 public function isInsertRow() 2135 { 2136 if (isset($_POST['insert_rows']) 2137 && is_numeric($_POST['insert_rows']) 2138 && $_POST['insert_rows'] != $GLOBALS['cfg']['InsertRows'] 2139 ) { 2140 $GLOBALS['cfg']['InsertRows'] = $_POST['insert_rows']; 2141 $response = Response::getInstance(); 2142 $header = $response->getHeader(); 2143 $scripts = $header->getScripts(); 2144 $scripts->addFile('vendor/jquery/additional-methods.js'); 2145 $scripts->addFile('tbl_change.js'); 2146 if (!defined('TESTSUITE')) { 2147 include 'tbl_change.php'; 2148 exit; 2149 } 2150 } 2151 } 2152 2153 /** 2154 * set $_SESSION for edit_next 2155 * 2156 * @param string $one_where_clause one where clause from where clauses array 2157 * 2158 * @return void 2159 */ 2160 public function setSessionForEditNext($one_where_clause) 2161 { 2162 $local_query = 'SELECT * FROM ' . Util::backquote($GLOBALS['db']) 2163 . '.' . Util::backquote($GLOBALS['table']) . ' WHERE ' 2164 . str_replace('` =', '` >', $one_where_clause) . ' LIMIT 1;'; 2165 2166 $res = $this->dbi->query($local_query); 2167 $row = $this->dbi->fetchRow($res); 2168 $meta = $this->dbi->getFieldsMeta($res); 2169 // must find a unique condition based on unique key, 2170 // not a combination of all fields 2171 list($unique_condition, $clause_is_unique) 2172 = Util::getUniqueCondition( 2173 $res, // handle 2174 count($meta), // fields_cnt 2175 $meta, // fields_meta 2176 $row, // row 2177 true, // force_unique 2178 false, // restrict_to_table 2179 null // analyzed_sql_results 2180 ); 2181 if (! empty($unique_condition)) { 2182 $_SESSION['edit_next'] = $unique_condition; 2183 } 2184 unset($unique_condition, $clause_is_unique); 2185 } 2186 2187 /** 2188 * set $goto_include variable for different cases and retrieve like, 2189 * if $GLOBALS['goto'] empty, if $goto_include previously not defined 2190 * and new_insert, same_insert, edit_next 2191 * 2192 * @param string $goto_include store some script for include, otherwise it is 2193 * boolean false 2194 * 2195 * @return string $goto_include 2196 */ 2197 public function getGotoInclude($goto_include) 2198 { 2199 $valid_options = array('new_insert', 'same_insert', 'edit_next'); 2200 if (isset($_POST['after_insert']) 2201 && in_array($_POST['after_insert'], $valid_options) 2202 ) { 2203 $goto_include = 'tbl_change.php'; 2204 } elseif (! empty($GLOBALS['goto'])) { 2205 if (! preg_match('@^[a-z_]+\.php$@', $GLOBALS['goto'])) { 2206 // this should NOT happen 2207 //$GLOBALS['goto'] = false; 2208 $goto_include = false; 2209 } else { 2210 $goto_include = $GLOBALS['goto']; 2211 } 2212 if ($GLOBALS['goto'] == 'db_sql.php' && strlen($GLOBALS['table']) > 0) { 2213 $GLOBALS['table'] = ''; 2214 } 2215 } 2216 if (! $goto_include) { 2217 if (strlen($GLOBALS['table']) === 0) { 2218 $goto_include = 'db_sql.php'; 2219 } else { 2220 $goto_include = 'tbl_sql.php'; 2221 } 2222 } 2223 return $goto_include; 2224 } 2225 2226 /** 2227 * Defines the url to return in case of failure of the query 2228 * 2229 * @param array $url_params url parameters 2230 * 2231 * @return string error url for query failure 2232 */ 2233 public function getErrorUrl(array $url_params) 2234 { 2235 if (isset($_POST['err_url'])) { 2236 return $_POST['err_url']; 2237 } 2238 2239 return 'tbl_change.php' . Url::getCommon($url_params); 2240 } 2241 2242 /** 2243 * Builds the sql query 2244 * 2245 * @param boolean $is_insertignore $_POST['submit_type'] == 'insertignore' 2246 * @param array $query_fields column names array 2247 * @param array $value_sets array of query values 2248 * 2249 * @return array of query 2250 */ 2251 public function buildSqlQuery($is_insertignore, array $query_fields, array $value_sets) 2252 { 2253 if ($is_insertignore) { 2254 $insert_command = 'INSERT IGNORE '; 2255 } else { 2256 $insert_command = 'INSERT '; 2257 } 2258 $query = array( 2259 $insert_command . 'INTO ' 2260 . Util::backquote($GLOBALS['table']) 2261 . ' (' . implode(', ', $query_fields) . ') VALUES (' 2262 . implode('), (', $value_sets) . ')' 2263 ); 2264 unset($insert_command, $query_fields); 2265 return $query; 2266 } 2267 2268 /** 2269 * Executes the sql query and get the result, then move back to the calling page 2270 * 2271 * @param array $url_params url parameters array 2272 * @param array $query built query from buildSqlQuery() 2273 * 2274 * @return array $url_params, $total_affected_rows, $last_messages 2275 * $warning_messages, $error_messages, $return_to_sql_query 2276 */ 2277 public function executeSqlQuery(array $url_params, array $query) 2278 { 2279 $return_to_sql_query = ''; 2280 if (! empty($GLOBALS['sql_query'])) { 2281 $url_params['sql_query'] = $GLOBALS['sql_query']; 2282 $return_to_sql_query = $GLOBALS['sql_query']; 2283 } 2284 $GLOBALS['sql_query'] = implode('; ', $query) . ';'; 2285 // to ensure that the query is displayed in case of 2286 // "insert as new row" and then "insert another new row" 2287 $GLOBALS['display_query'] = $GLOBALS['sql_query']; 2288 2289 $total_affected_rows = 0; 2290 $last_messages = array(); 2291 $warning_messages = array(); 2292 $error_messages = array(); 2293 2294 foreach ($query as $single_query) { 2295 if ($_POST['submit_type'] == 'showinsert') { 2296 $last_messages[] = Message::notice(__('Showing SQL query')); 2297 continue; 2298 } 2299 if ($GLOBALS['cfg']['IgnoreMultiSubmitErrors']) { 2300 $result = $this->dbi->tryQuery($single_query); 2301 } else { 2302 $result = $this->dbi->query($single_query); 2303 } 2304 if (! $result) { 2305 $error_messages[] = $this->dbi->getError(); 2306 } else { 2307 // The next line contains a real assignment, it's not a typo 2308 if ($tmp = @$this->dbi->affectedRows()) { 2309 $total_affected_rows += $tmp; 2310 } 2311 unset($tmp); 2312 2313 $insert_id = $this->dbi->insertId(); 2314 if ($insert_id != 0) { 2315 // insert_id is id of FIRST record inserted in one insert, so if we 2316 // inserted multiple rows, we had to increment this 2317 2318 if ($total_affected_rows > 0) { 2319 $insert_id = $insert_id + $total_affected_rows - 1; 2320 } 2321 $last_message = Message::notice(__('Inserted row id: %1$d')); 2322 $last_message->addParam($insert_id); 2323 $last_messages[] = $last_message; 2324 } 2325 $this->dbi->freeResult($result); 2326 } 2327 $warning_messages = $this->getWarningMessages(); 2328 } 2329 return array( 2330 $url_params, 2331 $total_affected_rows, 2332 $last_messages, 2333 $warning_messages, 2334 $error_messages, 2335 $return_to_sql_query 2336 ); 2337 } 2338 2339 /** 2340 * get the warning messages array 2341 * 2342 * @return array $warning_essages 2343 */ 2344 private function getWarningMessages() 2345 { 2346 $warning_essages = array(); 2347 foreach ($this->dbi->getWarnings() as $warning) { 2348 $warning_essages[] = Message::sanitize( 2349 $warning['Level'] . ': #' . $warning['Code'] . ' ' . $warning['Message'] 2350 ); 2351 } 2352 return $warning_essages; 2353 } 2354 2355 /** 2356 * Column to display from the foreign table? 2357 * 2358 * @param string $where_comparison string that contain relation field value 2359 * @param array $map all Relations to foreign tables for a given 2360 * table or optionally a given column in a table 2361 * @param string $relation_field relation field 2362 * 2363 * @return string $dispval display value from the foreign table 2364 */ 2365 public function getDisplayValueForForeignTableColumn( 2366 $where_comparison, 2367 array $map, 2368 $relation_field 2369 ) { 2370 $foreigner = $this->relation->searchColumnInForeigners($map, $relation_field); 2371 $display_field = $this->relation->getDisplayField( 2372 $foreigner['foreign_db'], 2373 $foreigner['foreign_table'] 2374 ); 2375 // Field to display from the foreign table? 2376 if (isset($display_field) && strlen($display_field) > 0) { 2377 $dispsql = 'SELECT ' . Util::backquote($display_field) 2378 . ' FROM ' . Util::backquote($foreigner['foreign_db']) 2379 . '.' . Util::backquote($foreigner['foreign_table']) 2380 . ' WHERE ' . Util::backquote($foreigner['foreign_field']) 2381 . $where_comparison; 2382 $dispresult = $this->dbi->tryQuery( 2383 $dispsql, 2384 DatabaseInterface::CONNECT_USER, 2385 DatabaseInterface::QUERY_STORE 2386 ); 2387 if ($dispresult && $this->dbi->numRows($dispresult) > 0) { 2388 list($dispval) = $this->dbi->fetchRow($dispresult, 0); 2389 } else { 2390 $dispval = ''; 2391 } 2392 if ($dispresult) { 2393 $this->dbi->freeResult($dispresult); 2394 } 2395 return $dispval; 2396 } 2397 return ''; 2398 } 2399 2400 /** 2401 * Display option in the cell according to user choices 2402 * 2403 * @param array $map all Relations to foreign tables for a given 2404 * table or optionally a given column in a table 2405 * @param string $relation_field relation field 2406 * @param string $where_comparison string that contain relation field value 2407 * @param string $dispval display value from the foreign table 2408 * @param string $relation_field_value relation field value 2409 * 2410 * @return string $output HTML <a> tag 2411 */ 2412 public function getLinkForRelationalDisplayField( 2413 array $map, 2414 $relation_field, 2415 $where_comparison, 2416 $dispval, 2417 $relation_field_value 2418 ) { 2419 $foreigner = $this->relation->searchColumnInForeigners($map, $relation_field); 2420 if ('K' == $_SESSION['tmpval']['relational_display']) { 2421 // user chose "relational key" in the display options, so 2422 // the title contains the display field 2423 $title = (! empty($dispval)) 2424 ? ' title="' . htmlspecialchars($dispval) . '"' 2425 : ''; 2426 } else { 2427 $title = ' title="' . htmlspecialchars($relation_field_value) . '"'; 2428 } 2429 $_url_params = array( 2430 'db' => $foreigner['foreign_db'], 2431 'table' => $foreigner['foreign_table'], 2432 'pos' => '0', 2433 'sql_query' => 'SELECT * FROM ' 2434 . Util::backquote($foreigner['foreign_db']) 2435 . '.' . Util::backquote($foreigner['foreign_table']) 2436 . ' WHERE ' . Util::backquote($foreigner['foreign_field']) 2437 . $where_comparison 2438 ); 2439 $output = '<a href="sql.php' 2440 . Url::getCommon($_url_params) . '"' . $title . '>'; 2441 2442 if ('D' == $_SESSION['tmpval']['relational_display']) { 2443 // user chose "relational display field" in the 2444 // display options, so show display field in the cell 2445 $output .= (!empty($dispval)) ? htmlspecialchars($dispval) : ''; 2446 } else { 2447 // otherwise display data in the cell 2448 $output .= htmlspecialchars($relation_field_value); 2449 } 2450 $output .= '</a>'; 2451 return $output; 2452 } 2453 2454 /** 2455 * Transform edited values 2456 * 2457 * @param string $db db name 2458 * @param string $table table name 2459 * @param array $transformation mimetypes for all columns of a table 2460 * [field_name][field_key] 2461 * @param array &$edited_values transform columns list and new values 2462 * @param string $file file containing the transformation plugin 2463 * @param string $column_name column name 2464 * @param array $extra_data extra data array 2465 * @param string $type the type of transformation 2466 * 2467 * @return array $extra_data 2468 */ 2469 public function transformEditedValues( 2470 $db, 2471 $table, 2472 array $transformation, 2473 array &$edited_values, 2474 $file, 2475 $column_name, 2476 array $extra_data, 2477 $type 2478 ) { 2479 $include_file = 'libraries/classes/Plugins/Transformations/' . $file; 2480 if (is_file($include_file)) { 2481 $_url_params = array( 2482 'db' => $db, 2483 'table' => $table, 2484 'where_clause_sign' => Core::signSqlQuery($_POST['where_clause']), 2485 'where_clause' => $_POST['where_clause'], 2486 'transform_key' => $column_name 2487 ); 2488 $transform_options = Transformations::getOptions( 2489 isset($transformation[$type . '_options']) 2490 ? $transformation[$type . '_options'] 2491 : '' 2492 ); 2493 $transform_options['wrapper_link'] = Url::getCommon($_url_params); 2494 $class_name = Transformations::getClassName($include_file); 2495 if (class_exists($class_name)) { 2496 /** @var TransformationsPlugin $transformation_plugin */ 2497 $transformation_plugin = new $class_name(); 2498 2499 foreach ($edited_values as $cell_index => $curr_cell_edited_values) { 2500 if (isset($curr_cell_edited_values[$column_name])) { 2501 $edited_values[$cell_index][$column_name] 2502 = $extra_data['transformations'][$cell_index] 2503 = $transformation_plugin->applyTransformation( 2504 $curr_cell_edited_values[$column_name], 2505 $transform_options, 2506 '' 2507 ); 2508 } 2509 } // end of loop for each transformation cell 2510 } 2511 } 2512 return $extra_data; 2513 } 2514 2515 /** 2516 * Get current value in multi edit mode 2517 * 2518 * @param array $multi_edit_funcs multiple edit functions array 2519 * @param array $multi_edit_salt multiple edit array with encryption salt 2520 * @param array $gis_from_text_functions array that contains gis from text functions 2521 * @param string $current_value current value in the column 2522 * @param array $gis_from_wkb_functions initially $val is $multi_edit_columns[$key] 2523 * @param array $func_optional_param array('RAND','UNIX_TIMESTAMP') 2524 * @param array $func_no_param array of set of string 2525 * @param string $key an md5 of the column name 2526 * 2527 * @return array $cur_value 2528 */ 2529 public function getCurrentValueAsAnArrayForMultipleEdit( 2530 $multi_edit_funcs, 2531 $multi_edit_salt, 2532 $gis_from_text_functions, 2533 $current_value, 2534 $gis_from_wkb_functions, 2535 $func_optional_param, 2536 $func_no_param, 2537 $key 2538 ) { 2539 if (empty($multi_edit_funcs[$key])) { 2540 return $current_value; 2541 } elseif ('UUID' === $multi_edit_funcs[$key]) { 2542 /* This way user will know what UUID new row has */ 2543 $uuid = $this->dbi->fetchValue('SELECT UUID()'); 2544 return "'" . $uuid . "'"; 2545 } elseif ((in_array($multi_edit_funcs[$key], $gis_from_text_functions) 2546 && substr($current_value, 0, 3) == "'''") 2547 || in_array($multi_edit_funcs[$key], $gis_from_wkb_functions) 2548 ) { 2549 // Remove enclosing apostrophes 2550 $current_value = mb_substr($current_value, 1, -1); 2551 // Remove escaping apostrophes 2552 $current_value = str_replace("''", "'", $current_value); 2553 return $multi_edit_funcs[$key] . '(' . $current_value . ')'; 2554 } elseif (! in_array($multi_edit_funcs[$key], $func_no_param) 2555 || ($current_value != "''" 2556 && in_array($multi_edit_funcs[$key], $func_optional_param)) 2557 ) { 2558 if ((isset($multi_edit_salt[$key]) 2559 && ($multi_edit_funcs[$key] == "AES_ENCRYPT" 2560 || $multi_edit_funcs[$key] == "AES_DECRYPT")) 2561 || (! empty($multi_edit_salt[$key]) 2562 && ($multi_edit_funcs[$key] == "DES_ENCRYPT" 2563 || $multi_edit_funcs[$key] == "DES_DECRYPT" 2564 || $multi_edit_funcs[$key] == "ENCRYPT")) 2565 ) { 2566 return $multi_edit_funcs[$key] . '(' . $current_value . ",'" 2567 . $this->dbi->escapeString($multi_edit_salt[$key]) . "')"; 2568 } 2569 2570 return $multi_edit_funcs[$key] . '(' . $current_value . ')'; 2571 } 2572 2573 return $multi_edit_funcs[$key] . '()'; 2574 } 2575 2576 /** 2577 * Get query values array and query fields array for insert and update in multi edit 2578 * 2579 * @param array $multi_edit_columns_name multiple edit columns name array 2580 * @param array $multi_edit_columns_null multiple edit columns null array 2581 * @param string $current_value current value in the column in loop 2582 * @param array $multi_edit_columns_prev multiple edit previous columns array 2583 * @param array $multi_edit_funcs multiple edit functions array 2584 * @param boolean $is_insert boolean value whether insert or not 2585 * @param array $query_values SET part of the sql query 2586 * @param array $query_fields array of query fields 2587 * @param string $current_value_as_an_array current value in the column 2588 * as an array 2589 * @param array $value_sets array of valu sets 2590 * @param string $key an md5 of the column name 2591 * @param array $multi_edit_columns_null_prev array of multiple edit columns 2592 * null previous 2593 * 2594 * @return array ($query_values, $query_fields) 2595 */ 2596 public function getQueryValuesForInsertAndUpdateInMultipleEdit( 2597 $multi_edit_columns_name, 2598 $multi_edit_columns_null, 2599 $current_value, 2600 $multi_edit_columns_prev, 2601 $multi_edit_funcs, 2602 $is_insert, 2603 $query_values, 2604 $query_fields, 2605 $current_value_as_an_array, 2606 $value_sets, 2607 $key, 2608 $multi_edit_columns_null_prev 2609 ) { 2610 // i n s e r t 2611 if ($is_insert) { 2612 // no need to add column into the valuelist 2613 if (strlen($current_value_as_an_array) > 0) { 2614 $query_values[] = $current_value_as_an_array; 2615 // first inserted row so prepare the list of fields 2616 if (empty($value_sets)) { 2617 $query_fields[] = Util::backquote( 2618 $multi_edit_columns_name[$key] 2619 ); 2620 } 2621 } 2622 } elseif (! empty($multi_edit_columns_null_prev[$key]) 2623 && ! isset($multi_edit_columns_null[$key]) 2624 ) { 2625 // u p d a t e 2626 2627 // field had the null checkbox before the update 2628 // field no longer has the null checkbox 2629 $query_values[] 2630 = Util::backquote($multi_edit_columns_name[$key]) 2631 . ' = ' . $current_value_as_an_array; 2632 } elseif (empty($multi_edit_funcs[$key]) 2633 && isset($multi_edit_columns_prev[$key]) 2634 && (("'" . $this->dbi->escapeString($multi_edit_columns_prev[$key]) . "'" === $current_value) 2635 || ('0x' . $multi_edit_columns_prev[$key] === $current_value)) 2636 ) { 2637 // No change for this column and no MySQL function is used -> next column 2638 } elseif (! empty($current_value)) { 2639 // avoid setting a field to NULL when it's already NULL 2640 // (field had the null checkbox before the update 2641 // field still has the null checkbox) 2642 if (empty($multi_edit_columns_null_prev[$key]) 2643 || empty($multi_edit_columns_null[$key]) 2644 ) { 2645 $query_values[] 2646 = Util::backquote($multi_edit_columns_name[$key]) 2647 . ' = ' . $current_value_as_an_array; 2648 } 2649 } 2650 return array($query_values, $query_fields); 2651 } 2652 2653 /** 2654 * Get the current column value in the form for different data types 2655 * 2656 * @param string|false $possibly_uploaded_val uploaded file content 2657 * @param string $key an md5 of the column name 2658 * @param array $multi_edit_columns_type array of multi edit column types 2659 * @param string $current_value current column value in the form 2660 * @param array $multi_edit_auto_increment multi edit auto increment 2661 * @param integer $rownumber index of where clause array 2662 * @param array $multi_edit_columns_name multi edit column names array 2663 * @param array $multi_edit_columns_null multi edit columns null array 2664 * @param array $multi_edit_columns_null_prev multi edit columns previous null 2665 * @param boolean $is_insert whether insert or not 2666 * @param boolean $using_key whether editing or new row 2667 * @param string $where_clause where clause 2668 * @param string $table table name 2669 * @param array $multi_edit_funcs multiple edit functions array 2670 * 2671 * @return string $current_value current column value in the form 2672 */ 2673 public function getCurrentValueForDifferentTypes( 2674 $possibly_uploaded_val, 2675 $key, 2676 $multi_edit_columns_type, 2677 $current_value, 2678 $multi_edit_auto_increment, 2679 $rownumber, 2680 $multi_edit_columns_name, 2681 $multi_edit_columns_null, 2682 $multi_edit_columns_null_prev, 2683 $is_insert, 2684 $using_key, 2685 $where_clause, 2686 $table, 2687 $multi_edit_funcs 2688 ) { 2689 // Fetch the current values of a row to use in case we have a protected field 2690 if ($is_insert 2691 && $using_key && isset($multi_edit_columns_type) 2692 && is_array($multi_edit_columns_type) && !empty($where_clause) 2693 ) { 2694 $protected_row = $this->dbi->fetchSingleRow( 2695 'SELECT * FROM ' . Util::backquote($table) 2696 . ' WHERE ' . $where_clause . ';' 2697 ); 2698 } 2699 2700 if (false !== $possibly_uploaded_val) { 2701 $current_value = $possibly_uploaded_val; 2702 } elseif (! empty($multi_edit_funcs[$key])) { 2703 $current_value = "'" . $this->dbi->escapeString($current_value) 2704 . "'"; 2705 } else { 2706 // c o l u m n v a l u e i n t h e f o r m 2707 if (isset($multi_edit_columns_type[$key])) { 2708 $type = $multi_edit_columns_type[$key]; 2709 } else { 2710 $type = ''; 2711 } 2712 2713 if ($type != 'protected' && $type != 'set' && strlen($current_value) === 0) { 2714 // best way to avoid problems in strict mode 2715 // (works also in non-strict mode) 2716 if (isset($multi_edit_auto_increment) 2717 && isset($multi_edit_auto_increment[$key]) 2718 ) { 2719 $current_value = 'NULL'; 2720 } else { 2721 $current_value = "''"; 2722 } 2723 } elseif ($type == 'set') { 2724 if (! empty($_POST['fields']['multi_edit'][$rownumber][$key])) { 2725 $current_value = implode( 2726 ',', 2727 $_POST['fields']['multi_edit'][$rownumber][$key] 2728 ); 2729 $current_value = "'" 2730 . $this->dbi->escapeString($current_value) . "'"; 2731 } else { 2732 $current_value = "''"; 2733 } 2734 } elseif ($type == 'protected') { 2735 // here we are in protected mode (asked in the config) 2736 // so tbl_change has put this special value in the 2737 // columns array, so we do not change the column value 2738 // but we can still handle column upload 2739 2740 // when in UPDATE mode, do not alter field's contents. When in INSERT 2741 // mode, insert empty field because no values were submitted. 2742 // If protected blobs where set, insert original fields content. 2743 if (! empty($protected_row[$multi_edit_columns_name[$key]])) { 2744 $current_value = '0x' 2745 . bin2hex($protected_row[$multi_edit_columns_name[$key]]); 2746 } else { 2747 $current_value = ''; 2748 } 2749 } elseif ($type === 'hex') { 2750 if (substr($current_value, 0, 2) != '0x') { 2751 $current_value = '0x' . $current_value; 2752 } 2753 } elseif ($type == 'bit') { 2754 $current_value = preg_replace('/[^01]/', '0', $current_value); 2755 $current_value = "b'" . $this->dbi->escapeString($current_value) 2756 . "'"; 2757 } elseif (! ($type == 'datetime' || $type == 'timestamp') 2758 || ($current_value != 'CURRENT_TIMESTAMP' 2759 && $current_value != 'current_timestamp()') 2760 ) { 2761 $current_value = "'" . $this->dbi->escapeString($current_value) 2762 . "'"; 2763 } 2764 2765 // Was the Null checkbox checked for this field? 2766 // (if there is a value, we ignore the Null checkbox: this could 2767 // be possible if Javascript is disabled in the browser) 2768 if (! empty($multi_edit_columns_null[$key]) 2769 && ($current_value == "''" || $current_value == '') 2770 ) { 2771 $current_value = 'NULL'; 2772 } 2773 2774 // The Null checkbox was unchecked for this field 2775 if (empty($current_value) 2776 && ! empty($multi_edit_columns_null_prev[$key]) 2777 && ! isset($multi_edit_columns_null[$key]) 2778 ) { 2779 $current_value = "''"; 2780 } 2781 } // end else (column value in the form) 2782 return $current_value; 2783 } 2784 2785 /** 2786 * Check whether inline edited value can be truncated or not, 2787 * and add additional parameters for extra_data array if needed 2788 * 2789 * @param string $db Database name 2790 * @param string $table Table name 2791 * @param string $column_name Column name 2792 * @param array &$extra_data Extra data for ajax response 2793 * 2794 * @return void 2795 */ 2796 public function verifyWhetherValueCanBeTruncatedAndAppendExtraData( 2797 $db, 2798 $table, 2799 $column_name, 2800 array &$extra_data 2801 ) { 2802 $extra_data['isNeedToRecheck'] = false; 2803 2804 $sql_for_real_value = 'SELECT ' . Util::backquote($table) . '.' 2805 . Util::backquote($column_name) 2806 . ' FROM ' . Util::backquote($db) . '.' 2807 . Util::backquote($table) 2808 . ' WHERE ' . $_POST['where_clause'][0]; 2809 2810 $result = $this->dbi->tryQuery($sql_for_real_value); 2811 $fields_meta = $this->dbi->getFieldsMeta($result); 2812 $meta = $fields_meta[0]; 2813 if ($row = $this->dbi->fetchRow($result)) { 2814 $new_value = $row[0]; 2815 if ((substr($meta->type, 0, 9) == 'timestamp') 2816 || ($meta->type == 'datetime') 2817 || ($meta->type == 'time') 2818 ) { 2819 $new_value = Util::addMicroseconds($new_value); 2820 } elseif (mb_strpos($meta->flags, 'binary') !== false) { 2821 $new_value = '0x' . bin2hex($new_value); 2822 } 2823 $extra_data['isNeedToRecheck'] = true; 2824 $extra_data['truncatableFieldValue'] = $new_value; 2825 } 2826 $this->dbi->freeResult($result); 2827 } 2828 2829 /** 2830 * Function to get the columns of a table 2831 * 2832 * @param string $db current db 2833 * @param string $table current table 2834 * 2835 * @return array 2836 */ 2837 public function getTableColumns($db, $table) 2838 { 2839 $this->dbi->selectDb($db); 2840 return array_values($this->dbi->getColumns($db, $table, null, true)); 2841 } 2842 2843 /** 2844 * Function to determine Insert/Edit rows 2845 * 2846 * @param string $where_clause where clause 2847 * @param string $db current database 2848 * @param string $table current table 2849 * 2850 * @return mixed 2851 */ 2852 public function determineInsertOrEdit($where_clause, $db, $table) 2853 { 2854 if (isset($_POST['where_clause'])) { 2855 $where_clause = $_POST['where_clause']; 2856 } 2857 if (isset($_SESSION['edit_next'])) { 2858 $where_clause = $_SESSION['edit_next']; 2859 unset($_SESSION['edit_next']); 2860 $after_insert = 'edit_next'; 2861 } 2862 if (isset($_POST['ShowFunctionFields'])) { 2863 $GLOBALS['cfg']['ShowFunctionFields'] = $_POST['ShowFunctionFields']; 2864 } 2865 if (isset($_POST['ShowFieldTypesInDataEditView'])) { 2866 $GLOBALS['cfg']['ShowFieldTypesInDataEditView'] 2867 = $_POST['ShowFieldTypesInDataEditView']; 2868 } 2869 if (isset($_POST['after_insert'])) { 2870 $after_insert = $_POST['after_insert']; 2871 } 2872 2873 if (isset($where_clause)) { 2874 // we are editing 2875 $insert_mode = false; 2876 $where_clause_array = $this->getWhereClauseArray($where_clause); 2877 list($where_clauses, $result, $rows, $found_unique_key) 2878 = $this->analyzeWhereClauses( 2879 $where_clause_array, 2880 $table, 2881 $db 2882 ); 2883 } else { 2884 // we are inserting 2885 $insert_mode = true; 2886 $where_clause = null; 2887 list($result, $rows) = $this->loadFirstRow($table, $db); 2888 $where_clauses = null; 2889 $where_clause_array = array(); 2890 $found_unique_key = false; 2891 } 2892 2893 // Copying a row - fetched data will be inserted as a new row, 2894 // therefore the where clause is needless. 2895 if (isset($_POST['default_action']) 2896 && $_POST['default_action'] === 'insert' 2897 ) { 2898 $where_clause = $where_clauses = null; 2899 } 2900 2901 return array( 2902 $insert_mode, $where_clause, $where_clause_array, $where_clauses, 2903 $result, $rows, $found_unique_key, 2904 isset($after_insert) ? $after_insert : null 2905 ); 2906 } 2907 2908 /** 2909 * Function to get comments for the table columns 2910 * 2911 * @param string $db current database 2912 * @param string $table current table 2913 * 2914 * @return array $comments_map comments for columns 2915 */ 2916 public function getCommentsMap($db, $table) 2917 { 2918 $comments_map = array(); 2919 2920 if ($GLOBALS['cfg']['ShowPropertyComments']) { 2921 $comments_map = $this->relation->getComments($db, $table); 2922 } 2923 2924 return $comments_map; 2925 } 2926 2927 /** 2928 * Function to get URL parameters 2929 * 2930 * @param string $db current database 2931 * @param string $table current table 2932 * 2933 * @return array $url_params url parameters 2934 */ 2935 public function getUrlParameters($db, $table) 2936 { 2937 /** 2938 * @todo check if we could replace by "db_|tbl_" - please clarify!? 2939 */ 2940 $url_params = array( 2941 'db' => $db, 2942 'sql_query' => $_POST['sql_query'] 2943 ); 2944 2945 if (preg_match('@^tbl_@', $GLOBALS['goto'])) { 2946 $url_params['table'] = $table; 2947 } 2948 2949 return $url_params; 2950 } 2951 2952 /** 2953 * Function to get html for the gis editor div 2954 * 2955 * @return string 2956 */ 2957 public function getHtmlForGisEditor() 2958 { 2959 return '<div id="gis_editor"></div>' 2960 . '<div id="popup_background"></div>' 2961 . '<br />'; 2962 } 2963 2964 /** 2965 * Function to get html for the ignore option in insert mode 2966 * 2967 * @param int $row_id row id 2968 * @param bool $checked ignore option is checked or not 2969 * 2970 * @return string 2971 */ 2972 public function getHtmlForIgnoreOption($row_id, $checked = true) 2973 { 2974 return '<input type="checkbox"' 2975 . ($checked ? ' checked="checked"' : '') 2976 . ' name="insert_ignore_' . $row_id . '"' 2977 . ' id="insert_ignore_' . $row_id . '" />' 2978 . '<label for="insert_ignore_' . $row_id . '">' 2979 . __('Ignore') 2980 . '</label><br />' . "\n"; 2981 } 2982 2983 /** 2984 * Function to get html for the function option 2985 * 2986 * @param array $column column 2987 * @param string $column_name_appendix column name appendix 2988 * 2989 * @return String 2990 */ 2991 private function getHtmlForFunctionOption(array $column, $column_name_appendix) 2992 { 2993 return '<tr class="noclick">' 2994 . '<td ' 2995 . 'class="center">' 2996 . $column['Field_title'] 2997 . '<input type="hidden" name="fields_name' . $column_name_appendix 2998 . '" value="' . $column['Field_html'] . '"/>' 2999 . '</td>'; 3000 } 3001 3002 /** 3003 * Function to get html for the column type 3004 * 3005 * @param array $column column 3006 * 3007 * @return string 3008 */ 3009 private function getHtmlForInsertEditColumnType(array $column) 3010 { 3011 return '<td class="center' . $column['wrap'] . '">' 3012 . '<span class="column_type" dir="ltr">' . $column['pma_type'] . '</span>' 3013 . '</td>'; 3014 } 3015 3016 /** 3017 * Function to get html for the insert edit form header 3018 * 3019 * @param bool $has_blob_field whether has blob field 3020 * @param bool $is_upload whether is upload 3021 * 3022 * @return string 3023 */ 3024 public function getHtmlForInsertEditFormHeader($has_blob_field, $is_upload) 3025 { 3026 $html_output ='<form id="insertForm" class="lock-page '; 3027 if ($has_blob_field && $is_upload) { 3028 $html_output .='disableAjax'; 3029 } 3030 $html_output .='" method="post" action="tbl_replace.php" name="insertForm" '; 3031 if ($is_upload) { 3032 $html_output .= ' enctype="multipart/form-data"'; 3033 } 3034 $html_output .= '>'; 3035 3036 return $html_output; 3037 } 3038 3039 /** 3040 * Function to get html for each insert/edit column 3041 * 3042 * @param array $table_columns table columns 3043 * @param int $column_number column index in table_columns 3044 * @param array $comments_map comments map 3045 * @param bool $timestamp_seen whether timestamp seen 3046 * @param array $current_result current result 3047 * @param string $chg_evt_handler javascript change event handler 3048 * @param string $jsvkey javascript validation key 3049 * @param string $vkey validation key 3050 * @param bool $insert_mode whether insert mode 3051 * @param array $current_row current row 3052 * @param int &$o_rows row offset 3053 * @param int &$tabindex tab index 3054 * @param int $columns_cnt columns count 3055 * @param bool $is_upload whether upload 3056 * @param int $tabindex_for_function tab index offset for function 3057 * @param array $foreigners foreigners 3058 * @param int $tabindex_for_null tab index offset for null 3059 * @param int $tabindex_for_value tab index offset for value 3060 * @param string $table table 3061 * @param string $db database 3062 * @param int $row_id row id 3063 * @param array $titles titles 3064 * @param int $biggest_max_file_size biggest max file size 3065 * @param string $default_char_editing default char editing mode which is stored 3066 * in the config.inc.php script 3067 * @param string $text_dir text direction 3068 * @param array $repopulate the data to be repopulated 3069 * @param array $column_mime the mime information of column 3070 * @param string $where_clause the where clause 3071 * 3072 * @return string 3073 */ 3074 private function getHtmlForInsertEditFormColumn( 3075 array $table_columns, 3076 $column_number, 3077 array $comments_map, 3078 $timestamp_seen, 3079 $current_result, 3080 $chg_evt_handler, 3081 $jsvkey, 3082 $vkey, 3083 $insert_mode, 3084 array $current_row, 3085 &$o_rows, 3086 &$tabindex, 3087 $columns_cnt, 3088 $is_upload, 3089 $tabindex_for_function, 3090 array $foreigners, 3091 $tabindex_for_null, 3092 $tabindex_for_value, 3093 $table, 3094 $db, 3095 $row_id, 3096 array $titles, 3097 $biggest_max_file_size, 3098 $default_char_editing, 3099 $text_dir, 3100 array $repopulate, 3101 array $column_mime, 3102 $where_clause 3103 ) { 3104 $column = $table_columns[$column_number]; 3105 $readOnly = false; 3106 3107 if (! isset($column['processed'])) { 3108 $column = $this->analyzeTableColumnsArray( 3109 $column, 3110 $comments_map, 3111 $timestamp_seen 3112 ); 3113 } 3114 $as_is = false; 3115 if (!empty($repopulate) && !empty($current_row)) { 3116 $current_row[$column['Field']] = $repopulate[$column['Field_md5']]; 3117 $as_is = true; 3118 } 3119 3120 $extracted_columnspec 3121 = Util::extractColumnSpec($column['Type']); 3122 3123 if (-1 === $column['len']) { 3124 $column['len'] = $this->dbi->fieldLen( 3125 $current_result, 3126 $column_number 3127 ); 3128 // length is unknown for geometry fields, 3129 // make enough space to edit very simple WKTs 3130 if (-1 === $column['len']) { 3131 $column['len'] = 30; 3132 } 3133 } 3134 //Call validation when the form submitted... 3135 $onChangeClause = $chg_evt_handler 3136 . "=\"return verificationsAfterFieldChange('" 3137 . Sanitize::escapeJsString($column['Field_md5']) . "', '" 3138 . Sanitize::escapeJsString($jsvkey) . "','" . $column['pma_type'] . "')\""; 3139 3140 // Use an MD5 as an array index to avoid having special characters 3141 // in the name attribute (see bug #1746964 ) 3142 $column_name_appendix = $vkey . '[' . $column['Field_md5'] . ']'; 3143 3144 if ($column['Type'] === 'datetime' 3145 && ! isset($column['Default']) 3146 && ! is_null($column['Default']) 3147 && $insert_mode 3148 ) { 3149 $column['Default'] = date('Y-m-d H:i:s', time()); 3150 } 3151 3152 $html_output = $this->getHtmlForFunctionOption( 3153 $column, 3154 $column_name_appendix 3155 ); 3156 3157 if ($GLOBALS['cfg']['ShowFieldTypesInDataEditView']) { 3158 $html_output .= $this->getHtmlForInsertEditColumnType($column); 3159 } //End if 3160 3161 // Get a list of GIS data types. 3162 $gis_data_types = Util::getGISDatatypes(); 3163 3164 // Prepares the field value 3165 $real_null_value = false; 3166 $special_chars_encoded = ''; 3167 if (!empty($current_row)) { 3168 // (we are editing) 3169 list( 3170 $real_null_value, $special_chars_encoded, $special_chars, 3171 $data, $backup_field 3172 ) 3173 = $this->getSpecialCharsAndBackupFieldForExistingRow( 3174 $current_row, 3175 $column, 3176 $extracted_columnspec, 3177 $real_null_value, 3178 $gis_data_types, 3179 $column_name_appendix, 3180 $as_is 3181 ); 3182 } else { 3183 // (we are inserting) 3184 // display default values 3185 $tmp = $column; 3186 if (isset($repopulate[$column['Field_md5']])) { 3187 $tmp['Default'] = $repopulate[$column['Field_md5']]; 3188 } 3189 list($real_null_value, $data, $special_chars, $backup_field, 3190 $special_chars_encoded 3191 ) 3192 = $this->getSpecialCharsAndBackupFieldForInsertingMode( 3193 $tmp, 3194 $real_null_value 3195 ); 3196 unset($tmp); 3197 } 3198 3199 $idindex = ($o_rows * $columns_cnt) + $column_number + 1; 3200 $tabindex = $idindex; 3201 3202 // Get a list of data types that are not yet supported. 3203 $no_support_types = Util::unsupportedDatatypes(); 3204 3205 // The function column 3206 // ------------------- 3207 $foreignData = $this->relation->getForeignData( 3208 $foreigners, 3209 $column['Field'], 3210 false, 3211 '', 3212 '' 3213 ); 3214 if ($GLOBALS['cfg']['ShowFunctionFields']) { 3215 $html_output .= $this->getFunctionColumn( 3216 $column, 3217 $is_upload, 3218 $column_name_appendix, 3219 $onChangeClause, 3220 $no_support_types, 3221 $tabindex_for_function, 3222 $tabindex, 3223 $idindex, 3224 $insert_mode, 3225 $readOnly, 3226 $foreignData 3227 ); 3228 } 3229 3230 // The null column 3231 // --------------- 3232 $html_output .= $this->getNullColumn( 3233 $column, 3234 $column_name_appendix, 3235 $real_null_value, 3236 $tabindex, 3237 $tabindex_for_null, 3238 $idindex, 3239 $vkey, 3240 $foreigners, 3241 $foreignData, 3242 $readOnly 3243 ); 3244 3245 // The value column (depends on type) 3246 // ---------------- 3247 // See bug #1667887 for the reason why we don't use the maxlength 3248 // HTML attribute 3249 3250 //add data attributes "no of decimals" and "data type" 3251 $no_decimals = 0; 3252 $type = current(explode("(", $column['pma_type'])); 3253 if (preg_match('/\(([^()]+)\)/', $column['pma_type'], $match)) { 3254 $match[0] = trim($match[0], '()'); 3255 $no_decimals = $match[0]; 3256 } 3257 $html_output .= '<td' . ' data-type="' . $type . '"' . ' data-decimals="' 3258 . $no_decimals . '">' . "\n"; 3259 // Will be used by js/tbl_change.js to set the default value 3260 // for the "Continue insertion" feature 3261 $html_output .= '<span class="default_value hide">' 3262 . $special_chars . '</span>'; 3263 3264 // Check input transformation of column 3265 $transformed_html = ''; 3266 if (!empty($column_mime['input_transformation'])) { 3267 $file = $column_mime['input_transformation']; 3268 $include_file = 'libraries/classes/Plugins/Transformations/' . $file; 3269 if (is_file($include_file)) { 3270 $class_name = Transformations::getClassName($include_file); 3271 if (class_exists($class_name)) { 3272 $transformation_plugin = new $class_name(); 3273 $transformation_options = Transformations::getOptions( 3274 $column_mime['input_transformation_options'] 3275 ); 3276 $_url_params = array( 3277 'db' => $db, 3278 'table' => $table, 3279 'transform_key' => $column['Field'], 3280 'where_clause_sign' => Core::signSqlQuery($where_clause), 3281 'where_clause' => $where_clause 3282 ); 3283 $transformation_options['wrapper_link'] 3284 = Url::getCommon($_url_params); 3285 $current_value = ''; 3286 if (isset($current_row[$column['Field']])) { 3287 $current_value = $current_row[$column['Field']]; 3288 } 3289 if (method_exists($transformation_plugin, 'getInputHtml')) { 3290 $transformed_html = $transformation_plugin->getInputHtml( 3291 $column, 3292 $row_id, 3293 $column_name_appendix, 3294 $transformation_options, 3295 $current_value, 3296 $text_dir, 3297 $tabindex, 3298 $tabindex_for_value, 3299 $idindex 3300 ); 3301 } 3302 if (method_exists($transformation_plugin, 'getScripts')) { 3303 $GLOBALS['plugin_scripts'] = array_merge( 3304 $GLOBALS['plugin_scripts'], 3305 $transformation_plugin->getScripts() 3306 ); 3307 } 3308 } 3309 } 3310 } 3311 if (!empty($transformed_html)) { 3312 $html_output .= $transformed_html; 3313 } else { 3314 $html_output .= $this->getValueColumn( 3315 $column, 3316 $backup_field, 3317 $column_name_appendix, 3318 $onChangeClause, 3319 $tabindex, 3320 $tabindex_for_value, 3321 $idindex, 3322 $data, 3323 $special_chars, 3324 $foreignData, 3325 array($table, $db), 3326 $row_id, 3327 $titles, 3328 $text_dir, 3329 $special_chars_encoded, 3330 $vkey, 3331 $is_upload, 3332 $biggest_max_file_size, 3333 $default_char_editing, 3334 $no_support_types, 3335 $gis_data_types, 3336 $extracted_columnspec, 3337 $readOnly 3338 ); 3339 } 3340 return $html_output; 3341 } 3342 3343 /** 3344 * Function to get html for each insert/edit row 3345 * 3346 * @param array $url_params url parameters 3347 * @param array $table_columns table columns 3348 * @param array $comments_map comments map 3349 * @param bool $timestamp_seen whether timestamp seen 3350 * @param array $current_result current result 3351 * @param string $chg_evt_handler javascript change event handler 3352 * @param string $jsvkey javascript validation key 3353 * @param string $vkey validation key 3354 * @param bool $insert_mode whether insert mode 3355 * @param array $current_row current row 3356 * @param int &$o_rows row offset 3357 * @param int &$tabindex tab index 3358 * @param int $columns_cnt columns count 3359 * @param bool $is_upload whether upload 3360 * @param int $tabindex_for_function tab index offset for function 3361 * @param array $foreigners foreigners 3362 * @param int $tabindex_for_null tab index offset for null 3363 * @param int $tabindex_for_value tab index offset for value 3364 * @param string $table table 3365 * @param string $db database 3366 * @param int $row_id row id 3367 * @param array $titles titles 3368 * @param int $biggest_max_file_size biggest max file size 3369 * @param string $text_dir text direction 3370 * @param array $repopulate the data to be repopulated 3371 * @param array $where_clause_array the array of where clauses 3372 * 3373 * @return string 3374 */ 3375 public function getHtmlForInsertEditRow( 3376 array $url_params, 3377 array $table_columns, 3378 array $comments_map, 3379 $timestamp_seen, 3380 $current_result, 3381 $chg_evt_handler, 3382 $jsvkey, 3383 $vkey, 3384 $insert_mode, 3385 array $current_row, 3386 &$o_rows, 3387 &$tabindex, 3388 $columns_cnt, 3389 $is_upload, 3390 $tabindex_for_function, 3391 array $foreigners, 3392 $tabindex_for_null, 3393 $tabindex_for_value, 3394 $table, 3395 $db, 3396 $row_id, 3397 array $titles, 3398 $biggest_max_file_size, 3399 $text_dir, 3400 array $repopulate, 3401 array $where_clause_array 3402 ) { 3403 $html_output = $this->getHeadAndFootOfInsertRowTable($url_params) 3404 . '<tbody>'; 3405 3406 //store the default value for CharEditing 3407 $default_char_editing = $GLOBALS['cfg']['CharEditing']; 3408 $mime_map = Transformations::getMIME($db, $table); 3409 $where_clause = ''; 3410 if (isset($where_clause_array[$row_id])) { 3411 $where_clause = $where_clause_array[$row_id]; 3412 } 3413 for ($column_number = 0; $column_number < $columns_cnt; $column_number++) { 3414 $table_column = $table_columns[$column_number]; 3415 $column_mime = array(); 3416 if (isset($mime_map[$table_column['Field']])) { 3417 $column_mime = $mime_map[$table_column['Field']]; 3418 } 3419 3420 $virtual = [ 3421 'VIRTUAL', 3422 'PERSISTENT', 3423 'VIRTUAL GENERATED', 3424 'STORED GENERATED', 3425 ]; 3426 if (! in_array($table_column['Extra'], $virtual)) { 3427 $html_output .= $this->getHtmlForInsertEditFormColumn( 3428 $table_columns, 3429 $column_number, 3430 $comments_map, 3431 $timestamp_seen, 3432 $current_result, 3433 $chg_evt_handler, 3434 $jsvkey, 3435 $vkey, 3436 $insert_mode, 3437 $current_row, 3438 $o_rows, 3439 $tabindex, 3440 $columns_cnt, 3441 $is_upload, 3442 $tabindex_for_function, 3443 $foreigners, 3444 $tabindex_for_null, 3445 $tabindex_for_value, 3446 $table, 3447 $db, 3448 $row_id, 3449 $titles, 3450 $biggest_max_file_size, 3451 $default_char_editing, 3452 $text_dir, 3453 $repopulate, 3454 $column_mime, 3455 $where_clause 3456 ); 3457 } 3458 } // end for 3459 $o_rows++; 3460 $html_output .= ' </tbody>' 3461 . '</table></div><br />' 3462 . '<div class="clearfloat"></div>'; 3463 3464 return $html_output; 3465 } 3466 3467 /** 3468 * Returns whether the user has necessary insert/update privileges for the column 3469 * 3470 * @param array $table_column array of column details 3471 * @param bool $insert_mode whether on insert mode 3472 * 3473 * @return boolean whether user has necessary privileges 3474 */ 3475 private function userHasColumnPrivileges(array $table_column, $insert_mode) 3476 { 3477 $privileges = $table_column['Privileges']; 3478 return ($insert_mode && strstr($privileges, 'insert') !== false) 3479 || (! $insert_mode && strstr($privileges, 'update') !== false); 3480 } 3481} 3482