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 along
20 * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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';
55 function Upgrade(&$request)
57 $this->request =& $request;
58 $this->dbi =& $request->_dbi; // no reference for dbadmin ?
59 $this->phpwiki_version = $this->current_db_version = phpwiki_version();
60 //$this->current_db_version = 1030.13; // should be stored in the db. should be phpwiki_version
62 $this->db_version = $this->dbi->get_db_version();
63 $this->isSQL = $this->dbi->_backend->isSQL();
66 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")
73 echo "$path/$pagename: " . _("always skip the HomePage.") . " " . _("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.") . " " . _("Skipped.") . "<br />\n";
110 echo sprintf(_("%s does not exist"), $pagename), "<br />\n";
111 LoadAny($this->request, $path . "/" . $filename);
116 function CheckActionPageUpdate()
118 echo "<h2>", sprintf(_("Check for necessary %s updates"), _("ActionPage")), "</h2>\n";
119 // 1.3.13 before we pull in all missing pages, we rename existing ones
120 $this->_rename_page_helper(_("_AuthInfo"), _("DebugAuthInfo"));
121 // this is in some templates. so we keep the old name
122 //$this->_rename_page_helper($this->dbi, _("DebugInfo"), _("DebugBackendInfo"));
123 $this->_rename_page_helper(_("_GroupInfo"), _("GroupAuthInfo")); //never officially existed
124 $this->_rename_page_helper("InterWikiKarte", "InterWikiListe"); // german only
126 $path = FindFile('pgsrc');
127 $pgsrc = new fileSet($path);
128 // most actionpages have the same name as the plugin
129 $loc_path = FindLocalizedFile('pgsrc');
130 foreach ($pgsrc->getFiles() as $filename) {
131 if (substr($filename, -1, 1) == '~') continue;
132 if (substr($filename, -5, 5) == '.orig') continue;
133 $pagename = urldecode($filename);
134 if (isActionPage($pagename)) {
135 $translation = gettext($pagename);
136 if ($translation == $pagename)
137 $this->doPgsrcUpdate($pagename, $path, $filename);
138 elseif (FindLocalizedFile('pgsrc/' . urlencode($translation), 1))
139 $this->doPgsrcUpdate($translation, $loc_path, urlencode($translation)); else
140 $this->doPgsrcUpdate($pagename, $path, $filename);
145 // see loadsave.php for saving new pages.
146 function CheckPgsrcUpdate()
148 // Check some theme specific pgsrc files (blog, wikilens, fusionforge, custom).
149 // We check theme specific pgsrc first in case the page is present in both
150 // theme specific and global pgsrc
152 $path = $WikiTheme->file("pgsrc");
153 // TBD: the call to fileSet prints a warning:
154 // Notice: Unable to open directory 'themes/MonoBook/pgsrc' for reading
155 $pgsrc = new fileSet($path);
156 if ($pgsrc->getFiles()) {
157 echo "<h2>", sprintf(_("Check for necessary theme %s updates"),
159 foreach ($pgsrc->getFiles() as $filename) {
160 if (substr($filename, -1, 1) == '~') continue;
161 if (substr($filename, -5, 5) == '.orig') continue;
162 $pagename = urldecode($filename);
163 $this->doPgsrcUpdate($pagename, $path, $filename);
167 echo "<h2>", sprintf(_("Check for necessary %s updates"),
169 if ($this->db_version < 1030.12200612) {
170 echo "<h4>", _("rename to Help: pages"), "</h4>\n";
172 $path = FindLocalizedFile(WIKI_PGSRC);
173 $pgsrc = new fileSet($path);
174 // fixme: verification, ...
175 foreach ($pgsrc->getFiles() as $filename) {
176 if (substr($filename, -1, 1) == '~') continue;
177 if (substr($filename, -5, 5) == '.orig') continue;
178 $pagename = urldecode($filename);
179 if (!isActionPage($filename)) {
180 // There're a lot of now unneeded pages around.
181 // At first rename the BlaPlugin pages to Help/<pagename> and then to the update.
182 if ($this->db_version < 1030.12200612) {
183 $this->_rename_to_help_page($pagename);
185 $this->doPgsrcUpdate($pagename, $path, $filename);
190 function _rename_page_helper($oldname, $pagename)
192 echo sprintf(_("rename %s to %s"), $oldname, $pagename), " ...";
193 if ($this->dbi->isWikiPage($oldname) and !$this->dbi->isWikiPage($pagename)) {
194 if ($this->dbi->_backend->rename_page($oldname, $pagename)) {
195 echo _("OK"), " <br />\n";
197 echo ' <span style="color: red; font-weight: bold;">' . _("FAILED") . "</span><br />\n";
200 echo " " . _("Skipped.") . "<br />\n";
204 function _rename_to_help_page($pagename)
206 $newprefix = _("Help") . "/";
207 if (substr($pagename, 0, strlen($newprefix)) != $newprefix) return;
208 $oldname = substr($pagename, strlen($newprefix));
209 $this->_rename_page_helper($oldname, $pagename);
213 * TODO: Search table definition in appropriate schema
215 * Supported: mysql and generic SQL, for ADODB and PearDB.
217 function installTable($table, $backend_type)
220 if (!$this->isSQL) return;
221 echo _("MISSING"), " ... \n";
222 $backend = &$this->dbi->_backend->_dbh;
224 $schema = findFile("schemas/${backend_type}.sql");
226 echo " ",_("FAILED"),": ",sprintf(_("no schema %s found"),
227 "schemas/${backend_type}.sql")," ... <br />\n";
231 extract($this->dbi->_backend->_table_names);
232 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
235 assert($session_tbl);
236 if ($backend_type == 'mysql') {
237 $this->dbi->genericSqlQuery("
238 CREATE TABLE $session_tbl (
239 sess_id CHAR(32) NOT NULL DEFAULT '',
240 sess_data BLOB NOT NULL,
241 sess_date INT UNSIGNED NOT NULL,
242 sess_ip CHAR(15) NOT NULL,
243 PRIMARY KEY (sess_id),
247 $this->dbi->genericSqlQuery("
248 CREATE TABLE $session_tbl (
249 sess_id CHAR(32) NOT NULL DEFAULT '',
250 sess_data " . ($backend_type == 'pgsql' ? 'TEXT' : 'BLOB') . " NOT NULL,
252 sess_ip CHAR(15) NOT NULL
254 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX sess_id ON $session_tbl (sess_id)");
256 $this->dbi->genericSqlQuery("CREATE INDEX sess_date on session (sess_date)");
257 echo " ", _("CREATED");
260 $pref_tbl = $prefix . 'pref';
261 if ($backend_type == 'mysql') {
262 $this->dbi->genericSqlQuery("
263 CREATE TABLE $pref_tbl (
264 userid CHAR(48) BINARY NOT NULL UNIQUE,
265 prefs TEXT NULL DEFAULT '',
269 $this->dbi->genericSqlQuery("
270 CREATE TABLE $pref_tbl (
271 userid CHAR(48) NOT NULL,
272 prefs TEXT NULL DEFAULT ''
274 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX userid ON $pref_tbl (userid)");
276 echo " ", _("CREATED");
279 $member_tbl = $prefix . 'member';
280 if ($backend_type == 'mysql') {
281 $this->dbi->genericSqlQuery("
282 CREATE TABLE $member_tbl (
283 userid CHAR(48) BINARY NOT NULL,
284 groupname CHAR(48) BINARY NOT NULL DEFAULT 'users',
289 $this->dbi->genericSqlQuery("
290 CREATE TABLE $member_tbl (
291 userid CHAR(48) NOT NULL,
292 groupname CHAR(48) NOT NULL DEFAULT 'users'
294 $this->dbi->genericSqlQuery("CREATE INDEX userid ON $member_tbl (userid)");
295 $this->dbi->genericSqlQuery("CREATE INDEX groupname ON $member_tbl (groupname)");
297 echo " ", _("CREATED");
300 $rating_tbl = $prefix . 'rating';
301 if ($backend_type == 'mysql') {
302 $this->dbi->genericSqlQuery("
303 CREATE TABLE $rating_tbl (
304 dimension INT(4) NOT NULL,
305 raterpage INT(11) NOT NULL,
306 rateepage INT(11) NOT NULL,
307 ratingvalue FLOAT NOT NULL,
308 rateeversion INT(11) NOT NULL,
309 tstamp TIMESTAMP(14) NOT NULL,
310 PRIMARY KEY (dimension, raterpage, rateepage)
313 $this->dbi->genericSqlQuery("
314 CREATE TABLE $rating_tbl (
315 dimension INT(4) NOT NULL,
316 raterpage INT(11) NOT NULL,
317 rateepage INT(11) NOT NULL,
318 ratingvalue FLOAT NOT NULL,
319 rateeversion INT(11) NOT NULL,
320 tstamp TIMESTAMP(14) NOT NULL
322 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX rating"
323 . " ON $rating_tbl (dimension, raterpage, rateepage)");
325 echo " ", _("CREATED");
328 $log_tbl = $prefix . 'accesslog';
329 // fields according to http://www.outoforder.cc/projects/apache/mod_log_sql/docs-2.0/#id2756178
331 A User Agent agent varchar(255) Mozilla/4.0 (compat; MSIE 6.0; Windows)
332 a CGi request arguments request_args varchar(255) user=Smith&cart=1231&item=532
333 b Bytes transfered bytes_sent int unsigned 32561
334 c??? Text of cookie cookie varchar(255) Apache=sdyn.fooonline.net 1300102700823
335 f Local filename requested request_file varchar(255) /var/www/html/books-cycroad.html
336 H HTTP request_protocol request_protocol varchar(10) HTTP/1.1
337 h Name of remote host remote_host varchar(50) blah.foobar.com
338 I Request ID (from modd_unique_id) id char(19) POlFcUBRH30AAALdBG8
339 l Ident user info remote_logname varcgar(50) bobby
340 M Machine ID??? machine_id varchar(25) web01
341 m HTTP request method request_method varchar(10) GET
342 P httpd cchild PID child_pid smallint unsigned 3215
343 p http port server_port smallint unsigned 80
344 R Referer referer varchar(255) http://www.biglinks4u.com/linkpage.html
345 r Request in full form request_line varchar(255) GET /books-cycroad.html HTTP/1.1
346 S Time of request in UNIX time_t format time_stamp int unsigned 1005598029
347 T Seconds to service request request_duration smallint unsigned 2
348 t Time of request in human format request_time char(28) [02/Dec/2001:15:01:26 -0800]
349 U Request in simple form request_uri varchar(255) /books-cycroad.html
350 u User info from HTTP auth remote_user varchar(50) bobby
351 v Virtual host servicing the request virtual_host varchar(255)
353 $this->dbi->genericSqlQuery("
354 CREATE TABLE $log_tbl (
355 time_stamp int unsigned,
356 remote_host varchar(100),
357 remote_user varchar(50),
358 request_method varchar(10),
359 request_line varchar(255),
360 request_args varchar(255),
361 request_uri varchar(255),
362 request_time char(28),
363 status smallint unsigned,
364 bytes_sent smallint unsigned,
365 referer varchar(255),
367 request_duration float
369 $this->dbi->genericSqlQuery("CREATE INDEX log_time ON $log_tbl (time_stamp)");
370 $this->dbi->genericSqlQuery("CREATE INDEX log_host ON $log_tbl (remote_host)");
371 echo " ", _("CREATED");
378 * Update from ~1.3.4 to current.
379 * tables: Only session, user, pref and member
380 * jeffs-hacks database api (around 1.3.2) later:
381 * people should export/import their pages if using that old versions.
383 function CheckDatabaseUpdate()
385 global $DBAuthParams, $DBParams;
387 echo "<h2>", sprintf(_("Check for necessary %s updates"),
389 " - ", DATABASE_TYPE, "</h2>\n";
390 $dbadmin = $this->request->getArg('dbadmin');
393 if (isset($dbadmin['cancel'])) {
394 echo _("Cancel"), " <br />\n";
398 echo "db version: we want ", $this->current_db_version, "\n<br />";
399 echo "db version: we have ", $this->db_version, "\n<br />";
400 if ($this->db_version >= $this->current_db_version) {
401 echo _("OK"), "<br />\n";
405 $backend_type = $this->dbi->_backend->backendType();
407 echo "<h4>", _("Backend type: "), $backend_type, "</h4>\n";
408 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
409 $tables = $this->dbi->_backend->listOfTables();
410 foreach (explode(':', 'session:pref:member') as $table) {
411 echo sprintf(_("Check for table %s"), $table), " ...";
412 if (!in_array($prefix . $table, $tables)) {
413 $this->installTable($table, $backend_type);
415 echo _("OK"), " <br />\n";
420 if ($this->phpwiki_version >= 1030.12200612 and $this->db_version < 1030.13) {
421 if ($this->isSQL and preg_match("/(pgsql|postgres)/", $backend_type)) {
422 trigger_error("You need to upgrade to schema/psql-initialize.sql manually!",
424 // $this->_upgrade_psql_tsearch2();
426 $this->_upgrade_relation_links();
429 if (ACCESS_LOG_SQL and $this->isSQL) {
430 $table = "accesslog";
431 echo sprintf(_("Check for table %s"), $table), " ...";
432 if (!in_array($prefix . $table, $tables)) {
433 $this->installTable($table, $backend_type);
435 echo _("OK"), " <br />\n";
438 if ($this->isSQL and (class_exists("RatingsUserFactory") or $this->dbi->isWikiPage(_("RateIt")))) {
440 echo sprintf(_("Check for table %s"), $table), " ...";
441 if (!in_array($prefix . $table, $tables)) {
442 $this->installTable($table, $backend_type);
444 echo _("OK"), " <br />\n";
447 $backend = &$this->dbi->_backend->_dbh;
449 extract($this->dbi->_backend->_table_names);
451 // 1.3.8 added session.sess_ip
452 if ($this->isSQL and $this->phpwiki_version >= 1030.08 and USE_DB_SESSION
453 and isset($this->request->_dbsession)
455 echo _("Check for new session.sess_ip column"), " ... ";
456 $database = $this->dbi->_backend->database();
457 assert(!empty($DBParams['db_session_table']));
458 $session_tbl = $prefix . $DBParams['db_session_table'];
459 $sess_fields = $this->dbi->_backend->listOfFields($database, $session_tbl);
462 } elseif (!strstr(strtolower(join(':', $sess_fields)), "sess_ip")) {
463 // TODO: postgres test (should be able to add columns at the end, but not in between)
464 echo "<b>", _("ADDING"), "</b>", " ... ";
465 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL");
466 $this->dbi->genericSqlQuery("CREATE INDEX sess_date ON $session_tbl (sess_date)");
471 if (substr($backend_type, 0, 5) == 'mysql') {
472 // upgrade to 4.1.8 destroyed my session table:
473 // sess_id => varchar(10), sess_data => varchar(5). For others obviously also.
474 echo _("Check for mysql session.sess_id sanity"), " ... ";
475 $result = $this->dbi->genericSqlQuery("DESCRIBE $session_tbl");
476 if (DATABASE_TYPE == 'SQL') {
477 $iter = new WikiDB_backend_PearDB_generic_iter($backend, $result);
478 } elseif (DATABASE_TYPE == 'ADODB') {
479 $iter = new WikiDB_backend_ADODB_generic_iter($backend, $result,
480 array("Field", "Type", "Null", "Key", "Default", "Extra"));
481 } elseif (DATABASE_TYPE == 'PDO') {
482 $iter = new WikiDB_backend_PDO_generic_iter($backend, $result);
484 while ($col = $iter->next()) {
485 if ($col["Field"] == 'sess_id' and !strstr(strtolower($col["Type"]), 'char(32)')) {
486 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_id"
487 . " sess_id CHAR(32) NOT NULL");
488 echo "sess_id ", $col["Type"], " ", _("fixed"), " => CHAR(32) ";
490 if ($col["Field"] == 'sess_ip' and !strstr(strtolower($col["Type"]), 'char(15)')) {
491 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_ip"
492 . " sess_ip CHAR(15) NOT NULL");
493 echo "sess_ip ", $col["Type"], " ", _("fixed"), " => CHAR(15) ";
496 echo _("OK"), "<br />\n";
501 ALTER TABLE link ADD relation INT DEFAULT 0;
502 CREATE INDEX linkrelation ON link (relation);
505 // mysql >= 4.0.4 requires LOCK TABLE privileges
506 if (substr($backend_type, 0, 5) == 'mysql') {
507 echo _("Check for mysql LOCK TABLE privilege"), " ...";
508 $mysql_version = $this->dbi->_backend->_serverinfo['version'];
509 if ($mysql_version > 400.40) {
510 if (!empty($this->dbi->_backend->_parsedDSN))
511 $parseDSN = $this->dbi->_backend->_parsedDSN;
512 elseif (function_exists('parseDSN')) // ADODB or PDO
513 $parseDSN = parseDSN($DBParams['dsn']); else // pear
514 $parseDSN = DB::parseDSN($DBParams['dsn']);
515 $username = $this->dbi->_backend->qstr($parseDSN['username']);
517 $query = "SELECT lock_tables_priv FROM mysql.db WHERE user='$username'";
518 //mysql_select_db("mysql", $this->dbi->_backend->connection());
519 $db_fields = $this->dbi->_backend->listOfFields("mysql", "db");
520 if (!strstr(strtolower(join(':', $db_fields)), "lock_tables_priv")) {
521 echo join(':', $db_fields);
522 die("lock_tables_priv missing. The DB Admin must run mysql_fix_privilege_tables");
524 $row = $this->dbi->_backend->getRow($query);
525 if (isset($row[0]) and $row[0] == 'N') {
526 $this->dbi->genericSqlQuery("UPDATE mysql.db SET lock_tables_priv='Y'"
527 . " WHERE mysql.user='$username'");
528 $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
529 echo "mysql.db user='$username'", _("fixed"), "<br />\n";
532 $query = "SELECT lock_tables_priv FROM mysql.user WHERE user='$username'";
533 $row = $this->dbi->_backend->getRow($query);
534 if ($row and $row[0] == 'N') {
535 $this->dbi->genericSqlQuery("UPDATE mysql.user SET lock_tables_priv='Y'"
536 . " WHERE mysql.user='$username'");
537 $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
538 echo "mysql.user user='$username'", _("fixed"), "<br />\n";
540 echo ' <span style="color: red; font-weight: bold;">' . _("FAILED") . "</span>"
541 . " Neither mysql.db nor mysql.user has a user='$username'"
542 . " or the lock_tables_priv field",
545 echo _("OK"), "<br />\n";
548 echo _("OK"), "<br />\n";
550 //mysql_select_db($this->dbi->_backend->database(), $this->dbi->_backend->connection());
552 echo sprintf(_("version <em>%s</em> not affected"), $mysql_version), "<br />\n";
556 // 1.3.10 mysql requires page.id auto_increment
557 // mysql, mysqli or mysqlt
558 if ($this->phpwiki_version >= 1030.099 and substr($backend_type, 0, 5) == 'mysql'
559 and DATABASE_TYPE != 'PDO'
561 echo _("Check for mysql page.id auto_increment flag"), " ...";
562 assert(!empty($page_tbl));
563 $database = $this->dbi->_backend->database();
564 $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
565 $columns = mysql_num_fields($fields);
566 for ($i = 0; $i < $columns; $i++) {
567 if (mysql_field_name($fields, $i) == 'id') {
568 $flags = mysql_field_flags($fields, $i);
569 //DONE: something was wrong with ADODB here.
570 if (!strstr(strtolower($flags), "auto_increment")) {
571 echo "<b>", _("ADDING"), "</b>", " ... ";
572 // MODIFY col_def valid since mysql 3.22.16,
573 // older mysql's need CHANGE old_col col_def
574 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE id"
575 . " id INT NOT NULL AUTO_INCREMENT");
576 $fields = mysql_list_fields($database, $page_tbl);
577 if (!strstr(strtolower(mysql_field_flags($fields, $i)), "auto_increment"))
578 echo ' <span style="color: red; font-weight: bold;">' . _("FAILED") . "</span><br />\n";
580 echo _("OK"), "<br />\n";
582 echo _("OK"), "<br />\n";
587 mysql_free_result($fields);
590 // Check for mysql 4.1.x/5.0.0a binary search problem.
591 // http://bugs.mysql.com/bug.php?id=4398
592 // "select * from page where LOWER(pagename) like '%search%'" does not apply LOWER!
593 // Confirmed for 4.1.0alpha,4.1.3-beta,5.0.0a; not yet tested for 4.1.2alpha,
594 // On windows only, though utf8 would be useful elsewhere also.
595 // Illegal mix of collations (latin1_bin,IMPLICIT) and
596 // (utf8_general_ci, COERCIBLE) for operation '='])
597 if (isWindows() and substr($backend_type, 0, 5) == 'mysql') {
598 echo _("Check for mysql 4.1.x/5.0.0 binary search on Windows problem"), " ...";
599 $mysql_version = $this->dbi->_backend->_serverinfo['version'];
600 if ($mysql_version < 401.0) {
601 echo sprintf(_("version <em>%s</em>"), $mysql_version), " ",
602 _("not affected"), "<br />\n";
603 } elseif ($mysql_version >= 401.6) { // FIXME: since which version?
604 $row = $this->dbi->_backend->getRow("SHOW CREATE TABLE $page_tbl");
605 $result = join(" ", $row);
606 if (strstr(strtolower($result), "character set")
607 and strstr(strtolower($result), "collate")
609 echo _("OK"), "<br />\n";
611 //SET CHARACTER SET latin1
613 if ($charset == 'iso-8859-1') $charset = 'latin1';
614 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename "
615 . "pagename VARCHAR(100) "
616 . "CHARACTER SET '$charset' COLLATE '$charset" . "_bin' NOT NULL");
617 echo sprintf(_("version <em>%s</em>"), $mysql_version),
618 " <b>", _("FIXED"), "</b>",
621 } elseif (DATABASE_TYPE != 'PDO') {
622 // check if already fixed
623 extract($this->dbi->_backend->_table_names);
624 assert(!empty($page_tbl));
625 $database = $this->dbi->_backend->database();
626 $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
627 $columns = mysql_num_fields($fields);
628 for ($i = 0; $i < $columns; $i++) {
629 if (mysql_field_name($fields, $i) == 'pagename') {
630 $flags = mysql_field_flags($fields, $i);
631 // I think it was fixed with 4.1.6, but I tested it only with 4.1.8
632 if ($mysql_version > 401.0 and $mysql_version < 401.6) {
633 // remove the binary flag
634 if (strstr(strtolower($flags), "binary")) {
635 // FIXME: on duplicate pagenames this will fail!
636 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename"
637 . " pagename VARCHAR(100) NOT NULL");
638 echo sprintf(_("version <em>%s</em>"), $mysql_version),
639 "<b>", _("FIXED"), "</b>"
648 if ($this->isSQL and ACCESS_LOG_SQL & 2) {
649 echo _("Check for ACCESS_LOG_SQL passwords in POST requests"), " ...";
650 // Don't display passwords in POST requests (up to 2005-02-04 12:03:20)
651 $res = $this->dbi->genericSqlIter("SELECT time_stamp, remote_host, " .
652 "request_args FROM ${prefix}accesslog WHERE request_args LIKE " .
653 "'%s:6:\"passwd\"%' AND request_args NOT LIKE '%s:6:\"passwd\";" .
654 "s:15:\"<not displayed>\"%'");
656 while ($row = $res->next()) {
657 $args = preg_replace("/(s:6:\"passwd\";s:15:\").*(\")/",
658 "$1<not displayed>$2", $row["request_args"]);
659 $ts = $row["time_stamp"];
660 $rh = $row["remote_host"];
661 $this->dbi->genericSqlQuery("UPDATE ${prefix}accesslog SET " .
662 "request_args='$args' WHERE time_stamp=$ts AND " .
663 "remote_host='$rh'");
667 echo "<b>", _("FIXED"), "</b>", "<br />\n";
669 echo _("OK"), "<br />\n";
671 if ($this->phpwiki_version >= 1030.13) {
672 echo _("Check for ACCESS_LOG_SQL remote_host varchar(50)"), " ...";
673 $database = $this->dbi->_backend->database();
674 $accesslog_tbl = $prefix . 'accesslog';
675 $fields = $this->dbi->_backend->listOfFields($database, $accesslog_tbl);
678 } elseif (strstr(strtolower(join(':', $sess_fields)), "remote_host")) {
679 // TODO: how to check size, already done?
680 echo "<b>", _("FIXING"), "remote_host</b>", " ... ";
681 $this->dbi->genericSqlQuery("ALTER TABLE $accesslog_tbl CHANGE remote_host VARCHAR(100)");
688 $this->_upgrade_cached_html();
690 if ($this->db_version < $this->current_db_version) {
691 $this->dbi->set_db_version($this->current_db_version);
692 $this->db_version = $this->dbi->get_db_version();
693 echo "db version: upgrade to ", $this->db_version, " ";
694 echo _("OK"), "<br />\n";
702 * Filter SQL missing permissions errors.
704 * A wrong DBADMIN user will not be able to connect
705 * @see _is_false_error, ErrorManager
708 function _dbpermission_filter($err)
710 if ($err->isWarning()) {
711 global $ErrorManager;
712 $this->error_caught = 1;
713 $ErrorManager->_postponed_errors[] = $err;
719 function _try_dbadmin_user($user, $passwd)
721 global $DBParams, $DBAuthParams;
722 $AdminParams = $DBParams;
723 if (DATABASE_TYPE == 'SQL')
724 $dsn = DB::parseDSN($AdminParams['dsn']);
726 $dsn = parseDSN($AdminParams['dsn']);
728 $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
734 $AdminParams['_tryroot_from_upgrade'] = 1;
735 // add error handler to warn about missing permissions for DBADMIN_USER
736 global $ErrorManager;
737 $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_dbpermission_filter'));
738 $this->error_caught = 0;
739 $this->dbi = WikiDB::open($AdminParams);
740 if (!$this->error_caught) return true;
741 // FAILED: redo our connection with the wikiuser
742 $this->dbi = WikiDB::open($DBParams);
743 $ErrorManager->flushPostponedErrors();
744 $ErrorManager->popErrorHandler();
750 if (!$this->isSQL) return;
752 /* SQLite never needs admin params */
753 $backend_type = $this->dbi->_backend->backendType();
754 if (substr($backend_type, 0, 6) == "sqlite") {
757 $dbadmin_user = 'root';
758 if ($dbadmin = $this->request->getArg('dbadmin')) {
759 $dbadmin_user = $dbadmin['user'];
760 if (isset($dbadmin['cancel'])) {
762 } elseif (!empty($dbadmin_user)) {
763 if ($this->_try_dbadmin_user($dbadmin['user'], $dbadmin['passwd']))
766 } elseif (DBADMIN_USER) {
767 if ($this->_try_dbadmin_user(DBADMIN_USER, DBADMIN_PASSWD)) {
771 // Check if the privileges are enough. Need CREATE and ALTER perms.
772 // And on Windows: SELECT FROM mysql, possibly: UPDATE mysql.
773 $form = HTML::form(array("method" => "post",
774 "action" => $this->request->getPostURL(),
775 "accept-charset" => $GLOBALS['charset']),
776 HTML::p(_("Upgrade requires database privileges to CREATE and ALTER the phpwiki database."),
778 _("And on Windows at least the privilege to SELECT FROM mysql, and possibly UPDATE mysql")),
779 HiddenInputs(array('action' => 'upgrade',
780 'overwrite' => $this->request->getArg('overwrite'))),
781 HTML::table(array("cellspacing" => 4),
782 HTML::tr(HTML::td(array('align' => 'right'),
783 _("DB admin user:")),
784 HTML::td(HTML::input(array('name' => "dbadmin[user]",
787 'value' => $dbadmin_user)))),
788 HTML::tr(HTML::td(array('align' => 'right'),
789 _("DB admin password:")),
790 HTML::td(HTML::input(array('name' => "dbadmin[passwd]",
791 'type' => 'password',
793 'maxlength' => 256)))),
794 HTML::tr(HTML::td(array('align' => 'center', 'colspan' => 2),
795 Button("submit:", _("Submit"), 'wikiaction'),
797 Button("submit:dbadmin[cancel]", _("Cancel"),
800 echo "</div><!-- content -->\n";
801 echo asXML(Template("bottom"));
802 echo "</body></html>\n";
803 $this->request->finish();
808 * if page.cached_html does not exists:
809 * put _cached_html from pagedata into a new seperate blob,
810 * not into the huge serialized string.
812 * It is only rarelely needed: for current page only, if-not-modified,
813 * but was extracetd for every simple page iteration.
815 function _upgrade_cached_html($verbose = true)
818 if (!$this->isSQL) return 0;
820 if ($this->phpwiki_version >= 1030.10) {
822 echo _("Check for extra page.cached_html column"), " ... ";
823 $database = $this->dbi->_backend->database();
824 extract($this->dbi->_backend->_table_names);
825 $fields = $this->dbi->_backend->listOfFields($database, $page_tbl);
827 echo _("SKIP"), "<br />\n";
830 if (!strstr(strtolower(join(':', $fields)), "cached_html")) {
832 echo "<b>", _("ADDING"), "</b>", " ... ";
833 $backend_type = $this->dbi->_backend->backendType();
834 if (substr($backend_type, 0, 5) == 'mysql')
835 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html MEDIUMBLOB");
837 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html BLOB");
839 echo "<b>", _("CONVERTING"), "</b>", " ... ";
840 $count = _convert_cached_html();
842 echo $count, " ", _("OK"), "<br />\n";
845 echo _("OK"), "<br />\n";
852 * move _cached_html for all pages from pagedata into a new seperate blob.
853 * decoupled from action=upgrade, so that it can be used by a WikiAdminUtils button also.
855 function _convert_cached_html()
858 if (!$this->isSQL) return 0;
859 //if (!in_array(DATABASE_TYPE, array('SQL','ADODB'))) return;
861 $pages = $this->dbi->getAllPages();
862 $cache =& $this->dbi->_cache;
864 extract($this->dbi->_backend->_table_names);
865 while ($page = $pages->next()) {
866 $pagename = $page->getName();
867 $data = $this->dbi->_backend->get_pagedata($pagename);
868 if (!empty($data['_cached_html'])) {
869 $cached_html = $data['_cached_html'];
870 $data['_cached_html'] = '';
871 $cache->update_pagedata($pagename, $data);
872 // store as blob, not serialized
873 $this->dbi->genericSqlQuery("UPDATE $page_tbl SET cached_html=? WHERE pagename=?",
874 array($cached_html, $pagename));
882 * upgrade to 1.3.13 link structure.
884 function _upgrade_relation_links($verbose = true)
886 if ($this->phpwiki_version >= 1030.12200610 and $this->isSQL) {
887 echo _("Check for relation field in link table"), " ...";
888 $database = $this->dbi->_backend->database();
889 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
890 $link_tbl = $prefix . 'link';
891 $fields = $this->dbi->_backend->listOfFields($database, $link_tbl);
894 } elseif (strstr(strtolower(join(':', $fields)), "link")) {
895 echo "<b>", _("ADDING"), " relation</b>", " ... ";
896 $this->dbi->genericSqlQuery("ALTER TABLE $link_tbl ADD relation INT DEFAULT 0;");
897 $this->dbi->genericSqlQuery("CREATE INDEX link_relation ON $link_tbl (relation);");
903 if ($this->phpwiki_version >= 1030.12200610) {
904 echo _("Rebuild entire database to upgrade relation links"), " ... ";
905 if (DATABASE_TYPE == 'dba') {
906 echo "<b>", _("CONVERTING"), " dba linktable</b>", "(~2 min, max 4 min) ... ";
909 $this->dbi->_backend->_linkdb->rebuild();
913 $this->dbi->_backend->rebuild();
915 echo _("OK"), "<br />\n";
919 function CheckPluginUpdate()
921 echo "<h2>", sprintf(_("Check for necessary %s updates"),
922 _("plugin argument")), "</h2>\n";
924 $this->_configUpdates = array();
925 $this->_configUpdates[] = new UpgradePluginEntry
926 ($this, array('key' => 'plugin_randompage_numpages',
927 'fixed_with' => 1012.0,
928 //'header' => _("change RandomPage pages => numpages"),
929 //'notice' =>_("found RandomPage plugin"),
930 'check_args' => array("plugin RandomPage pages",
931 "/(<\?\s*plugin\s+ RandomPage\s+)pages/",
933 $this->_configUpdates[] = new UpgradePluginEntry
934 ($this, array('key' => 'plugin_createtoc_position',
935 'fixed_with' => 1013.0,
936 //'header' => _("change CreateToc align => position"),
937 //'notice' =>_("found CreateToc plugin"),
938 'check_args' => array("plugin CreateToc align",
939 "/(<\?\s*plugin\s+ CreateToc[^\?]+)align/",
942 if (empty($this->_configUpdates)) return;
943 foreach ($this->_configUpdates as $update) {
944 $pages = $this->dbi->fullSearch($this->check_args[0]);
945 while ($page = $allpages->next()) {
946 $current = $page->getCurrentRevision();
947 $pagetext = $current->getPackedContent();
948 $update->check($this->check_args[1], $this->check_args[2], $pagetext, $page, $current);
958 * preg_replace over local file.
959 * Only line-orientated matches possible.
961 function fixLocalFile($match, $replace, $filename)
963 $o_filename = $filename;
964 if (!file_exists($filename))
965 $filename = FindFile($filename);
966 if (!file_exists($filename))
967 return array(false, sprintf(_("File “%s” not found."), $o_filename));
969 if (is_writable($filename)) {
970 $in = fopen($filename, "rb");
971 $out = fopen($tmp = tempnam(getUploadFilePath(), "cfg"), "wb");
973 $tmp = str_replace("/", "\\", $tmp);
974 // Detect the existing linesep at first line. fgets strips it even if 'rb'.
975 // Before we simply assumed \r\n on Windows local files.
976 $s = fread($in, 1024);
978 $linesep = (substr_count($s, "\r\n") > substr_count($s, "\n")) ? "\r\n" : "\n";
979 //$linesep = isWindows() ? "\r\n" : "\n";
980 while ($s = fgets($in)) {
981 // =>php-5.0.1 can fill count
982 //$new = preg_replace($match, $replace, $s, -1, $count);
983 $new = preg_replace($match, $replace, $s);
985 $s = $new . $linesep;
994 $reason = sprintf(_("%s not found in %s"), $match, $filename);
996 return array($found, $reason);
998 @unlink("$file.bak");
999 @rename($file, "$file.bak");
1000 if (!rename($tmp, $file))
1001 return array(false, sprintf(_("couldn't move %s to %s"), $tmp, $filename));
1005 return array(false, sprintf(_("file %s is not writable"), $filename));
1009 function CheckConfigUpdate()
1011 echo "<h2>", sprintf(_("Check for necessary %s updates"),
1012 "config.ini"), "</h2>\n";
1013 $entry = new UpgradeConfigEntry
1014 ($this, array('key' => 'cache_control_none',
1015 'fixed_with' => 1012.0,
1016 'header' => sprintf(_("Check for %s"), "CACHE_CONTROL = NONE"),
1017 'applicable_args' => 'CACHE_CONTROL',
1018 'notice' => _("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'"),
1019 'check_args' => array("/^\s*CACHE_CONTROL\s*=\s*NONE/", "CACHE_CONTROL = NO_CACHE")));
1020 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1021 $this->_configUpdates[] = $entry;
1023 $entry = new UpgradeConfigEntry
1024 ($this, array('key' => 'group_method_none',
1025 'fixed_with' => 1012.0,
1026 'header' => sprintf(_("Check for %s"), "GROUP_METHOD = NONE"),
1027 'applicable_args' => 'GROUP_METHOD',
1028 'notice' => _("GROUP_METHOD is set to NONE, and must be changed to \"NONE\""),
1029 'check_args' => array("/^\s*GROUP_METHOD\s*=\s*NONE/", "GROUP_METHOD = \"NONE\"")));
1030 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1031 $this->_configUpdates[] = $entry;
1033 $entry = new UpgradeConfigEntry
1034 ($this, array('key' => 'blog_empty_default_prefix',
1035 'fixed_with' => 1013.0,
1036 'header' => sprintf(_("Check for %s"), "BLOG_EMPTY_DEFAULT_PREFIX"),
1037 'applicable_args' => 'BLOG_EMPTY_DEFAULT_PREFIX',
1038 'notice' => _("fix BLOG_EMPTY_DEFAULT_PREFIX into BLOG_DEFAULT_EMPTY_PREFIX"),
1039 'check_args' => array("/BLOG_EMPTY_DEFAULT_PREFIX\s*=/", "BLOG_DEFAULT_EMPTY_PREFIX =")));
1040 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined'));
1041 $this->_configUpdates[] = $entry;
1043 // TODO: find extra file updates
1044 if (empty($this->_configUpdates)) return;
1045 foreach ($this->_configUpdates as $update) {
1055 * Add an upgrade item to be checked.
1057 * @param $parent object The parent Upgrade class to inherit the version properties
1058 * @param $key string A short unique key to store success in the WikiDB
1059 * @param $fixed_with double @see phpwiki_version() number
1060 * @param $header string Optional header to be printed always even if not applicable
1061 * @param $applicable WikiCallback Optional callback boolean applicable()
1062 * @param $notice string Description of the check
1063 * @param $method WikiCallback Optional callback array method(array)
1064 * //param All other args are passed to $method
1066 function UpgradeEntry(&$parent, $params)
1068 $this->parent =& $parent; // get the properties db_version
1069 foreach (array('key' => 'required',
1070 // the wikidb stores the version when we actually fixed that.
1071 'fixed_with' => 'required',
1072 'header' => '', // always printed
1073 'applicable_cb' => null, // method to check if applicable
1074 'applicable_args' => array(), // might be the config name
1076 'check_cb' => null, // method to apply
1077 'check_args' => array())
1079 if (!isset($params[$k])) { // default
1080 if ($v == 'required') trigger_error("Required arg $k missing", E_USER_ERROR);
1081 else $this->{$k} = $v;
1083 $this->{$k} = $params[$k];
1086 if (!is_array($this->applicable_args)) // single arg convenience shortcut
1087 $this->applicable_args = array($this->applicable_args);
1088 if (!is_array($this->check_args)) // single arg convenience shortcut
1089 $this->check_args = array($this->check_args);
1090 if ($this->notice === '' and count($this->applicable_args) > 0)
1091 $this->notice = 'Check for ' . join(', ', $this->applicable_args);
1092 $this->_db_key = "_upgrade";
1093 $this->upgrade = $this->parent->dbi->get($this->_db_key);
1097 function setApplicableCb($object)
1099 $this->applicable_cb =& $object;
1102 function _check_if_already_fixed()
1105 if (!isset($this->upgrade['name'])) return false;
1106 // override with force?
1107 if ($this->parent->request->getArg('force')) return false;
1108 // already fixed and with an ok version
1109 if ($this->upgrade['name'] >= $this->fixed_with) return $this->upgrade['name'];
1110 // already fixed but with an older version. do it again.
1116 // store in db no to fix again
1117 $this->upgrade['name'] = $this->parent->phpwiki_version;
1118 $this->parent->dbi->set($this->_db_key, $this->upgrade);
1119 echo "<b>", _("FIXED"), "</b>";
1120 if (isset($this->reason))
1121 echo _(": "), $this->reason;
1129 echo '<span style="color: red; font-weight: bold; ">' . _("FAILED") . "</span>";
1130 if (isset($this->reason))
1131 echo _(": "), $this->reason;
1139 if (isset($this->silent_skip)) return true;
1140 echo " " . _("Skipped.") . "<br />\n";
1145 function check($args = null)
1147 if ($this->header) echo $this->header, ' ... ';
1148 if ($when = $this->_check_if_already_fixed()) {
1149 // be totally silent if no header is defined.
1150 if ($this->header) echo _("fixed with"), " ", $when, "<br />\n";
1154 if (is_object($this->applicable_cb)) {
1155 if (!$this->applicable_cb->call_array($this->applicable_args))
1156 return $this->skip();
1158 if ($this->notice) {
1161 echo $this->notice, " ";
1164 if (!is_null($args)) $this->check_args =& $args;
1165 if (is_object($this->check_cb))
1166 $do = $this->method_cb->call_array($this->check_args);
1168 $do = $this->default_method($this->check_args);
1169 if (is_array($do)) {
1170 $this->reason = $do[1];
1173 return $do ? $this->pass() : $this->fail();
1175 } // class UpgradeEntry
1177 class UpgradeConfigEntry extends UpgradeEntry
1179 function _applicable_defined()
1181 return (boolean)defined($this->applicable_args[0]);
1184 function _applicable_defined_and_empty()
1186 $const = $this->applicable_args[0];
1187 return (boolean)(defined($const) and !constant($const));
1190 function default_method($args)
1193 $replace = $args[1];
1194 return $this->parent->fixLocalFile($match, $replace, "config/config.ini");
1196 } // class UpdateConfigEntry
1198 /* This is different */
1199 class UpgradePluginEntry extends UpgradeEntry
1203 * check all pages for a plugin match
1205 var $silent_skip = 1;
1207 function default_method(&$args)
1210 $replace = $args[1];
1211 $pagetext =& $args[2];
1213 $current =& $args[4];
1214 if (preg_match($match, $pagetext)) {
1215 echo $page->getName(), " ", $this->notice, " ... ";
1216 if ($newtext = preg_replace($match, $replace, $pagetext)) {
1217 $meta = $current->_data;
1218 $meta['summary'] = "upgrade: " . $this->header;
1219 $page->save($newtext, $current->getVersion() + 1, $meta);
1226 } // class UpdatePluginEntry
1229 * fix custom themes which are not in our distribution
1230 * this should be optional
1232 class UpgradeThemeEntry extends UpgradeEntry
1235 function default_method(&$args)
1238 $replace = $args[1];
1239 $template = $args[2];
1242 function fixThemeTemplate($match, $new, $template)
1244 // for all custom themes
1245 $ourthemes = explode(":", "blog:Crao:default:Hawaiian:MacOSX:MonoBook:Portland:shamino_com:SpaceWiki:wikilens:Wordpress");
1246 $themedir = NormalizeLocalFileName("themes");
1247 $dh = opendir($themedir);
1248 while ($r = readdir($dh)) {
1249 if (filetype($r) == 'dir' and $r[0] != '.' and !is_array($r, $ourthemes))
1250 $customthemes[] = $r;
1254 foreach ($customthemes as $customtheme) {
1255 $template = FindFile("themes/$customtheme/templates/$template");
1256 $do = $this->parent->fixLocalFile($match, $new, template);
1259 $errors .= $do[1] . " ";
1263 return array($success, $errors);
1270 * Upgrade: Base class for multipage worksteps
1271 * identify, validate, display options, next step
1276 // TODO: At which step are we?
1277 // validate and do it again or go on with next step.
1279 /** entry function from lib/main.php
1281 function DoUpgrade(&$request)
1284 if (!$request->_user->isAdmin()) {
1285 $request->_notAuthorized(WIKIAUTH_ADMIN);
1287 HTML::div(array('class' => 'disabled-plugin'),
1288 fmt("Upgrade disabled: user != isAdmin")));
1291 // TODO: StartLoadDump should turn on implicit_flush.
1292 @ini_set("implicit_flush", true);
1293 StartLoadDump($request, _("Upgrading this PhpWiki"));
1294 $upgrade = new Upgrade($request);
1295 //if (!$request->getArg('noindex'))
1296 // CheckOldIndexUpdate($request); // index.php => config.ini to upgrade from < 1.3.10
1297 if (!$request->getArg('nodb')) {
1298 $upgrade->CheckDatabaseUpdate($request); // first check cached_html and friends
1300 if (!$request->getArg('nopgsrc')) {
1301 $upgrade->CheckPgsrcUpdate($request);
1302 $upgrade->CheckActionPageUpdate($request);
1304 // if (!$request->getArg('noplugin')) {
1305 // $upgrade->CheckPluginUpdate($request);
1307 if (!$request->getArg('noconfig')) {
1308 $upgrade->CheckConfigUpdate($request);
1310 // This is optional and should be linked. In EndLoadDump or PhpWikiAdministration?
1311 //if ($request->getArg('theme'))
1312 // $upgrade->CheckThemeUpdate($request);
1313 EndLoadDump($request);
1319 // c-basic-offset: 4
1320 // c-hanging-comment-ender-p: nil
1321 // indent-tabs-mode: nil