]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/upgrade.php
get db params from config/config.ini
[SourceForge/phpwiki.git] / lib / upgrade.php
1 <?php //-*-php-*-
2 rcs_id('$Id: upgrade.php,v 1.19 2004-06-19 12:19:09 rurban Exp $');
3
4 /*
5  Copyright 2004 $ThePhpWikiProgrammingTeam
6
7  This file is part of PhpWiki.
8
9  PhpWiki is free software; you can redistribute it and/or modify
10  it under the terms of the GNU General Public License as published by
11  the Free Software Foundation; either version 2 of the License, or
12  (at your option) any later version.
13
14  PhpWiki is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  GNU General Public License for more details.
18
19  You should have received a copy of the GNU General Public License
20  along with PhpWiki; if not, write to the Free Software
21  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23
24
25 /**
26  * Upgrade the WikiDB and config settings after installing a new 
27  * PhpWiki upgrade.
28  * Status: experimental, no queries for verification yet, no db update,
29  *         no merge conflict
30  *
31  * Installation on an existing PhpWiki database needs some 
32  * additional worksteps. Each step will require multiple pages.
33  *
34  * This is the plan:
35  *  1. Check for new or changed database schema and update it 
36  *     according to some predefined upgrade tables. (medium)
37  *  2. Check for new or changed (localized) pgsrc/ pages and ask 
38  *     for upgrading these. Check timestamps, upgrade silently or 
39  *     show diffs if existing. Overwrite or merge (easy)
40  *  3. Check for new or changed or deprecated index.php/config.ini settings
41  *     and help in upgrading these. (hard)
42  *  3a Convert old-style index.php into config/config.ini. (easy)
43  *  4. Check for changed plugin invocation arguments. (hard)
44  *  5. Check for changed theme variables. (hard)
45  *  6. Convert the automatic update to a class-based multi-page 
46  *     version. (hard)
47  *
48  * @author: Reini Urban
49  */
50 require_once("lib/loadsave.php");
51
52 /**
53  * TODO: check for the pgsrc_version number, not the revision
54  */
55 function doPgsrcUpdate(&$request,$pagename,$path,$filename) {
56     $dbi = $request->getDbh(); 
57     $page = $dbi->getPage($pagename);
58     if ($page->exists()) {
59         // check mtime: update automatically if pgsrc is newer
60         $rev = $page->getCurrentRevision();
61         $page_mtime = $rev->get('mtime');
62         $data  = implode("", file($path."/".$filename));
63         if (($parts = ParseMimeifiedPages($data))) {
64             usort($parts, 'SortByPageVersion');
65             reset($parts);
66             $pageinfo = $parts[0];
67             $stat  = stat($path."/".$filename);
68             $new_mtime = @$pageinfo['versiondata']['mtime'];
69             if (!$new_mtime)
70                 $new_mtime = @$pageinfo['versiondata']['lastmodified'];
71             if (!$new_mtime)
72                 $new_mtime = @$pageinfo['pagedata']['date'];
73             if (!$new_mtime)
74                 $new_mtime = $stat[9];
75             if ($new_mtime > $page_mtime) {
76                 echo "$path/$pagename: ",_("newer than the existing page."),
77                     _(" replace "),"($new_mtime &gt; $page_mtime)","<br />\n";
78                 LoadAny($request,$path."/".$filename);
79                 echo "<br />\n";
80             } else {
81                 echo "$path/$pagename: ",_("older than the existing page."),
82                     _(" skipped"),".<br />\n";
83             }
84         } else {
85             echo "$path/$pagename: ",("unknown format."),
86                     _(" skipped"),".<br />\n";
87         }
88     } else {
89         echo sprintf(_("%s does not exist"),$pagename),"<br />\n";
90         LoadAny($request,$path."/".$filename);
91         echo "<br />\n";
92     }
93 }
94
95 /** need the english filename (required precondition: urlencode == urldecode)
96  *  returns the plugin name.
97  */ 
98 function isActionPage($filename) {
99     static $special = array("DebugInfo"         => "_BackendInfo",
100                             "PhpWikiRecentChanges" => "RssFeed",
101                             "ProjectSummary"    => "RssFeed",
102                             "RecentReleases"    => "RssFeed",
103                             );
104     $base = preg_replace("/\..{1,4}$/","",basename($filename));
105     if (isset($special[$base])) return $special[$base];
106     if (FindFile("lib/plugin/".$base.".php",true)) return $base;
107     else return false;
108 }
109
110 function CheckActionPageUpdate(&$request) {
111     echo "<h3>",_("check for necessary ActionPage updates"),"</h3>\n";
112     $dbi = $request->getDbh(); 
113     $path = FindFile('pgsrc');
114     $pgsrc = new fileSet($path);
115     // most actionpages have the same name as the plugin
116     $loc_path = FindLocalizedFile('pgsrc');
117     foreach ($pgsrc->getFiles() as $filename) {
118         if (substr($filename,-1,1) == '~') continue;
119         $pagename = urldecode($filename);
120         if (isActionPage($filename)) {
121             $translation = gettext($pagename);
122             if ($translation == $pagename)
123                 doPgsrcUpdate($request, $pagename, $path, $filename);
124             elseif (FindLocalizedFile('pgsrc/'.urlencode($translation),1))
125                 doPgsrcUpdate($request, $translation, $loc_path, 
126                               urlencode($translation));
127             else
128                 doPgsrcUpdate($request, $pagename, $path, $filename);
129         }
130     }
131 }
132
133 // see loadsave.php for saving new pages.
134 function CheckPgsrcUpdate(&$request) {
135     echo "<h3>",_("check for necessary pgsrc updates"),"</h3>\n";
136     $dbi = $request->getDbh(); 
137     $path = FindLocalizedFile(WIKI_PGSRC);
138     $pgsrc = new fileSet($path);
139     // fixme: verification, ...
140     $isHomePage = false;
141     foreach ($pgsrc->getFiles() as $filename) {
142         if (substr($filename,-1,1) == '~') continue;
143         $pagename = urldecode($filename);
144         // don't ever update the HomePage
145         if (defined(HOME_PAGE))
146             if ($pagename == HOME_PAGE) $isHomePage = true;
147         else
148             if ($pagename == _("HomePage")) $isHomePage = true;
149         if ($pagename == "HomePage") $isHomePage = true;
150         if ($isHomePage) {
151             echo "$path/$pagename: ",_("always skip the HomePage."),
152                 _(" skipped"),".<br />\n";
153             $isHomePage = false;
154             continue;
155         }
156         if (!isActionPage($filename)) {
157             doPgsrcUpdate($request,$pagename,$path,$filename);
158         }
159     }
160     return;
161 }
162
163 /**
164  * TODO: Search table definition in appropriate schema
165  *       and create it.
166  * Supported: mysql and generic SQL, for ADODB and PearDB.
167  */
168 function installTable(&$dbh, $table, $backend_type) {
169     global $DBParams;
170     if (!in_array($DBParams['dbtype'],array('SQL','ADODB'))) return;
171     echo _("MISSING")," ... \n";
172     $backend = &$dbh->_backend->_dbh;
173     /*
174     $schema = findFile("schemas/${backend_type}.sql");
175     if (!$schema) {
176         echo "  ",_("FAILED"),": ",sprintf(_("no schema %s found"),"schemas/${backend_type}.sql")," ... <br />\n";
177         return false;
178     }
179     */
180     extract($dbh->_backend->_table_names);
181     $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
182     switch ($table) {
183     case 'session':
184         assert($session_tbl);
185         if ($backend_type == 'mysql') {
186             $dbh->genericQuery("
187 CREATE TABLE $session_tbl (
188         sess_id         CHAR(32) NOT NULL DEFAULT '',
189         sess_data       BLOB NOT NULL,
190         sess_date       INT UNSIGNED NOT NULL,
191         sess_ip         CHAR(15) NOT NULL,
192         PRIMARY KEY (sess_id),
193         INDEX (sess_date)
194 )");
195         } else {
196             $dbh->genericQuery("
197 CREATE TABLE $session_tbl (
198         sess_id         CHAR(32) NOT NULL DEFAULT '',
199         sess_data       ".($backend_type == 'pgsql'?'TEXT':'BLOB')." NOT NULL,
200         sess_date       INT,
201         sess_ip         CHAR(15) NOT NULL
202 )");
203             $dbh->genericQuery("CREATE UNIQUE INDEX sess_id ON $session_tbl (sess_id)");
204         }
205         $dbh->genericQuery("CREATE INDEX sess_date on session (sess_date)");
206         break;
207     case 'user':
208         $user_tbl = $prefix.'user';
209         if ($backend_type == 'mysql') {
210             $dbh->genericQuery("
211 CREATE TABLE $user_tbl (
212         userid  CHAR(48) BINARY NOT NULL UNIQUE,
213         passwd  CHAR(48) BINARY DEFAULT '',
214         PRIMARY KEY (userid)
215 )");
216         } else {
217             $dbh->genericQuery("
218 CREATE TABLE $user_tbl (
219         userid  CHAR(48) NOT NULL,
220         passwd  CHAR(48) DEFAULT ''
221 )");
222             $dbh->genericQuery("CREATE UNIQUE INDEX userid ON $user_tbl (userid)");
223         }
224         break;
225     case 'pref':
226         $pref_tbl = $prefix.'pref';
227         if ($backend_type == 'mysql') {
228             $dbh->genericQuery("
229 CREATE TABLE $pref_tbl (
230         userid  CHAR(48) BINARY NOT NULL UNIQUE,
231         prefs   TEXT NULL DEFAULT '',
232         PRIMARY KEY (userid)
233 )");
234         } else {
235             $dbh->genericQuery("
236 CREATE TABLE $pref_tbl (
237         userid  CHAR(48) NOT NULL,
238         prefs   TEXT NULL DEFAULT '',
239 )");
240             $dbh->genericQuery("CREATE UNIQUE INDEX userid ON $pref_tbl (userid)");
241         }
242         break;
243     case 'member':
244         $member_tbl = $prefix.'member';
245         if ($backend_type == 'mysql') {
246             $dbh->genericQuery("
247 CREATE TABLE $member_tbl (
248         userid    CHAR(48) BINARY NOT NULL,
249         groupname CHAR(48) BINARY NOT NULL DEFAULT 'users',
250         INDEX (userid),
251         INDEX (groupname)
252 )");
253         } else {
254             $dbh->genericQuery("
255 CREATE TABLE $member_tbl (
256         userid    CHAR(48) NOT NULL,
257         groupname CHAR(48) NOT NULL DEFAULT 'users',
258 )");
259             $dbh->genericQuery("CREATE INDEX userid ON $member_tbl (userid)");
260             $dbh->genericQuery("CREATE INDEX groupname ON $member_tbl (groupname)");
261         }
262         break;
263     case 'rating':
264         $rating_tbl = $prefix.'rating';
265         if ($backend_type == 'mysql') {
266             $dbh->genericQuery("
267 CREATE TABLE $rating_tbl (
268         dimension INT(4) NOT NULL,
269         raterpage INT(11) NOT NULL,
270         rateepage INT(11) NOT NULL,
271         ratingvalue FLOAT NOT NULL,
272         rateeversion INT(11) NOT NULL,
273         tstamp TIMESTAMP(14) NOT NULL,
274         PRIMARY KEY (dimension, raterpage, rateepage)
275 )");
276         } else {
277             $dbh->genericQuery("
278 CREATE TABLE $rating_tbl (
279         dimension INT(4) NOT NULL,
280         raterpage INT(11) NOT NULL,
281         rateepage INT(11) NOT NULL,
282         ratingvalue FLOAT NOT NULL,
283         rateeversion INT(11) NOT NULL,
284         tstamp TIMESTAMP(14) NOT NULL,
285 )");
286             $dbh->genericQuery("CREATE UNIQUE INDEX rating ON $rating_tbl (dimension, raterpage, rateepage)");
287         }
288         break;
289     }
290     echo "  ",_("CREATED"),"<br />\n";
291 }
292
293 /**
294  * currently update only session, user, pref and member
295  * jeffs-hacks database api (around 1.3.2) later
296  *   people should export/import their pages if using that old versions.
297  */
298 function CheckDatabaseUpdate(&$request) {
299     global $DBParams, $DBAuthParams;
300     if (!in_array($DBParams['dbtype'], array('SQL','ADODB'))) return;
301     echo "<h3>",_("check for necessary database updates"),"</h3>\n";
302     $dbh = &$request->_dbi;
303     $tables = $dbh->_backend->listOfTables();
304     $backend_type = $dbh->_backend->backendType();
305     $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
306     extract($dbh->_backend->_table_names);
307     foreach (explode(':','session:user:pref:member') as $table) {
308         echo sprintf(_("check for table %s"), $table)," ...";
309         if (!in_array($prefix.$table, $tables)) {
310             installTable($dbh, $table, $backend_type);
311         } else {
312             echo _("OK")," <br />\n";
313         }
314     }
315     $backend = &$dbh->_backend->_dbh;
316     // 1.3.8 added session.sess_ip
317     if (phpwiki_version() >= 1030.08 and USE_DB_SESSION and isset($request->_dbsession)) {
318         echo _("check for new session.sess_ip column")," ... ";
319         $database = $dbh->_backend->database();
320         assert(!empty($DBParams['db_session_table']));
321         $session_tbl = $prefix . $DBParams['db_session_table'];
322         $sess_fields = $dbh->_backend->listOfFields($database, $session_tbl);
323         if (!strstr(strtolower(join(':', $sess_fields)),"sess_ip")) {
324             echo "<b>",_("ADDING"),"</b>"," ... ";              
325             $dbh->genericQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL");
326         } else {
327             echo _("OK");
328         }
329         echo "<br />\n";
330     }
331     // 1.3.10 mysql requires page.id auto_increment
332     // mysql, mysqli or mysqlt
333     if (phpwiki_version() >= 1030.099 and substr($backend_type,0,5) == 'mysql') {
334         echo _("check for page.id auto_increment flag")," ...";
335         assert(!empty($page_tbl));
336         $database = $dbh->_backend->database();
337         $fields = mysql_list_fields($database, $page_tbl, $dbh->_backend->connection());
338         $columns = mysql_num_fields($fields); 
339         for ($i = 0; $i < $columns; $i++) {
340             if (mysql_field_name($fields, $i) == 'id') {
341                 $flags = mysql_field_flags($fields, $i);
342                 //FIXME: something wrong with ADODB here!
343                 if (!strstr(strtolower($flags),"auto_increment")) {
344                     echo "<b>",_("ADDING"),"</b>"," ... ";              
345                     // MODIFY col_def valid since mysql 3.22.16,
346                     // older mysql's need CHANGE old_col col_def
347                     $dbh->genericQuery("ALTER TABLE $page_tbl CHANGE id id INT NOT NULL AUTO_INCREMENT");
348                     $fields = mysql_list_fields($database, $page_tbl);
349                     if (!strstr(strtolower(mysql_field_flags($fields, $i)),"auto_increment"))
350                         echo " <b><font color=\"red\">",_("FAILED"),"</font></b><br />\n";
351                     else     
352                         echo _("OK"),"<br />\n";
353                 } else {
354                     echo _("OK"),"<br />\n";                            
355                 }
356                 break;
357             }
358         }
359         mysql_free_result($fields);
360     }
361     // check for mysql 4.1.x binary search bug
362     // http://bugs.mysql.com/bug.php?id=1491
363     // not yet tested for 4.1.2alpha, but confirmed for 4.1.0alpha
364     if (substr($backend_type,0,5) == 'mysql') {
365         echo _("check for mysql 4.1.x binary search bug")," ...";
366         $result = mysql_query("SELECT VERSION()",$dbh->_backend->connection());
367         $row = mysql_fetch_row($result);
368         $mysql_version = $row[0];
369         $arr = explode('.',$mysql_version);
370         $version = (string)(($arr[0] * 100) + $arr[1]) . "." . $arr[2];
371         if ($version > 410.0 and $version < 420.0) {
372             $dbh->genericQuery("ALTER TABLE $page_tbl CHANGE pagename pagename VARCHAR(100) NOT NULL;");
373             echo sprintf(_("version <em>%s</em> <b>FIXED</b>"), $mysql_version),"<br />\n";     
374         } else {
375             echo sprintf(_("version <em>%s</em> not affected"), $mysql_version),"<br />\n";
376         }
377     }
378     return;
379 }
380
381 function fixConfigIni($match, $new) {
382     $file = FindFile("config/config.ini");
383     $found = false;
384     if (is_writable($file)) {
385         $in = fopen($file,"rb");
386         $out = fopen($tmp = tempnam(FindFile("uploads"),"cfg"),"wb");
387         if (isWindows())
388             $tmp = str_replace("/","\\",$tmp);
389         while ($s = fgets($in)) {
390             if (preg_match($match, $s)) {
391                 $s = $new . (isWindows() ? "\r\n" : "\n");
392                 $found = true;
393             }
394             fputs($out, $s);
395         }
396         fclose($in);
397         fclose($out);
398         if (!$found) {
399             echo " <b><font color=\"red\">",_("FAILED"),"</font></b>: ",
400                 sprintf(_("%s not found"), $match);
401             unlink($out);
402         } else {
403             @unlink("$file.bak");
404             @rename($file,"$file.bak");
405             if (rename($tmp, $file))
406                 echo " <b>",_("FIXED"),"</b>";
407             else {
408                 echo " <b>",_("FAILED"),"</b>: ";
409                 sprintf(_("couldn't move %s to %s"), $tmp, $file);
410                 return false;
411             }
412         }
413         return $found;
414     } else {
415         echo " <b><font color=\"red\">",_("FAILED"),"</font></b>: ",
416             sprintf(_("%s is not writable"), $file);
417         return false;
418     }
419 }
420
421 function CheckConfigUpdate(&$request) {
422     echo "<h3>",_("check for necessary config updates"),"</h3>\n";
423     echo _("check for old CACHE_CONTROL = NONE")," ... ";
424     if (defined('CACHE_CONTROL') and CACHE_CONTROL == '') {
425         echo "<br />&nbsp;&nbsp;",_("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'")," ...";
426         fixConfigIni("/^\s*CACHE_CONTROL\s*=\s*NONE/","CACHE_CONTROL = NO_CACHE");
427     } else {
428         echo _("OK");
429     }
430     echo "<br />\n";
431 }
432
433 /**
434  * TODO:
435  *
436  * Upgrade: Base class for multipage worksteps
437  * identify, validate, display options, next step
438  */
439 class Upgrade {
440 }
441
442 class Upgrade_CheckPgsrc extends Upgrade {
443 }
444
445 class Upgrade_CheckDatabaseUpdate extends Upgrade {
446 }
447
448 // TODO: At which step are we? 
449 // validate and do it again or go on with next step.
450
451 /** entry function from lib/main.php
452  */
453 function DoUpgrade($request) {
454
455     if (!$request->_user->isAdmin()) {
456         $request->_notAuthorized(WIKIAUTH_ADMIN);
457         $request->finish(
458                          HTML::div(array('class' => 'disabled-plugin'),
459                                    fmt("Upgrade disabled: user != isAdmin")));
460         return;
461     }
462
463     StartLoadDump($request, _("Upgrading this PhpWiki"));
464     CheckActionPageUpdate($request);
465     CheckDatabaseUpdate($request);
466     CheckPgsrcUpdate($request);
467     //CheckThemeUpdate($request);
468     CheckConfigUpdate($request);
469     EndLoadDump($request);
470 }
471
472
473 /**
474  $Log: not supported by cvs2svn $
475  Revision 1.18  2004/06/19 11:47:17  rurban
476  added CheckConfigUpdate: CACHE_CONTROL = NONE => NO_CACHE
477
478  Revision 1.17  2004/06/17 11:31:50  rurban
479  check necessary localized actionpages
480
481  Revision 1.16  2004/06/16 10:38:58  rurban
482  Disallow refernces in calls if the declaration is a reference
483  ("allow_call_time_pass_reference clean").
484    PhpWiki is now allow_call_time_pass_reference = Off clean,
485    but several external libraries may not.
486    In detail these libs look to be affected (not tested):
487    * Pear_DB odbc
488    * adodb oracle
489
490  Revision 1.15  2004/06/07 19:50:40  rurban
491  add owner field to mimified dump
492
493  Revision 1.14  2004/06/07 18:38:18  rurban
494  added mysql 4.1.x search fix
495
496  Revision 1.13  2004/06/04 20:32:53  rurban
497  Several locale related improvements suggested by Pierrick Meignen
498  LDAP fix by John Cole
499  reanable admin check without ENABLE_PAGEPERM in the admin plugins
500
501  Revision 1.12  2004/05/18 13:59:15  rurban
502  rename simpleQuery to genericQuery
503
504  Revision 1.11  2004/05/15 13:06:17  rurban
505  skip the HomePage, at first upgrade the ActionPages, then the database, then the rest
506
507  Revision 1.10  2004/05/15 01:19:41  rurban
508  upgrade prefix fix by Kai Krakow
509
510  Revision 1.9  2004/05/14 11:33:03  rurban
511  version updated to 1.3.11pre
512  upgrade stability fix
513
514  Revision 1.8  2004/05/12 10:49:55  rurban
515  require_once fix for those libs which are loaded before FileFinder and
516    its automatic include_path fix, and where require_once doesn't grok
517    dirname(__FILE__) != './lib'
518  upgrade fix with PearDB
519  navbar.tmpl: remove spaces for IE &nbsp; button alignment
520
521  Revision 1.7  2004/05/06 17:30:38  rurban
522  CategoryGroup: oops, dos2unix eol
523  improved phpwiki_version:
524    pre -= .0001 (1.3.10pre: 1030.099)
525    -p1 += .001 (1.3.9-p1: 1030.091)
526  improved InstallTable for mysql and generic SQL versions and all newer tables so far.
527  abstracted more ADODB/PearDB methods for action=upgrade stuff:
528    backend->backendType(), backend->database(),
529    backend->listOfFields(),
530    backend->listOfTables(),
531
532  Revision 1.6  2004/05/03 15:05:36  rurban
533  + table messages
534
535  Revision 1.4  2004/05/02 21:26:38  rurban
536  limit user session data (HomePageHandle and auth_dbi have to invalidated anyway)
537    because they will not survive db sessions, if too large.
538  extended action=upgrade
539  some WikiTranslation button work
540  revert WIKIAUTH_UNOBTAINABLE (need it for main.php)
541  some temp. session debug statements
542
543  Revision 1.3  2004/04/29 22:33:30  rurban
544  fixed sf.net bug #943366 (Kai Krakow)
545    couldn't load localized url-undecoded pagenames
546
547  Revision 1.2  2004/03/12 15:48:07  rurban
548  fixed explodePageList: wrong sortby argument order in UnfoldSubpages
549  simplified lib/stdlib.php:explodePageList
550
551  */
552
553 // For emacs users
554 // Local Variables:
555 // mode: php
556 // tab-width: 8
557 // c-basic-offset: 4
558 // c-hanging-comment-ender-p: nil
559 // indent-tabs-mode: nil
560 // End:
561 ?>