| [ Index ] |
|
Code source de eGroupWare 1.2.106-2 |
1 <?php 2 /**************************************************************************\ 3 * eGroupWare - eTemplates - DB-Tools * 4 * http://www.egroupware.org * 5 * Written by Ralf Becker <RalfBecker@outdoor-training.de> * 6 * -------------------------------------------- * 7 * This program is free software; you can redistribute it and/or modify it * 8 * under the terms of the GNU General Public License as published by the * 9 * Free Software Foundation; either version 2 of the License, or (at your * 10 * option) any later version. * 11 \**************************************************************************/ 12 13 /* $Id: class.db_tools.inc.php 20167 2005-12-19 04:27:19Z ralfbecker $ */ 14 15 /** 16 * db-tools: creats and modifys eGroupWare schem-files (to be installed via setup) 17 * 18 * @package etemplate 19 * @subpackage tools 20 * @author RalfBecker-AT-outdoor-training.de 21 * @license GPL 22 */ 23 class db_tools 24 { 25 var $public_functions = array 26 ( 27 'edit' => True, 28 'needs_save' => True, 29 ); 30 31 var $debug = 0; 32 var $editor; // editor eTemplate 33 var $data; // Table definitions 34 var $app; // used app 35 var $table; // used table 36 var $types = array( 37 'varchar' => 'varchar', 38 'int' => 'int', 39 'auto' => 'auto', 40 'blob' => 'blob', 41 'char' => 'char', 42 'date' => 'date', 43 'decimal' => 'decimal', 44 'float' => 'float', 45 'longtext' => 'longtext', 46 'text' => 'text', 47 'timestamp' => 'timestamp', 48 'bool' => 'boolean', 49 // 'abstime' => 'abstime (mysql:timestamp)', 50 ); 51 var $setup_header = '<?php 52 /**************************************************************************\\ 53 * eGroupWare - Setup * 54 * http://www.eGroupWare.org * 55 * Created by eTemplates DB-Tools written by ralfbecker@outdoor-training.de * 56 * -------------------------------------------- * 57 * This program is free software; you can redistribute it and/or modify it * 58 * under the terms of the GNU General Public License as published by the * 59 * Free Software Foundation; either version 2 of the License, or (at your * 60 * option) any later version. * 61 \\**************************************************************************/ 62 63 /* $Id: class.db_tools.inc.php 20167 2005-12-19 04:27:19Z ralfbecker $ */ 64 '; 65 66 /** 67 * constructor of class 68 */ 69 function db_tools() 70 { 71 $this->editor =& CreateObject('etemplate.etemplate','etemplate.db-tools.edit'); 72 $this->data = array(); 73 74 if (!is_array($GLOBALS['egw_info']['apps']) || !count($GLOBALS['egw_info']['apps'])) 75 { 76 ExecMethod('phpgwapi.applications.read_installed_apps'); 77 } 78 $GLOBALS['egw_info']['flags']['app_header'] = 79 $GLOBALS['egw_info']['apps']['etemplate']['title'].' - '.lang('DB-Tools'); 80 } 81 82 /** 83 * table editor (and the callback/submit-method too) 84 */ 85 function edit($content='',$msg = '') 86 { 87 if (isset($_GET['app'])) 88 { 89 $this->app = $_GET['app']; 90 } 91 if (is_array($content)) 92 { 93 if ($this->debug) 94 { 95 echo "content ="; _debug_array($content); 96 } 97 $this->app = $content['app']; // this is what the user selected 98 $this->table = $content['table_name']; 99 $posted_app = $content['posted_app']; // this is the old selection 100 $posted_table = $content['posted_table']; 101 } 102 if ($posted_app && $posted_table && // user changed app or table 103 ($posted_app != $this->app || $posted_table != $this->table)) 104 { 105 if ($this->needs_save('',$posted_app,$posted_table,$this->content2table($content))) 106 { 107 return; 108 } 109 $this->renames = array(); 110 } 111 if (!$this->app) 112 { 113 $this->table = ''; 114 $table_names = array('' => lang('none')); 115 } 116 else 117 { 118 $this->read($this->app,$this->data); 119 120 foreach($this->data as $name => $table) 121 { 122 $table_names[$name] = $name; 123 } 124 } 125 if (!$this->table || $this->app != $posted_app) 126 { 127 reset($this->data); 128 list($this->table) = each($this->data); // use first table 129 } 130 elseif ($this->app == $posted_app && $posted_table) 131 { 132 $this->data[$posted_table] = $this->content2table($content); 133 } 134 if ($content['write_tables']) 135 { 136 if ($this->needs_save('',$this->app,$this->table,$this->data[$posted_table])) 137 { 138 return; 139 } 140 $msg .= lang('Table unchanged, no write necessary !!!'); 141 } 142 elseif ($content['delete']) 143 { 144 list($col) = each($content['delete']); 145 146 @reset($this->data[$posted_table]['fd']); 147 while ($col-- > 0 && list($key,$data) = @each($this->data[$posted_table]['fd'])) ; 148 149 unset($this->data[$posted_table]['fd'][$key]); 150 $this->changes[$posted_table][$key] = '**deleted**'; 151 } 152 elseif ($content['add_column']) 153 { 154 $this->data[$posted_table]['fd'][''] = array(); 155 } 156 elseif ($content['add_table'] || $content['import']) 157 { 158 if (!$this->app) 159 { 160 $msg .= lang('Select an app first !!!'); 161 } 162 elseif (!$content['new_table_name']) 163 { 164 $msg .= lang('Please enter table-name first !!!'); 165 } 166 elseif ($content['add_table']) 167 { 168 $this->table = $content['new_table_name']; 169 $this->data[$this->table] = array('fd' => array(),'pk' =>array(),'ix' => array(),'uc' => array(),'fk' => array()); 170 $msg .= lang('New table created'); 171 } 172 else // import 173 { 174 $oProc =& CreateObject('phpgwapi.schema_proc',$GLOBALS['egw_info']['server']['db_type']); 175 if (method_exists($oProc,'GetTableDefinition')) 176 { 177 $this->data[$this->table = $content['new_table_name']] = $oProc->GetTableDefinition($content['new_table_name']); 178 } 179 else // to support eGW 1.0 180 { 181 $oProc->m_odb = clone($GLOBALS['egw']->db); 182 $oProc->m_oTranslator->_GetColumns($oProc,$content['new_table_name'],$nul); 183 184 while (list($key,$tbldata) = each ($oProc->m_oTranslator->sCol)) 185 { 186 $cols .= $tbldata; 187 } 188 eval('$cols = array('. $cols . ');'); 189 190 $this->data[$this->table = $content['new_table_name']] = array( 191 'fd' => $cols, 192 'pk' => $oProc->m_oTranslator->pk, 193 'fk' => $oProc->m_oTranslator->fk, 194 'ix' => $oProc->m_oTranslator->ix, 195 'uc' => $oProc->m_oTranslator->uc 196 ); 197 } 198 } 199 } 200 elseif ($content['editor']) 201 { 202 ExecMethod('etemplate.editor.edit'); 203 return; 204 } 205 $add_index = isset($content['add_index']); 206 207 // from here on, filling new content for eTemplate 208 $content = array( 209 'msg' => $msg, 210 'table_name' => $this->table, 211 'app' => $this->app, 212 ); 213 if (!isset($table_names[$this->table])) // table is not jet written 214 { 215 $table_names[$this->table] = $this->table; 216 } 217 $sel_options = array( 218 'table_name' => $table_names, 219 'type' => $this->types 220 ); 221 if ($this->table != '' && isset($this->data[$this->table])) 222 { 223 $content += $this->table2content($this->data[$this->table],$sel_options['Index'],$add_index); 224 } 225 $no_button = array( ); 226 if (!$this->app || !$this->table) 227 { 228 $no_button += array('write_tables' => True); 229 } 230 if ($this->debug) 231 { 232 echo 'editor.edit: content ='; _debug_array($content); 233 } 234 $this->editor->exec('etemplate.db_tools.edit',$content,$sel_options,$no_button, 235 array('posted_table' => $this->table,'posted_app' => $this->app,'changes' => $this->changes)); 236 } 237 238 /** 239 * checks if table was changed and if so offers user to save changes 240 * 241 * @param array $cont the content of the form (if called by process_exec) 242 * @param string $posted_app the app the table is from 243 * @param string $posted_table the table-name 244 * @param array $edited_table the edited table-definitions 245 * @return only if no changes 246 */ 247 function needs_save($cont='',$posted_app='',$posted_table='',$edited_table='',$msg='') 248 { 249 //echo "<p>db_tools::needs_save(cont,'$posted_app','$posted_table',edited_table,'$msg')</p> cont=\n"; _debug_array($cont); echo "edited_table="; _debug_array($edited_table); 250 if (!$posted_app && is_array($cont)) 251 { 252 if (isset($cont['yes'])) 253 { 254 $this->app = $cont['app']; 255 $this->table = $cont['table']; 256 $this->read($this->app,$this->data); 257 $this->data[$this->table] = $cont['edited_table']; 258 $this->changes = $cont['changes']; 259 if ($cont['new_version']) 260 { 261 $this->update($this->app,$this->data,$cont['new_version']); 262 } 263 else 264 { 265 foreach($this->data as $tname => $tinfo) 266 { 267 $tables .= ($tables ? ',' : '') . "'$tname'"; 268 } 269 $this->setup_version($this->app,'',$tables); 270 } 271 if (!$this->write($this->app,$this->data)) 272 { 273 $this->app = $cont['new_app']; // these are the ones, the users whiches to change too 274 $this->table = $cont['new_table']; 275 276 return $this->needs_save('',$cont['app'],$cont['table'],$cont['edited_table'], 277 lang('Error: writing file (no write-permission for the webserver) !!!')); 278 } 279 $msg = lang('File writen'); 280 } 281 $this->changes = array(); 282 // return to edit with everything set, so the user gets the table he asked for 283 $this->edit(array( 284 'app' => $cont['new_app'], 285 'table_name' => $cont['app']==$cont['new_app'] ? $cont['new_table'] : '', 286 'posted_app' => $cont['new_app'] 287 ),$msg); 288 289 return True; 290 } 291 $new_app = $this->app; // these are the ones, the users whiches to change too 292 $new_table = $this->table; 293 294 $this->app = $posted_app; 295 $this->data = array(); 296 $this->read($posted_app,$this->data); 297 298 if (isset($this->data[$posted_table]) && 299 $this->tables_identical($this->data[$posted_table],$edited_table)) 300 { 301 if ($new_app != $this->app) // are we changeing the app, or hit the user just write 302 { 303 $this->app = $new_app; // if we change init the data empty 304 $this->data = array(); 305 } 306 return False; // continue edit 307 } 308 $content = array( 309 'msg' => $msg, 310 'app' => $posted_app, 311 'table' => $posted_table, 312 'version' => $this->setup_version($posted_app) 313 ); 314 $preserv = $content + array( 315 'new_app' => $new_app, 316 'new_table' => $new_table, 317 'edited_table' => $edited_table, 318 'changes' => $this->changes 319 ); 320 $new_version = explode('.',$content['version']); 321 $minor = count($new_version)-1; 322 $new_version[$minor] = sprintf('%03d',1+$new_version[$minor]); 323 $content['new_version'] = implode('.',$new_version); 324 325 $tmpl =& new etemplate('etemplate.db-tools.ask_save'); 326 327 if (!file_exists(EGW_SERVER_ROOT."/$posted_app/setup/tables_current.inc.php")) 328 { 329 $tmpl->disable_cells('version'); 330 $tmpl->disable_cells('new_version'); 331 } 332 $tmpl->exec('etemplate.db_tools.needs_save',$content,array(),array(),$preserv); 333 334 return True; // dont continue in edit 335 } 336 337 /** 338 * checks if there is an index (only) on $col (not a multiple index incl. $col) 339 * 340 * @param string $col column name 341 * @param array $index ix or uc array of table-defintion 342 * @param string &$options db specific options 343 * @return True if $col has a single index 344 */ 345 function has_single_index($col,$index,&$options) 346 { 347 foreach($index as $in) 348 { 349 if ($in == $col || is_array($in) && $in[0] == $col && !isset($in[1])) 350 { 351 if ($in != $col && isset($in['options'])) 352 { 353 foreach($in['options'] as $db => $opts) 354 { 355 $options[] = $db.'('.(is_array($opts)?implode(',',$opts):$opts).')'; 356 } 357 $options = implode(', ',$options); 358 } 359 return True; 360 } 361 } 362 return False; 363 } 364 365 /** 366 * creates content-array from a table 367 * 368 * @param array $table table-definition, eg. $phpgw_baseline[$table_name] 369 * @param array &$columns returns array with column-names 370 * @param bool $extra_index add an additional index-row 371 * @return array content-array to call exec with 372 */ 373 function table2content($table,&$columns,$extra_index=False) 374 { 375 $content = $columns = array(); 376 for ($n = 1; list($col_name,$col_defs) = each($table['fd']); ++$n) 377 { 378 $col_defs['name'] = $col_name; 379 $col_defs['pk'] = in_array($col_name,$table['pk']); 380 $col_defs['uc'] = $this->has_single_index($col_name,$table['uc'],$col_defs['options']); 381 $col_defs['ix'] = $this->has_single_index($col_name,$table['ix'],$col_defs['options']); 382 $col_defs['fk'] = $table['fk'][$col_name]; 383 if (isset($col_defs['default']) && $col_defs['default'] == '') 384 { 385 $col_defs['default'] = is_int($col_defs['default']) ? '0' : "''"; // spezial value for empty, but set, default 386 } 387 $col_defs['notnull'] = isset($col_defs['nullable']) && !$col_defs['nullable']; 388 389 $col_defs['n'] = $n; 390 391 $content["Row$n"] = $col_defs; 392 393 $columns[$n] = $col_name; 394 } 395 $n = 2; 396 foreach(array('uc','ix') as $type) 397 { 398 foreach($table[$type] as $index) 399 { 400 if (is_array($index) && isset($index[1])) // multicolum index 401 { 402 $content['Index'][$n]['unique'] = $type == 'uc'; 403 $content['Index'][$n]['n'] = $n - 1; 404 foreach($index as $col) 405 { 406 $content['Index'][$n][] = array_search($col,$columns); 407 } 408 ++$n; 409 } 410 } 411 } 412 if ($extra_index) 413 { 414 $content['Index'][$n]['n'] = $n-1; 415 } 416 if ($this->debug >= 3) 417 { 418 echo "<p>table2content(,,'$extra_index'): content ="; _debug_array($content); 419 echo "<p>columns ="; _debug_array($columns); 420 } 421 return $content; 422 } 423 424 /** 425 * creates table-definition from posted content 426 * 427 * It sets some reasonalbe defaults for not set precisions (else setup will not install) 428 * 429 * @param array $content posted content-array 430 * @return table-definition 431 */ 432 function content2table($content) 433 { 434 if (!is_array($this->data)) 435 { 436 $this->read($content['posted_app'],$this->data); 437 } 438 $old_cols = $this->data[$posted_table = $content['posted_table']]['fd']; 439 $this->changes = $content['changes']; 440 441 $table = array(); 442 $table['fd'] = array(); // do it in the default order of tables_* 443 $table['pk'] = array(); 444 $table['fk'] = array(); 445 $table['ix'] = array(); 446 $table['uc'] = array(); 447 for (reset($content),$n = 1; isset($content["Row$n"]); ++$n) 448 { 449 $col = $content["Row$n"]; 450 451 while ((list($old_name,$old_col) = @each($old_cols)) && 452 $this->changes[$posted_table][$old_name] == '**deleted**') ; 453 454 if (($name = $col['name']) != '') // ignoring lines without column-name 455 { 456 if ($col['name'] != $old_name && $n <= count($old_cols)) // column renamed --> remeber it 457 { 458 $this->changes[$posted_table][$old_name] = $col['name']; 459 //echo "<p>content2table: $posted_table.$old_name renamed to $col[name]</p>\n"; 460 } 461 if ($col['precision'] <= 0) 462 { 463 switch ($col['type']) // set some defaults for precision, else setup fails 464 { 465 case 'float': 466 case 'int': $col['precision'] = 4; break; 467 case 'char': $col['precision'] = 1; break; 468 case 'varchar': $col['precision'] = 255; break; 469 } 470 } 471 while (list($prop,$val) = each($col)) 472 { 473 switch ($prop) 474 { 475 case 'default': 476 case 'type': // selectbox ensures type is not empty 477 case 'precision': 478 case 'scale': 479 // case 'nullable': 480 if ($val != '' || $prop == 'nullable') 481 { 482 $table['fd'][$name][$prop] = $prop=='default'&& $val=="''" ? '' : $val; 483 } 484 break; 485 case 'notnull': 486 if ($val) 487 { 488 $table['fd'][$name]['nullable'] = False; 489 } 490 break; 491 case 'pk': 492 case 'uc': 493 case 'ix': 494 if ($val) 495 { 496 if ($col['options']) 497 { 498 $opts = array(); 499 foreach(explode(',',$col['options']) as $opt) 500 { 501 list($db,$opt) = split('[(:)]',$opt); 502 $opts[$db] = is_numeric($opt) ? intval($opt) : $opt; 503 } 504 $table[$prop][] = array( 505 $name, 506 'options' => $opts 507 ); 508 } 509 else 510 { 511 $table[$prop][] = $name; 512 } 513 } 514 break; 515 case 'fk': 516 if ($val != '') 517 { 518 $table['fk'][$name] = $val; 519 } 520 break; 521 } 522 } 523 $num2col[$n] = $col['name']; 524 } 525 } 526 foreach($content['Index'] as $n => $index) 527 { 528 $idx_arr = array(); 529 foreach($index as $key => $num) 530 { 531 if (is_numeric($key) && $num && @$num2col[$num]) 532 { 533 $idx_arr[] = $num2col[$num]; 534 } 535 } 536 if (count($idx_arr) && !isset($content['delete_index'][$n])) 537 { 538 if ($index['unique']) 539 { 540 $table['uc'][] = $idx_arr; 541 } 542 else 543 { 544 $table['ix'][] = $idx_arr; 545 } 546 } 547 } 548 if ($this->debug >= 2) 549 { 550 echo "<p>content2table: table ="; _debug_array($table); 551 echo "<p>changes = "; _debug_array($this->changes); 552 } 553 return $table; 554 } 555 556 /** 557 * includes $app/setup/tables_current.inc.php 558 * @param string $app application name 559 * @param array &$phpgw_baseline where to return the data 560 * @return boolean True if file found, False else 561 */ 562 function read($app,&$phpgw_baseline) 563 { 564 $file = EGW_SERVER_ROOT."/$app/setup/tables_current.inc.php"; 565 566 $phpgw_baseline = array(); 567 568 if ($app != '' && file_exists($file)) 569 { 570 include($file); 571 } 572 else 573 { 574 return False; 575 } 576 if ($this->debug >= 5) 577 { 578 echo "<p>read($app): file='$file', phpgw_baseline ="; 579 _debug_array($phpgw_baseline); 580 } 581 return True; 582 } 583 584 /** 585 * returns an array as string in php-notation 586 * 587 * @param array $arr 588 * @param int $depth for idention 589 * @param string $parent 590 * @return string 591 */ 592 function write_array($arr,$depth,$parent='') 593 { 594 if (in_array($parent,array('pk','fk','ix','uc'))) 595 { 596 $depth = 0; 597 } 598 if ($depth) 599 { 600 $tabs = "\n"; 601 for ($n = 0; $n < $depth; ++$n) 602 { 603 $tabs .= "\t"; 604 } 605 ++$depth; 606 } 607 $def = "array($tabs".($tabs ? "\t" : ''); 608 609 $n = 0; 610 foreach($arr as $key => $val) 611 { 612 if (!is_int($key)) 613 { 614 $def .= "'$key' => "; 615 } 616 if (is_array($val)) 617 { 618 $def .= $this->write_array($val,$parent == 'fd' ? 0 : $depth,$key); 619 } 620 else 621 { 622 if (!$only_vals && $key === 'nullable') 623 { 624 $def .= $val ? 'True' : 'False'; 625 } 626 else 627 { 628 $def .= "'$val'"; 629 } 630 } 631 if ($n < count($arr)-1) 632 { 633 $def .= ",$tabs".($tabs ? "\t" : ''); 634 } 635 ++$n; 636 } 637 $def .= "$tabs)"; 638 639 return $def; 640 } 641 642 /** 643 * writes tabledefinitions $phpgw_baseline to file /$app/setup/tables_current.inc.php 644 * 645 * @param string $app app-name 646 * @param array $phpgw_baseline tabledefinitions 647 * @return boolean True if file writen else False 648 */ 649 function write($app,$phpgw_baseline) 650 { 651 $file = EGW_SERVER_ROOT."/$app/setup/tables_current.inc.php"; 652 653 if (file_exists($file) && ($f = fopen($file,'r'))) 654 { 655 $header = fread($f,filesize($file)); 656 if ($end = strpos($header,');')) 657 { 658 $footer = substr($header,$end+3); // this preservs other stuff, which should not be there 659 } 660 $header = substr($header,0,strpos($header,'$phpgw_baseline')); 661 fclose($f); 662 663 if (is_writable(EGW_SERVER_ROOT."/$app/setup")) 664 { 665 $old_file = EGW_SERVER_ROOT . "/$app/setup/tables_current.old.inc.php"; 666 if (file_exists($old_file)) 667 { 668 unlink($old_file); 669 } 670 rename($file,$old_file); 671 } 672 while ($header[strlen($header)-1] == "\t") 673 { 674 $header = substr($header,0,strlen($header)-1); 675 } 676 } 677 if (!$header) 678 { 679 $header = $this->setup_header . "\n\n"; 680 } 681 if (!is_writeable(EGW_SERVER_ROOT."/$app/setup") || !($f = fopen($file,'w'))) 682 { 683 return False; 684 } 685 $def .= "\t\$phpgw_baseline = "; 686 $def .= $this->write_array($phpgw_baseline,1); 687 $def .= ";\n"; 688 689 fwrite($f,$header . $def . $footer); 690 fclose($f); 691 692 return True; 693 } 694 695 /** 696 * reads and updates the version and tables info in file $app/setup/setup.inc.php 697 * @param string $app the app 698 * @param string $new new version number to set, if $new != '' 699 * @param string $tables new tables to include (comma delimited), if != '' 700 * @return the version or False if the file could not be read or written 701 */ 702 function setup_version($app,$new = '',$tables='') 703 { 704 //echo "<p>etemplate.db_tools.setup_version('$app','$new','$tables')</p>\n"; 705 706 $file = EGW_SERVER_ROOT."/$app/setup/setup.inc.php"; 707 if (file_exists($file)) 708 { 709 include($file); 710 } 711 if (!is_array($setup_info[$app]) || !isset($setup_info[$app]['version'])) 712 { 713 return False; 714 } 715 if (($new == '' || $setup_info[$app]['version'] == $new) && 716 (!$tables || $setup_info[$app]['tables'] && "'".implode("','",$setup_info[$app]['tables'])."'" == $tables)) 717 { 718 return $setup_info[$app]['version']; // no change requested or not necessary 719 } 720 if ($new == '') 721 { 722 $new = $setup_info[$app]['version']; 723 } 724 if (!($f = fopen($file,'r'))) 725 { 726 return False; 727 } 728 $fcontent = fread($f,filesize($file)); 729 fclose ($f); 730 731 $app_pattern = "'$app'"; 732 if (preg_match("/define\('([^']+)',$app_pattern\)/",$fcontent,$matches)) 733 { 734 $app_pattern = $matches[1]; 735 } 736 if (is_writable(EGW_SERVER_ROOT."/$app/setup")) 737 { 738 $old_file = EGW_SERVER_ROOT . "/$app/setup/setup.old.inc.php"; 739 if (file_exists($old_file)) 740 { 741 unlink($old_file); 742 } 743 rename($file,$old_file); 744 } 745 $fnew = preg_replace('/(.*\\$'."setup_info\\[$app_pattern\\]\\['version'\\][ \\t]*=[ \\t]*)'[^']*'(.*)/i","\\1'$new'\\2",$fcontent); 746 747 if ($tables != '') 748 { 749 if (isset($setup_info[$app]['tables'])) // if there is already tables array, update it 750 { 751 $fnew = preg_replace('/(.*\\$'."setup_info\\[$app_pattern\\]\\['tables'\\][ \\t]*=[ \\t]*array\()[^)]*/i","\\1$tables",$fwas=$fnew); 752 753 if ($fwas == $fnew) // nothing changed => tables are in single lines 754 { 755 $fwas = explode("\n",$fwas); 756 $fnew = $prefix = ''; 757 $stage = 0; // 0 = before, 1 = in, 2 = after tables section 758 foreach($fwas as $line) 759 { 760 if (preg_match('/(.*\\$'."setup_info\\[$app_pattern\\]\\['tables'\\]\\[[ \\t]*\\][ \\t]*=[ \\t]*)'/i",$line,$parts)) 761 { 762 if ($stage == 0) // first line of tables-section 763 { 764 $stage = 1; 765 $prefix = $parts[1]; 766 } 767 } 768 else // not in table-section 769 { 770 if ($stage == 1) // first line after tables-section ==> add it 771 { 772 $tables = explode(',',$tables); 773 foreach ($tables as $table) 774 { 775 $fnew .= $prefix . $table . ";\n"; 776 } 777 $stage = 2; 778 } 779 if (strpos($line,'?>') === False) // dont write the closeing tag 780 { 781 $fnew .= $line . "\n"; 782 } 783 } 784 } 785 } 786 } 787 else // add the tables array 788 { 789 if (strstr($fnew,'?>')) // remove a closeing tag 790 { 791 $fnew = str_replace('?>','',$fnew); 792 } 793 $fnew .= "\t\$setup_info[$app_pattern]['tables'] = array($tables);\n"; 794 } 795 } 796 if (!is_writeable(EGW_SERVER_ROOT."/$app/setup") || !($f = fopen($file,'w'))) 797 { 798 return False; 799 } 800 fwrite($f,$fnew); 801 fclose($f); 802 803 return $new; 804 } 805 806 /** 807 * updates file /$app/setup/tables_update.inc.php to reflect changes in $current 808 * 809 * @param string $app app-name 810 * @param array $current new tabledefinitions 811 * @param string $version new version 812 * @return boolean True if file writen else False 813 */ 814 function update($app,$current,$version) 815 { 816 //echo "<p>etemplate.db_tools.update('$app',...,'$version')</p>\n"; 817 if (!is_writable(EGW_SERVER_ROOT."/$app/setup")) 818 { 819 return False; 820 } 821 $file_baseline = EGW_SERVER_ROOT."/$app/setup/tables_baseline.inc.php"; 822 $file_current = EGW_SERVER_ROOT."/$app/setup/tables_current.inc.php"; 823 $file_update = EGW_SERVER_ROOT."/$app/setup/tables_update.inc.php"; 824 825 if (!file_exists($file_baseline) && !copy($file_current,$file_baseline)) 826 { 827 //echo "<p>Can't copy $file_current to $file_baseline !!!</p>\n"; 828 return False; 829 } 830 $old_version = $this->setup_version($app); 831 $old_version_ = str_replace('.','_',$old_version); 832 833 if (file_exists($file_update)) 834 { 835 $f = fopen($file_update,'r'); 836 $update = fread($f,filesize($file_update)); 837 $update = str_replace('?>','',$update); 838 fclose($f); 839 $old_file = EGW_SERVER_ROOT . "/$app/setup/tables_update.old.inc.php"; 840 if (file_exists($old_file)) 841 { 842 unlink($old_file); 843 } 844 rename($file_update,$old_file); 845 } 846 else 847 { 848 $update = $this->setup_header; 849 } 850 $update .= " 851 \$test[] = '$old_version'; 852 function $app"."_upgrade$old_version_() 853 {\n"; 854 855 $update .= $this->update_schema($app,$current,$tables); 856 857 $update .= " 858 return \$GLOBALS['setup_info']['$app']['currentver'] = '$version'; 859 } 860 ?".">\n"; 861 if (!($f = fopen($file_update,'w'))) 862 { 863 //echo "<p>Cant open '$update' for writing !!!</p>\n"; 864 return False; 865 } 866 fwrite($f,$update); 867 fclose($f); 868 869 $this->setup_version($app,$version,$tables); 870 871 return True; 872 } 873 874 /** 875 * unsets all keys in an array which have a given value 876 * 877 * @param array &$arr 878 * @param mixed $val value to check against 879 */ 880 function remove_from_array(&$arr,$value) 881 { 882 foreach($arr as $key => $val) 883 { 884 if ($val == $value) 885 { 886 unset($arr[$key]); 887 } 888 } 889 } 890 891 /** 892 * creates an update-script 893 * 894 * @param string $app app-name 895 * @param array $current new table-defintion 896 * @param string &$tables returns comma delimited list of new table-names 897 * @return string the update-script 898 */ 899 function update_schema($app,$current,&$tables) 900 { 901 $this->read($app,$old); 902 903 $tables = ''; 904 foreach($old as $name => $table_def) 905 { 906 if (!isset($current[$name])) // table $name droped 907 { 908 $update .= "\t\t\$GLOBALS['egw_setup']->oProc->DropTable('$name');\n"; 909 } 910 else 911 { 912 $tables .= ($tables ? ',' : '') . "'$name'"; 913 914 $new_table_def = $table_def; 915 foreach($table_def['fd'] as $col => $col_def) 916 { 917 if (!isset($current[$name]['fd'][$col])) // column $col droped 918 { 919 if (!isset($this->changes[$name][$col]) || $this->changes[$name][$col] == '**deleted**') 920 { 921 unset($new_table_def['fd'][$col]); 922 $this->remove_from_array($new_table_def['pk'],$col); 923 $this->remove_from_array($new_table_def['fk'],$col); 924 $this->remove_from_array($new_table_def['ix'],$col); 925 $this->remove_from_array($new_table_def['uc'],$col); 926 $update .= "\t\t\$GLOBALS['egw_setup']->oProc->DropColumn('$name',"; 927 $update .= $this->write_array($new_table_def,2).",'$col');\n"; 928 } 929 else // column $col renamed 930 { 931 $new_col = $this->changes[$name][$col]; 932 $update .= "\t\t\$GLOBALS['egw_setup']->oProc->RenameColumn('$name','$col','$new_col');\n"; 933 } 934 } 935 } 936 if (is_array($this->changes[$name])) 937 { 938 foreach($this->changes[$name] as $col => $new_col) 939 { 940 if ($new_col != '**deleted**') 941 { 942 $old[$name]['fd'][$new_col] = $old[$name]['fd'][$col]; // to be able to detect further changes of the definition 943 unset($old[$name]['fd'][$col]); 944 } 945 } 946 } 947 } 948 } 949 foreach($current as $name => $table_def) 950 { 951 if (!isset($old[$name])) // table $name added 952 { 953 $tables .= ($tables ? ',' : '') . "'$name'"; 954 955 $update .= "\t\t\$GLOBALS['egw_setup']->oProc->CreateTable('$name',"; 956 $update .= $this->write_array($table_def,2).");\n"; 957 } 958 else 959 { 960 $old_norm = $this->normalize($old[$name]); 961 $new_norm = $this->normalize($table_def); 962 $old_norm_fd = $old_norm['fd']; unset($old_norm['fd']); 963 $new_norm_fd = $new_norm['fd']; unset($new_norm['fd']); 964 965 // check if the indices are changed and refresh the table if so 966 $do_refresh = serialize($old_norm) != serialize($new_norm); 967 // we comment out the Add or AlterColumn code as it is not needed, but might be useful for more complex updates 968 foreach($table_def['fd'] as $col => $col_def) 969 { 970 if (($add = !isset($old[$name]['fd'][$col])) || // column $col added 971 serialize($old_norm_fd[$col]) != serialize($new_norm_fd[$col])) // column definition altered 972 { 973 $update .= "\t\t".($do_refresh ? "/* done by RefreshTable() anyway\n\t\t" : ''). 974 "\$GLOBALS['egw_setup']->oProc->".($add ? 'Add' : 'Alter')."Column('$name','$col',"; 975 $update .= $this->write_array($col_def,2) . ');' . ($do_refresh ? '*/' : '') . "\n"; 976 } 977 } 978 if ($do_refresh) 979 { 980 $update .= "\t\t\$GLOBALS['egw_setup']->oProc->RefreshTable('$name',"; 981 $update .= $this->write_array($table_def,2).");\n"; 982 } 983 } 984 } 985 if ($this->debug) 986 { 987 echo "<p>update_schema($app, ...) =<br><pre>$update</pre>)</p>\n"; 988 } 989 return $update; 990 } 991 992 /** 993 * orders the single-colum-indices after the columns and the multicolunm ones behind 994 * 995 * @param array $index array with indices 996 * @param array $cols array with column-defs (col-name is the key) 997 * @return array the new array of indices 998 */ 999 function normalize_index($index,$cols) 1000 { 1001 $normalized = array(); 1002 foreach($cols as $col => $data) 1003 { 1004 foreach($index as $n => $idx) 1005 { 1006 if ($idx == $col || is_array($idx) && $idx[0] == $col && !isset($idx[1])) 1007 { 1008 $normalized[] = isset($idx['options']) ? $idx : $col; 1009 unset($index[$n]); 1010 break; 1011 } 1012 } 1013 } 1014 foreach($index as $idx) 1015 { 1016 $normalized[] = $idx; 1017 } 1018 return $normalized; 1019 } 1020 1021 /** 1022 * normalices all properties in a table-definiton, eg. all nullable properties to True or False 1023 * 1024 * this is necessary to compare two table-defitions 1025 * 1026 * @param array $table table-definition 1027 * @return array the normaliced defintion 1028 */ 1029 function normalize($table) 1030 { 1031 $all_props = array('type','precision','nullable','default'); 1032 1033 foreach($table['fd'] as $col => $props) 1034 { 1035 $table['fd'][$col] = array( 1036 'type' => ''.$props['type'], 1037 'precision' => 0+$props['precision'], 1038 'scale' => 0+$props['scale'], 1039 'nullable' => !isset($props['nullable']) || !!$props['nullable'], 1040 'default' => ''.$props['default'] 1041 ); 1042 } 1043 return array( 1044 'fd' => $table['fd'], 1045 'pk' => $table['pk'], 1046 'fk' => $table['fk'], 1047 'ix' => $this->normalize_index($table['ix'],$table['fd']), 1048 'uc' => $this->normalize_index($table['uc'],$table['fd']) 1049 ); 1050 } 1051 1052 /** 1053 * compares two table-definitions, by comparing normaliced string-representations (serialize) 1054 * 1055 * @param array $a 1056 * @param array $b 1057 * @return boolean true if they are identical (would create an identical schema), false otherwise 1058 * 1059 */ 1060 function tables_identical($a,$b) 1061 { 1062 $a = serialize($this->normalize($a)); 1063 $b = serialize($this->normalize($b)); 1064 1065 //echo "<p>checking if tables identical = ".($a == $b ? 'True' : 'False')."<br>\n"; 1066 //echo "a: $a<br>\nb: $b</p>\n"; 1067 1068 return $a == $b; 1069 } 1070 }
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
| Généré le : Sun Feb 25 17:20:01 2007 | par Balluche grâce à PHPXref 0.7 |