4 * Copyright 2004,2005,2006,2007 $ThePhpWikiProgrammingTeam
5 * Copyright 2008 Marc-Etienne Vargenau, Alcatel-Lucent
7 * This file is part of PhpWiki.
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.
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.
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
25 * Upgrade existing WikiDB and config settings after installing a new PhpWiki sofwtare version.
26 * Status: almost no queries for verification.
27 * simple merge conflict resolution, or Overwrite All.
29 * Installation on an existing PhpWiki database needs some
30 * additional worksteps. Each step will require multiple pages.
33 * 1. Check for new or changed database schema and update it
34 * according to some predefined upgrade tables. (medium, complete)
35 * 2. Check for new or changed (localized) pgsrc/ pages and ask
36 * for upgrading these. Check timestamps, upgrade silently or
37 * show diffs if existing. Overwrite or merge (easy, complete)
38 * 3. Check for new or changed or deprecated index.php/config.ini settings
39 * and help in upgrading these. (for newer changes since 1.3.11, not yet)
40 * 3a. Convert old-style index.php into config/config.ini. (easy, not yet)
41 * 4. Check for changed plugin invocation arguments. (medium, done)
42 * 5. Check for changed theme variables. (hard, not yet)
43 * 6. Convert the single-request upgrade to a class-based multi-page
46 * Done: overwrite=1 link on edit conflicts at first occurence "Overwrite all".
48 * @author: Reini Urban
50 require_once("lib/loadsave.php");
54 function Upgrade (&$request) {
55 $this->request =& $request;
56 $this->dbi =& $request->_dbi; // no reference for dbadmin ?
57 $this->phpwiki_version = $this->current_db_version = phpwiki_version();
58 //$this->current_db_version = 1030.13; // should be stored in the db. should be phpwiki_version
60 $this->db_version = $this->dbi->get_db_version();
61 $this->isSQL = $this->dbi->_backend->isSQL();
65 * TODO: check for the pgsrc_version number, not the revision mtime only
67 function doPgsrcUpdate($pagename, $path, $filename) {
68 // don't ever update the HomePage
69 if ((defined(HOME_PAGE) and ($pagename == HOME_PAGE))
70 or ($pagename == _("HomePage"))
71 or ($pagename == "HomePage")) {
72 echo "$path/$pagename: ",_("always skip the HomePage."),
73 _(" skipped"),".<br />\n";
77 $page = $this->dbi->getPage($pagename);
78 if ($page->exists()) {
79 // check mtime: update automatically if pgsrc is newer
80 $rev = $page->getCurrentRevision();
81 $page_mtime = $rev->get('mtime');
82 $data = implode("", file($path."/".$filename));
83 if (($parts = ParseMimeifiedPages($data))) {
84 usort($parts, 'SortByPageVersion');
86 $pageinfo = $parts[0];
87 $stat = stat($path."/".$filename);
89 if (isset($pageinfo['versiondata']['mtime']))
90 $new_mtime = $pageinfo['versiondata']['mtime'];
91 if (!$new_mtime and isset($pageinfo['versiondata']['lastmodified']))
92 $new_mtime = $pageinfo['versiondata']['lastmodified'];
93 if (!$new_mtime and isset($pageinfo['pagedata']['date']))
94 $new_mtime = $pageinfo['pagedata']['date'];
96 $new_mtime = $stat[9];
97 if ($new_mtime > $page_mtime) {
98 echo "$path/$pagename: ",_("newer than the existing page."),
99 _(" replace "),"($new_mtime > $page_mtime)","<br />\n";
100 LoadAny($this->request, $path."/".$filename);
103 echo "$path/$pagename: ",_("older than the existing page."),
104 _(" skipped"),".<br />\n";
107 echo "$path/$pagename: ",("unknown format."),
108 _(" skipped"),".<br />\n";
111 echo sprintf(_("%s does not exist"),$pagename),"<br />\n";
112 LoadAny($this->request, $path."/".$filename);
117 function CheckActionPageUpdate() {
118 echo "<h3>",sprintf(_("check for necessary %s updates"),
119 _("ActionPage")),"</h3>\n";
120 // 1.3.13 before we pull in all missing pages, we rename existing ones
121 $this->_rename_page_helper(_("_AuthInfo"), _("DebugAuthInfo"));
122 // this is in some templates. so we keep the old name
123 //$this->_rename_page_helper($this->dbi, _("DebugInfo"), _("DebugBackendInfo"));
124 $this->_rename_page_helper(_("_GroupInfo"), _("GroupAuthInfo")); //never officially existed
125 $this->_rename_page_helper("InterWikiKarte", "InterWikiListe"); // german only
127 $path = FindFile('pgsrc');
128 $pgsrc = new fileSet($path);
129 // most actionpages have the same name as the plugin
130 $loc_path = FindLocalizedFile('pgsrc');
131 foreach ($pgsrc->getFiles() as $filename) {
132 if (substr($filename,-1,1) == '~') continue;
133 if (substr($filename,-5,5) == '.orig') continue;
134 $pagename = urldecode($filename);
135 if (isActionPage($filename)) {
136 $translation = gettext($pagename);
137 if ($translation == $pagename)
138 $this->doPgsrcUpdate($pagename, $path, $filename);
139 elseif (FindLocalizedFile('pgsrc/'.urlencode($translation),1))
140 $this->doPgsrcUpdate($translation, $loc_path, urlencode($translation));
142 $this->doPgsrcUpdate($pagename, $path, $filename);
147 // see loadsave.php for saving new pages.
148 function CheckPgsrcUpdate() {
149 echo "<h3>",sprintf(_("check for necessary %s updates"),
151 if ($this->db_version < 1030.12200612) {
152 echo "<h4>",_("rename to Help: pages"),"</h4>\n";
154 $path = FindLocalizedFile(WIKI_PGSRC);
155 $pgsrc = new fileSet($path);
156 // fixme: verification, ...
157 foreach ($pgsrc->getFiles() as $filename) {
158 if (substr($filename,-1,1) == '~') continue;
159 if (substr($filename,-5,5) == '.orig') continue;
160 $pagename = urldecode($filename);
161 if (!isActionPage($filename)) {
162 // There're a lot of now unneeded pages around.
163 // At first rename the BlaPlugin pages to Help/<pagename> and then to the update.
164 if ($this->db_version < 1030.12200612) {
165 $this->_rename_to_help_page($pagename);
167 $this->doPgsrcUpdate($pagename,$path,$filename);
171 // Now check some theme specific pgsrc files (blog, wikilens, custom).
173 $path = $WikiTheme->file("pgsrc");
174 // TBD: the call to fileSet prints a warning:
175 // Notice: Unable to open directory 'themes/MonoBook/pgsrc' for reading
176 $pgsrc = new fileSet($path);
177 if ($pgsrc->getFiles()) {
178 echo "<h3>",sprintf(_("check for additional theme %s updates"),
180 foreach ($pgsrc->getFiles() as $filename) {
181 if (substr($filename,-1,1) == '~') continue;
182 if (substr($filename,-5,5) == '.orig') continue;
183 $pagename = urldecode($filename);
184 $this->doPgsrcUpdate($pagename,$path,$filename);
190 function _rename_page_helper($oldname, $pagename) {
191 echo sprintf(_("rename %s to %s"), $oldname, $pagename)," ...";
192 if ($this->dbi->isWikiPage($oldname) and !$this->dbi->isWikiPage($pagename)) {
193 if ($this->dbi->_backend->rename_page($oldname, $pagename))
194 echo _("OK")," <br />\n";
196 echo " <b><font color=\"red\">", _("FAILED"), "</font></b>",
199 echo _(" skipped")," <br />\n";
203 function _rename_to_help_page($pagename) {
204 $newprefix = _("Help") . "/";
205 if (substr($pagename,0,strlen($newprefix)) != $newprefix) return;
206 $oldname = substr($pagename,strlen($newprefix));
207 $this->_rename_page_helper($oldname, $pagename);
211 * TODO: Search table definition in appropriate schema
213 * Supported: mysql and generic SQL, for ADODB and PearDB.
215 function installTable($table, $backend_type) {
217 if (!$this->isSQL) return;
218 echo _("MISSING")," ... \n";
219 $backend = &$this->dbi->_backend->_dbh;
221 $schema = findFile("schemas/${backend_type}.sql");
223 echo " ",_("FAILED"),": ",sprintf(_("no schema %s found"),
224 "schemas/${backend_type}.sql")," ... <br />\n";
228 extract($this->dbi->_backend->_table_names);
229 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
232 assert($session_tbl);
233 if ($backend_type == 'mysql') {
234 $this->dbi->genericSqlQuery("
235 CREATE TABLE $session_tbl (
236 sess_id CHAR(32) NOT NULL DEFAULT '',
237 sess_data BLOB NOT NULL,
238 sess_date INT UNSIGNED NOT NULL,
239 sess_ip CHAR(15) NOT NULL,
240 PRIMARY KEY (sess_id),
244 $this->dbi->genericSqlQuery("
245 CREATE TABLE $session_tbl (
246 sess_id CHAR(32) NOT NULL DEFAULT '',
247 sess_data ".($backend_type == 'pgsql'?'TEXT':'BLOB')." NOT NULL,
249 sess_ip CHAR(15) NOT NULL
251 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX sess_id ON $session_tbl (sess_id)");
253 $this->dbi->genericSqlQuery("CREATE INDEX sess_date on session (sess_date)");
254 echo " ",_("CREATED");
257 $pref_tbl = $prefix.'pref';
258 if ($backend_type == 'mysql') {
259 $this->dbi->genericSqlQuery("
260 CREATE TABLE $pref_tbl (
261 userid CHAR(48) BINARY NOT NULL UNIQUE,
262 prefs TEXT NULL DEFAULT '',
266 $this->dbi->genericSqlQuery("
267 CREATE TABLE $pref_tbl (
268 userid CHAR(48) NOT NULL,
269 prefs TEXT NULL DEFAULT ''
271 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX userid ON $pref_tbl (userid)");
273 echo " ",_("CREATED");
276 $member_tbl = $prefix.'member';
277 if ($backend_type == 'mysql') {
278 $this->dbi->genericSqlQuery("
279 CREATE TABLE $member_tbl (
280 userid CHAR(48) BINARY NOT NULL,
281 groupname CHAR(48) BINARY NOT NULL DEFAULT 'users',
286 $this->dbi->genericSqlQuery("
287 CREATE TABLE $member_tbl (
288 userid CHAR(48) NOT NULL,
289 groupname CHAR(48) NOT NULL DEFAULT 'users'
291 $this->dbi->genericSqlQuery("CREATE INDEX userid ON $member_tbl (userid)");
292 $this->dbi->genericSqlQuery("CREATE INDEX groupname ON $member_tbl (groupname)");
294 echo " ",_("CREATED");
297 $rating_tbl = $prefix.'rating';
298 if ($backend_type == 'mysql') {
299 $this->dbi->genericSqlQuery("
300 CREATE TABLE $rating_tbl (
301 dimension INT(4) NOT NULL,
302 raterpage INT(11) NOT NULL,
303 rateepage INT(11) NOT NULL,
304 ratingvalue FLOAT NOT NULL,
305 rateeversion INT(11) NOT NULL,
306 tstamp TIMESTAMP(14) NOT NULL,
307 PRIMARY KEY (dimension, raterpage, rateepage)
310 $this->dbi->genericSqlQuery("
311 CREATE TABLE $rating_tbl (
312 dimension INT(4) NOT NULL,
313 raterpage INT(11) NOT NULL,
314 rateepage INT(11) NOT NULL,
315 ratingvalue FLOAT NOT NULL,
316 rateeversion INT(11) NOT NULL,
317 tstamp TIMESTAMP(14) NOT NULL
319 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX rating"
320 ." ON $rating_tbl (dimension, raterpage, rateepage)");
322 echo " ",_("CREATED");
325 $log_tbl = $prefix.'accesslog';
326 // fields according to http://www.outoforder.cc/projects/apache/mod_log_sql/docs-2.0/#id2756178
328 A User Agent agent varchar(255) Mozilla/4.0 (compat; MSIE 6.0; Windows)
329 a CGi request arguments request_args varchar(255) user=Smith&cart=1231&item=532
330 b Bytes transfered bytes_sent int unsigned 32561
331 c??? Text of cookie cookie varchar(255) Apache=sdyn.fooonline.net 1300102700823
332 f Local filename requested request_file varchar(255) /var/www/html/books-cycroad.html
333 H HTTP request_protocol request_protocol varchar(10) HTTP/1.1
334 h Name of remote host remote_host varchar(50) blah.foobar.com
335 I Request ID (from modd_unique_id) id char(19) POlFcUBRH30AAALdBG8
336 l Ident user info remote_logname varcgar(50) bobby
337 M Machine ID??? machine_id varchar(25) web01
338 m HTTP request method request_method varchar(10) GET
339 P httpd cchild PID child_pid smallint unsigned 3215
340 p http port server_port smallint unsigned 80
341 R Referer referer varchar(255) http://www.biglinks4u.com/linkpage.html
342 r Request in full form request_line varchar(255) GET /books-cycroad.html HTTP/1.1
343 S Time of request in UNIX time_t format time_stamp int unsigned 1005598029
344 T Seconds to service request request_duration smallint unsigned 2
345 t Time of request in human format request_time char(28) [02/Dec/2001:15:01:26 -0800]
346 U Request in simple form request_uri varchar(255) /books-cycroad.html
347 u User info from HTTP auth remote_user varchar(50) bobby
348 v Virtual host servicing the request virtual_host varchar(255)
350 $this->dbi->genericSqlQuery("
351 CREATE TABLE $log_tbl (
352 time_stamp int unsigned,
353 remote_host varchar(100),
354 remote_user varchar(50),
355 request_method varchar(10),
356 request_line varchar(255),
357 request_args varchar(255),
358 request_uri varchar(255),
359 request_time char(28),
360 status smallint unsigned,
361 bytes_sent smallint unsigned,
362 referer varchar(255),
364 request_duration float
366 $this->dbi->genericSqlQuery("CREATE INDEX log_time ON $log_tbl (time_stamp)");
367 $this->dbi->genericSqlQuery("CREATE INDEX log_host ON $log_tbl (remote_host)");
368 echo " ",_("CREATED");
375 * Update from ~1.3.4 to current.
376 * tables: Only session, user, pref and member
377 * jeffs-hacks database api (around 1.3.2) later:
378 * people should export/import their pages if using that old versions.
380 function CheckDatabaseUpdate() {
381 global $DBAuthParams, $DBParams;
383 echo "<h3>",sprintf(_("check for necessary %s updates"),
385 " - ", DATABASE_TYPE,"</h3>\n";
386 $dbadmin = $this->request->getArg('dbadmin');
389 if (isset($dbadmin['cancel'])) {
390 echo _("CANCEL")," <br />\n";
394 echo "db version: we want ", $this->current_db_version, "\n<br />";
395 echo "db version: we have ", $this->db_version, "\n<br />";
396 if ($this->db_version >= $this->current_db_version) {
397 echo _("OK"), "<br />\n";
401 $backend_type = $this->dbi->_backend->backendType();
403 echo "<h4>",_("Backend type: "),$backend_type,"</h4>\n";
404 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
405 $tables = $this->dbi->_backend->listOfTables();
406 foreach (explode(':','session:pref:member') as $table) {
407 echo sprintf(_("check for table %s"), $table)," ...";
408 if (!in_array($prefix.$table, $tables)) {
409 $this->installTable($table, $backend_type);
411 echo _("OK")," <br />\n";
416 if ($this->phpwiki_version >= 1030.12200612 and $this->db_version < 1030.13) {
417 if ($this->isSQL and preg_match("/(pgsql|postgres)/", $backend_type)) {
418 trigger_error("You need to upgrade to schema/psql-initialize.sql manually!",
420 // $this->_upgrade_psql_tsearch2();
422 $this->_upgrade_relation_links();
425 if (ACCESS_LOG_SQL and $this->isSQL) {
426 $table = "accesslog";
427 echo sprintf(_("check for table %s"), $table)," ...";
428 if (!in_array($prefix.$table, $tables)) {
429 $this->installTable($table, $backend_type);
431 echo _("OK")," <br />\n";
434 if ($this->isSQL and (class_exists("RatingsUserFactory") or $this->dbi->isWikiPage(_("RateIt")))) {
436 echo sprintf(_("check for table %s"), $table)," ...";
437 if (!in_array($prefix.$table, $tables)) {
438 $this->installTable($table, $backend_type);
440 echo _("OK")," <br />\n";
443 $backend = &$this->dbi->_backend->_dbh;
445 extract($this->dbi->_backend->_table_names);
447 // 1.3.8 added session.sess_ip
448 if ($this->isSQL and $this->phpwiki_version >= 1030.08 and USE_DB_SESSION
449 and isset($this->request->_dbsession))
451 echo _("check for new session.sess_ip column")," ... ";
452 $database = $this->dbi->_backend->database();
453 assert(!empty($DBParams['db_session_table']));
454 $session_tbl = $prefix . $DBParams['db_session_table'];
455 $sess_fields = $this->dbi->_backend->listOfFields($database, $session_tbl);
458 } elseif (!strstr(strtolower(join(':', $sess_fields)), "sess_ip")) {
459 // TODO: postgres test (should be able to add columns at the end, but not in between)
460 echo "<b>",_("ADDING"),"</b>"," ... ";
461 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL");
462 $this->dbi->genericSqlQuery("CREATE INDEX sess_date ON $session_tbl (sess_date)");
467 if (substr($backend_type,0,5) == 'mysql') {
468 // upgrade to 4.1.8 destroyed my session table:
469 // sess_id => varchar(10), sess_data => varchar(5). For others obviously also.
470 echo _("check for mysql session.sess_id sanity")," ... ";
471 $result = $this->dbi->genericSqlQuery("DESCRIBE $session_tbl");
472 if (DATABASE_TYPE == 'SQL') {
473 $iter = new WikiDB_backend_PearDB_generic_iter($backend, $result);
474 } elseif (DATABASE_TYPE == 'ADODB') {
475 $iter = new WikiDB_backend_ADODB_generic_iter($backend, $result,
476 array("Field", "Type", "Null", "Key", "Default", "Extra"));
477 } elseif (DATABASE_TYPE == 'PDO') {
478 $iter = new WikiDB_backend_PDO_generic_iter($backend, $result);
480 while ($col = $iter->next()) {
481 if ($col["Field"] == 'sess_id' and !strstr(strtolower($col["Type"]), 'char(32)')) {
482 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_id"
483 ." sess_id CHAR(32) NOT NULL");
484 echo "sess_id ", $col["Type"], " ", _("fixed"), " => CHAR(32) ";
486 if ($col["Field"] == 'sess_ip' and !strstr(strtolower($col["Type"]), 'char(15)')) {
487 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_ip"
488 ." sess_ip CHAR(15) NOT NULL");
489 echo "sess_ip ", $col["Type"], " ", _("fixed"), " => CHAR(15) ";
492 echo _("OK"), "<br />\n";
497 ALTER TABLE link ADD relation INT DEFAULT 0;
498 CREATE INDEX linkrelation ON link (relation);
501 // mysql >= 4.0.4 requires LOCK TABLE privileges
502 if (substr($backend_type,0,5) == 'mysql') {
503 echo _("check for mysql LOCK TABLE privilege")," ...";
504 $mysql_version = $this->dbi->_backend->_serverinfo['version'];
505 if ($mysql_version > 400.40) {
506 if (!empty($this->dbi->_backend->_parsedDSN))
507 $parseDSN = $this->dbi->_backend->_parsedDSN;
508 elseif (function_exists('parseDSN')) // ADODB or PDO
509 $parseDSN = parseDSN($DBParams['dsn']);
511 $parseDSN = DB::parseDSN($DBParams['dsn']);
512 $username = $this->dbi->_backend->qstr($parseDSN['username']);
514 $query = "SELECT lock_tables_priv FROM mysql.db WHERE user='$username'";
515 //mysql_select_db("mysql", $this->dbi->_backend->connection());
516 $db_fields = $this->dbi->_backend->listOfFields("mysql", "db");
517 if (!strstr(strtolower(join(':', $db_fields)), "lock_tables_priv")) {
518 echo join(':', $db_fields);
519 die("lock_tables_priv missing. The DB Admin must run mysql_fix_privilege_tables");
521 $row = $this->dbi->_backend->getRow($query);
522 if (isset($row[0]) and $row[0] == 'N') {
523 $this->dbi->genericSqlQuery("UPDATE mysql.db SET lock_tables_priv='Y'"
524 ." WHERE mysql.user='$username'");
525 $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
526 echo "mysql.db user='$username'", _("fixed"), "<br />\n";
529 $query = "SELECT lock_tables_priv FROM mysql.user WHERE user='$username'";
530 $row = $this->dbi->_backend->getRow($query);
531 if ($row and $row[0] == 'N') {
532 $this->dbi->genericSqlQuery("UPDATE mysql.user SET lock_tables_priv='Y'"
533 ." WHERE mysql.user='$username'");
534 $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
535 echo "mysql.user user='$username'", _("fixed"), "<br />\n";
537 echo " <b><font color=\"red\">", _("FAILED"), "</font></b>: ",
538 "Neither mysql.db nor mysql.user has a user='$username'"
539 ." or the lock_tables_priv field",
542 echo _("OK"), "<br />\n";
545 echo _("OK"), "<br />\n";
547 //mysql_select_db($this->dbi->_backend->database(), $this->dbi->_backend->connection());
549 echo sprintf(_("version <em>%s</em> not affected"), $mysql_version),"<br />\n";
553 // 1.3.10 mysql requires page.id auto_increment
554 // mysql, mysqli or mysqlt
555 if ($this->phpwiki_version >= 1030.099 and substr($backend_type,0,5) == 'mysql'
556 and DATABASE_TYPE != 'PDO')
558 echo _("check for mysql page.id auto_increment flag")," ...";
559 assert(!empty($page_tbl));
560 $database = $this->dbi->_backend->database();
561 $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
562 $columns = mysql_num_fields($fields);
563 for ($i = 0; $i < $columns; $i++) {
564 if (mysql_field_name($fields, $i) == 'id') {
565 $flags = mysql_field_flags($fields, $i);
566 //DONE: something was wrong with ADODB here.
567 if (!strstr(strtolower($flags), "auto_increment")) {
568 echo "<b>",_("ADDING"),"</b>"," ... ";
569 // MODIFY col_def valid since mysql 3.22.16,
570 // older mysql's need CHANGE old_col col_def
571 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE id"
572 ." id INT NOT NULL AUTO_INCREMENT");
573 $fields = mysql_list_fields($database, $page_tbl);
574 if (!strstr(strtolower(mysql_field_flags($fields, $i)), "auto_increment"))
575 echo " <b><font color=\"red\">", _("FAILED"), "</font></b><br />\n";
577 echo _("OK"), "<br />\n";
579 echo _("OK"), "<br />\n";
584 mysql_free_result($fields);
587 // Check for mysql 4.1.x/5.0.0a binary search problem.
588 // http://bugs.mysql.com/bug.php?id=4398
589 // "select * from page where LOWER(pagename) like '%search%'" does not apply LOWER!
590 // Confirmed for 4.1.0alpha,4.1.3-beta,5.0.0a; not yet tested for 4.1.2alpha,
591 // On windows only, though utf8 would be useful elsewhere also.
592 // Illegal mix of collations (latin1_bin,IMPLICIT) and
593 // (utf8_general_ci, COERCIBLE) for operation '='])
594 if (isWindows() and substr($backend_type,0,5) == 'mysql') {
595 echo _("check for mysql 4.1.x/5.0.0 binary search on windows problem")," ...";
596 $mysql_version = $this->dbi->_backend->_serverinfo['version'];
597 if ($mysql_version < 401.0) {
598 echo sprintf(_("version <em>%s</em>"), $mysql_version)," ",
599 _("not affected"),"<br />\n";
600 } elseif ($mysql_version >= 401.6) { // FIXME: since which version?
601 $row = $this->dbi->_backend->getRow("SHOW CREATE TABLE $page_tbl");
602 $result = join(" ", $row);
603 if (strstr(strtolower($result), "character set")
604 and strstr(strtolower($result), "collate"))
606 echo _("OK"), "<br />\n";
608 //SET CHARACTER SET latin1
610 if ($charset == 'iso-8859-1') $charset = 'latin1';
611 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename "
612 ."pagename VARCHAR(100) "
613 ."CHARACTER SET '$charset' COLLATE '$charset"."_bin' NOT NULL");
614 echo sprintf(_("version <em>%s</em>"), $mysql_version),
615 " <b>",_("FIXED"),"</b>",
618 } elseif (DATABASE_TYPE != 'PDO') {
619 // check if already fixed
620 extract($this->dbi->_backend->_table_names);
621 assert(!empty($page_tbl));
622 $database = $this->dbi->_backend->database();
623 $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
624 $columns = mysql_num_fields($fields);
625 for ($i = 0; $i < $columns; $i++) {
626 if (mysql_field_name($fields, $i) == 'pagename') {
627 $flags = mysql_field_flags($fields, $i);
628 // I think it was fixed with 4.1.6, but I tested it only with 4.1.8
629 if ($mysql_version > 401.0 and $mysql_version < 401.6) {
630 // remove the binary flag
631 if (strstr(strtolower($flags), "binary")) {
632 // FIXME: on duplicate pagenames this will fail!
633 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename"
634 ." pagename VARCHAR(100) NOT NULL");
635 echo sprintf(_("version <em>%s</em>"), $mysql_version),
636 "<b>",_("FIXED"),"</b>"
645 if ($this->isSQL and ACCESS_LOG_SQL & 2) {
646 echo _("check for ACCESS_LOG_SQL passwords in POST requests")," ...";
647 // Don't display passwords in POST requests (up to 2005-02-04 12:03:20)
648 $res = $this->dbi->genericSqlIter("SELECT time_stamp, remote_host, " .
649 "request_args FROM ${prefix}accesslog WHERE request_args LIKE " .
650 "'%s:6:\"passwd\"%' AND request_args NOT LIKE '%s:6:\"passwd\";" .
651 "s:15:\"<not displayed>\"%'");
653 while ($row = $res->next()) {
654 $args = preg_replace("/(s:6:\"passwd\";s:15:\").*(\")/",
655 "$1<not displayed>$2", $row["request_args"]);
656 $ts = $row["time_stamp"];
657 $rh = $row["remote_host"];
658 $this->dbi->genericSqlQuery("UPDATE ${prefix}accesslog SET " .
659 "request_args='$args' WHERE time_stamp=$ts AND " .
660 "remote_host='$rh'");
664 echo "<b>",_("FIXED"),"</b>", "<br />\n";
666 echo _("OK"),"<br />\n";
668 if ($this->phpwiki_version >= 1030.13) {
669 echo _("check for ACCESS_LOG_SQL remote_host varchar(50)")," ...";
670 $database = $this->dbi->_backend->database();
671 $accesslog_tbl = $prefix . 'accesslog';
672 $fields = $this->dbi->_backend->listOfFields($database, $accesslog_tbl);
675 } elseif (strstr(strtolower(join(':', $sess_fields)), "remote_host")) {
676 // TODO: how to check size, already done?
677 echo "<b>",_("FIXING"),"remote_host</b>"," ... ";
678 $this->dbi->genericSqlQuery("ALTER TABLE $accesslog_tbl CHANGE remote_host VARCHAR(100)");
685 $this->_upgrade_cached_html();
687 if ($this->db_version < $this->current_db_version) {
688 $this->dbi->set_db_version($this->current_db_version);
689 $this->db_version = $this->dbi->get_db_version();
690 echo "db version: upgrade to ", $this->db_version," ";
691 echo _("OK"), "<br />\n";
699 * Filter SQL missing permissions errors.
701 * A wrong DBADMIN user will not be able to connect
702 * @see _is_false_error, ErrorManager
705 function _dbpermission_filter($err) {
706 if ( $err->isWarning() ) {
707 global $ErrorManager;
708 $this->error_caught = 1;
709 $ErrorManager->_postponed_errors[] = $err;
715 function _try_dbadmin_user ($user, $passwd) {
716 global $DBParams, $DBAuthParams;
717 $AdminParams = $DBParams;
718 if (DATABASE_TYPE == 'SQL')
719 $dsn = DB::parseDSN($AdminParams['dsn']);
721 $dsn = parseDSN($AdminParams['dsn']);
723 $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
729 $AdminParams['_tryroot_from_upgrade'] = 1;
730 // add error handler to warn about missing permissions for DBADMIN_USER
731 global $ErrorManager;
732 $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_dbpermission_filter'));
733 $this->error_caught = 0;
734 $this->dbi = WikiDB::open($AdminParams);
735 if (!$this->error_caught) return true;
736 // FAILED: redo our connection with the wikiuser
737 $this->dbi = WikiDB::open($DBParams);
738 $ErrorManager->flushPostponedErrors();
739 $ErrorManager->popErrorHandler();
743 function _db_init () {
744 if (!$this->isSQL) return;
746 /* SQLite never needs admin params */
747 $backend_type = $this->dbi->_backend->backendType();
748 if (substr($backend_type,0,6)=="sqlite") {
751 $dbadmin_user = 'root';
752 if ($dbadmin = $this->request->getArg('dbadmin')) {
753 $dbadmin_user = $dbadmin['user'];
754 if (isset($dbadmin['cancel'])) {
756 } elseif (!empty($dbadmin_user)) {
757 if ($this->_try_dbadmin_user($dbadmin['user'], $dbadmin['passwd']))
760 } elseif (DBADMIN_USER) {
761 if ($this->_try_dbadmin_user(DBADMIN_USER, DBADMIN_PASSWD))
764 // Check if the privileges are enough. Need CREATE and ALTER perms.
765 // And on windows: SELECT FROM mysql, possibly: UPDATE mysql.
766 $form = HTML::form(array("method" => "post",
767 "action" => $this->request->getPostURL(),
768 "accept-charset"=>$GLOBALS['charset']),
769 HTML::p(_("Upgrade requires database privileges to CREATE and ALTER the phpwiki database."),
771 _("And on windows at least the privilege to SELECT FROM mysql, and possibly UPDATE mysql")),
772 HiddenInputs(array('action' => 'upgrade',
773 'overwrite' => $this->request->getArg('overwrite'))),
774 HTML::table(array("cellspacing"=>4),
775 HTML::tr(HTML::td(array('align'=>'right'),
776 _("DB admin user:")),
777 HTML::td(HTML::input(array('name'=>"dbadmin[user]",
780 'value'=>$dbadmin_user)))),
781 HTML::tr(HTML::td(array('align'=>'right'),
782 _("DB admin password:")),
783 HTML::td(HTML::input(array('name'=>"dbadmin[passwd]",
786 'maxlength'=>256)))),
787 HTML::tr(HTML::td(array('align'=>'center', 'colspan' => 2),
788 Button("submit:", _("Submit"), 'wikiaction'),
790 Button("submit:dbadmin[cancel]", _("Cancel"),
793 echo "</div><!-- content -->\n";
794 echo asXML(Template("bottom"));
795 echo "</body></html>\n";
796 $this->request->finish();
801 * if page.cached_html does not exists:
802 * put _cached_html from pagedata into a new seperate blob,
803 * not into the huge serialized string.
805 * It is only rarelely needed: for current page only, if-not-modified,
806 * but was extracetd for every simple page iteration.
808 function _upgrade_cached_html ( $verbose=true ) {
810 if (!$this->isSQL) return;
812 if ($this->phpwiki_version >= 1030.10) {
814 echo _("check for extra page.cached_html column")," ... ";
815 $database = $this->dbi->_backend->database();
816 extract($this->dbi->_backend->_table_names);
817 $fields = $this->dbi->_backend->listOfFields($database, $page_tbl);
819 echo _("SKIP"), "<br />\n";
822 if (!strstr(strtolower(join(':', $fields)), "cached_html")) {
824 echo "<b>",_("ADDING"),"</b>"," ... ";
825 $backend_type = $this->dbi->_backend->backendType();
826 if (substr($backend_type,0,5) == 'mysql')
827 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html MEDIUMBLOB");
829 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html BLOB");
831 echo "<b>",_("CONVERTING"),"</b>"," ... ";
832 $count = _convert_cached_html();
834 echo $count, " ", _("OK"), "<br />\n";
837 echo _("OK"), "<br />\n";
844 * move _cached_html for all pages from pagedata into a new seperate blob.
845 * decoupled from action=upgrade, so that it can be used by a WikiAdminUtils button also.
847 function _convert_cached_html () {
849 if (!$this->isSQL) return;
850 //if (!in_array(DATABASE_TYPE, array('SQL','ADODB'))) return;
852 $pages = $this->dbi->getAllPages();
853 $cache =& $this->dbi->_cache;
855 extract($this->dbi->_backend->_table_names);
856 while ($page = $pages->next()) {
857 $pagename = $page->getName();
858 $data = $this->dbi->_backend->get_pagedata($pagename);
859 if (!empty($data['_cached_html'])) {
860 $cached_html = $data['_cached_html'];
861 $data['_cached_html'] = '';
862 $cache->update_pagedata($pagename, $data);
863 // store as blob, not serialized
864 $this->dbi->genericSqlQuery("UPDATE $page_tbl SET cached_html=? WHERE pagename=?",
865 array($cached_html, $pagename));
873 * upgrade to 1.3.13 link structure.
875 function _upgrade_relation_links ( $verbose=true ) {
876 if ($this->phpwiki_version >= 1030.12200610 and $this->isSQL) {
877 echo _("check for relation field in link table")," ...";
878 $database = $this->dbi->_backend->database();
879 $link_tbl = $prefix . 'link';
880 $fields = $this->dbi->_backend->listOfFields($database, $link_tbl);
883 } elseif (strstr(strtolower(join(':', $fields)), "link")) {
884 echo "<b>",_("ADDING")," relation</b>"," ... ";
885 $this->dbi->genericSqlQuery("ALTER TABLE $link_tbl ADD relation INT DEFAULT 0;");
886 $this->dbi->genericSqlQuery("CREATE INDEX link_relation ON $link_tbl (relation);");
892 if ($this->phpwiki_version >= 1030.12200610) {
893 echo _("Rebuild entire database to upgrade relation links")," ... ";
894 if (DATABASE_TYPE == 'dba') {
895 echo "<b>",_("CONVERTING")," dba linktable</b>","(~2 min, max 4 min) ... ";
898 $this->dbi->_backend->_linkdb->rebuild();
902 $this->dbi->_backend->rebuild();
904 echo _("OK"), "<br />\n";
908 function CheckPluginUpdate() {
911 echo "<h3>",sprintf(_("check for necessary %s updates"),
912 _("plugin argument")),"</h3>\n";
914 $this->_configUpdates = array();
915 $this->_configUpdates[] = new UpgradePluginEntry
916 ($this, array('key' => 'plugin_randompage_numpages',
917 'fixed_with' => 1012.0,
918 //'header' => _("change RandomPage pages => numpages"),
919 //'notice' =>_("found RandomPage plugin"),
920 'check_args' => array("plugin RandomPage pages",
921 "/(<\?\s*plugin\s+ RandomPage\s+)pages/",
923 $this->_configUpdates[] = new UpgradePluginEntry
924 ($this, array('key' => 'plugin_createtoc_position',
925 'fixed_with' => 1013.0,
926 //'header' => _("change CreateToc align => position"),
927 //'notice' =>_("found CreateToc plugin"),
928 'check_args' => array("plugin CreateToc align",
929 "/(<\?\s*plugin\s+ CreateToc[^\?]+)align/",
932 if (empty($this->_configUpdates)) return;
933 foreach ($this->_configUpdates as $update) {
934 $pages = $this->dbi->fullSearch($this->check_args[0]);
935 while ($page = $allpages->next()) {
936 $current = $page->getCurrentRevision();
937 $pagetext = $current->getPackedContent();
938 $update->check($this->check_args[1], $this->check_args[2], $pagetext, $page, $current);
948 * preg_replace over local file.
949 * Only line-orientated matches possible.
951 function fixLocalFile($match, $replace, $filename) {
952 $o_filename = $filename;
953 if (!file_exists($filename))
954 $filename = FindFile($filename);
955 if (!file_exists($filename))
956 return array(false, sprintf(_("file %s not found"), $o_filename));
958 if (is_writable($filename)) {
959 $in = fopen($filename, "rb");
960 $out = fopen($tmp = tempnam(getUploadFilePath(),"cfg"), "wb");
962 $tmp = str_replace("/","\\",$tmp);
963 // Detect the existing linesep at first line. fgets strips it even if 'rb'.
964 // Before we simply assumed \r\n on windows local files.
965 $s = fread($in, 1024);
967 $linesep = (substr_count($s, "\r\n") > substr_count($s, "\n")) ? "\r\n" : "\n";
968 //$linesep = isWindows() ? "\r\n" : "\n";
969 while ($s = fgets($in)) {
970 // =>php-5.0.1 can fill count
971 //$new = preg_replace($match, $replace, $s, -1, $count);
972 $new = preg_replace($match, $replace, $s);
974 $s = $new . $linesep;
983 $reason = sprintf(_("%s not found in %s"), $match, $filename);
985 return array($found, $reason);
987 @unlink("$file.bak");
988 @rename($file,"$file.bak");
989 if (!rename($tmp, $file))
990 return array(false, sprintf(_("couldn't move %s to %s"), $tmp, $filename));
994 return array(false, sprintf(_("file %s is not writable"), $filename));
998 function CheckConfigUpdate () {
999 echo "<h3>",sprintf(_("check for necessary %s updates"),
1000 "config.ini"),"</h3>\n";
1001 $entry = new UpgradeConfigEntry
1002 ($this, array('key' => 'cache_control_none',
1003 'fixed_with' => 1012.0,
1004 'header' => sprintf(_("check for %s"),"CACHE_CONTROL = NONE"),
1005 'applicable_args' => 'CACHE_CONTROL',
1006 'notice' => _("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'"),
1007 'check_args' => array("/^\s*CACHE_CONTROL\s*=\s*NONE/", "CACHE_CONTROL = NO_CACHE")));
1008 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1009 $this->_configUpdates[] = $entry;
1011 $entry = new UpgradeConfigEntry
1012 ($this, array('key' => 'group_method_none',
1013 'fixed_with' => 1012.0,
1014 'header' => sprintf(_("check for %s"), "GROUP_METHOD = NONE"),
1015 'applicable_args' => 'GROUP_METHOD',
1016 'notice' =>_("GROUP_METHOD is set to NONE, and must be changed to \"NONE\""),
1017 'check_args' => array("/^\s*GROUP_METHOD\s*=\s*NONE/", "GROUP_METHOD = \"NONE\"")));
1018 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1019 $this->_configUpdates[] = $entry;
1021 $entry = new UpgradeConfigEntry
1022 ($this, array('key' => 'blog_empty_default_prefix',
1023 'fixed_with' => 1013.0,
1024 'header' => sprintf(_("check for %s"), "BLOG_EMPTY_DEFAULT_PREFIX"),
1025 'applicable_args' => 'BLOG_EMPTY_DEFAULT_PREFIX',
1026 'notice' =>_("fix BLOG_EMPTY_DEFAULT_PREFIX into BLOG_DEFAULT_EMPTY_PREFIX"),
1027 'check_args' => array("/BLOG_EMPTY_DEFAULT_PREFIX\s*=/","BLOG_DEFAULT_EMPTY_PREFIX =")));
1028 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined'));
1029 $this->_configUpdates[] = $entry;
1031 // TODO: find extra file updates
1032 if (empty($this->_configUpdates)) return;
1033 foreach ($this->_configUpdates as $update) {
1043 * Add an upgrade item to be checked.
1045 * @param $parent object The parent Upgrade class to inherit the version properties
1046 * @param $key string A short unique key to store success in the WikiDB
1047 * @param $fixed_with double @see phpwiki_version() number
1048 * @param $header string Optional header to be printed always even if not applicable
1049 * @param $applicable WikiCallback Optional callback boolean applicable()
1050 * @param $notice string Description of the check
1051 * @param $method WikiCallback Optional callback array method(array)
1052 * //param All other args are passed to $method
1054 function UpgradeEntry(&$parent, $params) {
1055 $this->parent =& $parent; // get the properties db_version
1056 foreach (array('key' => 'required',
1057 // the wikidb stores the version when we actually fixed that.
1058 'fixed_with' => 'required',
1059 'header' => '', // always printed
1060 'applicable_cb' => null, // method to check if applicable
1061 'applicable_args' => array(), // might be the config name
1063 'check_cb' => null, // method to apply
1064 'check_args' => array())
1067 if (!isset($params[$k])) { // default
1068 if ($v == 'required') trigger_error("Required arg $k missing", E_USER_ERROR);
1069 else $this->{$k} = $v;
1071 $this->{$k} = $params[$k];
1074 if (!is_array($this->applicable_args)) // single arg convenience shortcut
1075 $this->applicable_args = array($this->applicable_args);
1076 if (!is_array($this->check_args)) // single arg convenience shortcut
1077 $this->check_args = array($this->check_args);
1078 if ($this->notice === '' and count($this->applicable_args) > 0)
1079 $this->notice = 'Check for '.join(', ', $this->applicable_args);
1080 $this->_db_key = "_upgrade";
1081 $this->upgrade = $this->parent->dbi->get($this->_db_key);
1084 function setApplicableCb($object) {
1085 $this->applicable_cb =& $object;
1087 function _check_if_already_fixed() {
1089 if (!isset($this->upgrade['name'])) return false;
1090 // override with force?
1091 if ($this->parent->request->getArg('force')) return false;
1092 // already fixed and with an ok version
1093 if ($this->upgrade['name'] >= $this->fixed_with) return $this->upgrade['name'];
1094 // already fixed but with an older version. do it again.
1098 // store in db no to fix again
1099 $this->upgrade['name'] = $this->parent->phpwiki_version;
1100 $this->parent->dbi->set($this->_db_key, $this->upgrade);
1101 echo "<b>",_("FIXED"),"</b>";
1102 if (isset($this->reason))
1103 echo ": ", $this->reason;
1109 echo " <b><font color=\"red\">", _("FAILED"), "</font></b>";
1110 if (isset($this->reason))
1111 echo ": ", $this->reason;
1116 function skip() { // not applicable
1117 if (isset($this->silent_skip)) return true;
1118 echo _(" skipped"),".<br />\n";
1122 function check($args = null) {
1123 if ($this->header) echo $this->header, ' ... ';
1124 if ($when = $this->_check_if_already_fixed()) {
1125 // be totally silent if no header is defined.
1126 if ($this->header) echo _("fixed with")," ",$when,"<br />\n";
1130 if (is_object($this->applicable_cb)) {
1131 if (!$this->applicable_cb->call_array($this->applicable_args))
1132 return $this->skip();
1134 if ($this->notice) {
1137 echo $this->notice," ";
1140 if (!is_null($args)) $this->check_args =& $args;
1141 if (is_object($this->check_cb))
1142 $do = $this->method_cb->call_array($this->check_args);
1144 $do = $this->default_method($this->check_args);
1145 if (is_array($do)) {
1146 $this->reason = $do[1];
1149 return $do ? $this->pass() : $this->fail();
1151 } // class UpgradeEntry
1153 class UpgradeConfigEntry extends UpgradeEntry {
1154 function _applicable_defined() {
1155 return (boolean)defined($this->applicable_args[0]);
1157 function _applicable_defined_and_empty() {
1158 $const = $this->applicable_args[0];
1159 return (boolean)(defined($const) and !constant($const));
1161 function default_method ($args) {
1163 $replace = $args[1];
1164 return $this->parent->fixLocalFile($match, $replace, "config/config.ini");
1166 } // class UpdateConfigEntry
1168 /* This is different */
1169 class UpgradePluginEntry extends UpgradeEntry {
1172 * check all pages for a plugin match
1174 var $silent_skip = 1;
1176 function default_method (&$args) {
1178 $replace = $args[1];
1179 $pagetext =& $args[2];
1181 $current =& $args[4];
1182 if (preg_match($match, $pagetext)) {
1183 echo $page->getName()," ",$this->notice," ... ";
1184 if ($newtext = preg_replace($match, $replace, $pagetext)) {
1185 $meta = $current->_data;
1186 $meta['summary'] = "upgrade: ".$this->header;
1187 $page->save($newtext, $current->getVersion() + 1, $meta);
1194 } // class UpdatePluginEntry
1197 * fix custom themes which are not in our distribution
1198 * this should be optional
1200 class UpgradeThemeEntry extends UpgradeEntry {
1202 function default_method (&$args) {
1204 $replace = $args[1];
1205 $template = $args[2];
1208 function fixThemeTemplate($match, $new, $template) {
1209 // for all custom themes
1210 $ourthemes = explode(":","blog:Crao:default:Hawaiian:MacOSX:MonoBook:Portland:shamino_com:SpaceWiki:wikilens:Wordpress");
1211 $themedir = NormalizeLocalFileName("themes");
1212 $dh = opendir($themedir);
1213 while ($r = readdir($dh)) {
1214 if (filetype($r) == 'dir' and $r[0] != '.' and !is_array($r, $ourthemes))
1215 $customthemes[] = $r;
1219 foreach ($customthemes as $customtheme) {
1220 $template = FindFile("themes/$customtheme/templates/$template");
1221 $do = $this->parent->fixLocalFile($match, $new, template);
1224 $errors .= $do[1]." ";
1228 return array($success, $errors);
1235 * Upgrade: Base class for multipage worksteps
1236 * identify, validate, display options, next step
1241 // TODO: At which step are we?
1242 // validate and do it again or go on with next step.
1244 /** entry function from lib/main.php
1246 function DoUpgrade(&$request) {
1248 if (!$request->_user->isAdmin()) {
1249 $request->_notAuthorized(WIKIAUTH_ADMIN);
1251 HTML::div(array('class' => 'disabled-plugin'),
1252 fmt("Upgrade disabled: user != isAdmin")));
1255 // TODO: StartLoadDump should turn on implicit_flush.
1256 @ini_set("implicit_flush", true);
1257 StartLoadDump($request, _("Upgrading this PhpWiki"));
1258 $upgrade = new Upgrade($request);
1259 //if (!$request->getArg('noindex'))
1260 // CheckOldIndexUpdate($request); // index.php => config.ini to upgrade from < 1.3.10
1261 if (!$request->getArg('nodb'))
1262 $upgrade->CheckDatabaseUpdate($request); // first check cached_html and friends
1263 if (!$request->getArg('nopgsrc')) {
1264 $upgrade->CheckActionPageUpdate($request);
1265 $upgrade->CheckPgsrcUpdate($request);
1267 if (!$request->getArg('noplugin'))
1268 $upgrade->CheckPluginUpdate($request);
1269 if (!$request->getArg('noconfig'))
1270 $upgrade->CheckConfigUpdate($request);
1271 // This is optional and should be linked. In EndLoadDump or PhpWikiAdministration?
1272 //if ($request->getArg('theme'))
1273 // $upgrade->CheckThemeUpdate($request);
1274 EndLoadDump($request);
1281 // c-basic-offset: 4
1282 // c-hanging-comment-ender-p: nil
1283 // indent-tabs-mode: nil