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();
67 * TODO: check for the pgsrc_version number, not the revision mtime only
69 function doPgsrcUpdate($pagename, $path, $filename)
71 // don't ever update the HomePage
72 if ((defined(HOME_PAGE) and ($pagename == HOME_PAGE))
73 or ($pagename == _("HomePage"))
74 or ($pagename == "HomePage")
76 echo "$path/$pagename: ", _("always skip the HomePage."),
77 _(" Skipped"), ".<br />\n";
81 $page = $this->dbi->getPage($pagename);
82 if ($page->exists()) {
83 // check mtime: update automatically if pgsrc is newer
84 $rev = $page->getCurrentRevision();
85 $page_mtime = $rev->get('mtime');
86 $data = implode("", file($path . "/" . $filename));
87 if (($parts = ParseMimeifiedPages($data))) {
88 usort($parts, 'SortByPageVersion');
90 $pageinfo = $parts[0];
91 $stat = stat($path . "/" . $filename);
93 if (isset($pageinfo['versiondata']['mtime']))
94 $new_mtime = $pageinfo['versiondata']['mtime'];
95 if (!$new_mtime and isset($pageinfo['versiondata']['lastmodified']))
96 $new_mtime = $pageinfo['versiondata']['lastmodified'];
97 if (!$new_mtime and isset($pageinfo['pagedata']['date']))
98 $new_mtime = $pageinfo['pagedata']['date'];
100 $new_mtime = $stat[9];
101 if ($new_mtime > $page_mtime) {
102 echo "$path/$pagename: ", _("newer than the existing page."),
103 _(" replace "), "($new_mtime > $page_mtime)", "<br />\n";
104 LoadAny($this->request, $path . "/" . $filename);
107 echo "$path/$pagename: ", _("older than the existing page."),
108 _(" Skipped"), ".<br />\n";
111 echo "$path/$pagename: ", _("unknown format."),
112 _(" Skipped"), ".<br />\n";
115 echo sprintf(_("%s does not exist"), $pagename), "<br />\n";
116 LoadAny($this->request, $path . "/" . $filename);
121 function CheckActionPageUpdate()
123 echo "<h2>", sprintf(_("Check for necessary %s updates"),
124 _("ActionPage")), "</h2>\n";
125 // 1.3.13 before we pull in all missing pages, we rename existing ones
126 $this->_rename_page_helper(_("_AuthInfo"), _("DebugAuthInfo"));
127 // this is in some templates. so we keep the old name
128 //$this->_rename_page_helper($this->dbi, _("DebugInfo"), _("DebugBackendInfo"));
129 $this->_rename_page_helper(_("_GroupInfo"), _("GroupAuthInfo")); //never officially existed
130 $this->_rename_page_helper("InterWikiKarte", "InterWikiListe"); // german only
132 $path = FindFile('pgsrc');
133 $pgsrc = new fileSet($path);
134 // most actionpages have the same name as the plugin
135 $loc_path = FindLocalizedFile('pgsrc');
136 foreach ($pgsrc->getFiles() as $filename) {
137 if (substr($filename, -1, 1) == '~') continue;
138 if (substr($filename, -5, 5) == '.orig') continue;
139 $pagename = urldecode($filename);
140 if (isActionPage($pagename)) {
141 $translation = gettext($pagename);
142 if ($translation == $pagename)
143 $this->doPgsrcUpdate($pagename, $path, $filename);
144 elseif (FindLocalizedFile('pgsrc/' . urlencode($translation), 1))
145 $this->doPgsrcUpdate($translation, $loc_path, urlencode($translation)); else
146 $this->doPgsrcUpdate($pagename, $path, $filename);
151 // see loadsave.php for saving new pages.
152 function CheckPgsrcUpdate()
154 // Check some theme specific pgsrc files (blog, wikilens, fusionforge, custom).
155 // We check theme specific pgsrc first in case the page is present in both
156 // theme specific and global pgsrc
158 $path = $WikiTheme->file("pgsrc");
159 // TBD: the call to fileSet prints a warning:
160 // Notice: Unable to open directory 'themes/MonoBook/pgsrc' for reading
161 $pgsrc = new fileSet($path);
162 if ($pgsrc->getFiles()) {
163 echo "<h2>", sprintf(_("Check for necessary theme %s updates"),
165 foreach ($pgsrc->getFiles() as $filename) {
166 if (substr($filename, -1, 1) == '~') continue;
167 if (substr($filename, -5, 5) == '.orig') continue;
168 $pagename = urldecode($filename);
169 $this->doPgsrcUpdate($pagename, $path, $filename);
173 echo "<h2>", sprintf(_("Check for necessary %s updates"),
175 if ($this->db_version < 1030.12200612) {
176 echo "<h4>", _("rename to Help: pages"), "</h4>\n";
178 $path = FindLocalizedFile(WIKI_PGSRC);
179 $pgsrc = new fileSet($path);
180 // fixme: verification, ...
181 foreach ($pgsrc->getFiles() as $filename) {
182 if (substr($filename, -1, 1) == '~') continue;
183 if (substr($filename, -5, 5) == '.orig') continue;
184 $pagename = urldecode($filename);
185 if (!isActionPage($filename)) {
186 // There're a lot of now unneeded pages around.
187 // At first rename the BlaPlugin pages to Help/<pagename> and then to the update.
188 if ($this->db_version < 1030.12200612) {
189 $this->_rename_to_help_page($pagename);
191 $this->doPgsrcUpdate($pagename, $path, $filename);
196 function _rename_page_helper($oldname, $pagename)
198 echo sprintf(_("rename %s to %s"), $oldname, $pagename), " ...";
199 if ($this->dbi->isWikiPage($oldname) and !$this->dbi->isWikiPage($pagename)) {
200 if ($this->dbi->_backend->rename_page($oldname, $pagename)) {
201 echo _("OK"), " <br />\n";
203 echo ' <span style="color: red; font-weight: bold;">' . _("FAILED") . "</span><br />\n";
206 echo _(" Skipped"), " <br />\n";
210 function _rename_to_help_page($pagename)
212 $newprefix = _("Help") . "/";
213 if (substr($pagename, 0, strlen($newprefix)) != $newprefix) return;
214 $oldname = substr($pagename, strlen($newprefix));
215 $this->_rename_page_helper($oldname, $pagename);
219 * TODO: Search table definition in appropriate schema
221 * Supported: mysql and generic SQL, for ADODB and PearDB.
223 function installTable($table, $backend_type)
226 if (!$this->isSQL) return;
227 echo _("MISSING"), " ... \n";
228 $backend = &$this->dbi->_backend->_dbh;
230 $schema = findFile("schemas/${backend_type}.sql");
232 echo " ",_("FAILED"),": ",sprintf(_("no schema %s found"),
233 "schemas/${backend_type}.sql")," ... <br />\n";
237 extract($this->dbi->_backend->_table_names);
238 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
241 assert($session_tbl);
242 if ($backend_type == 'mysql') {
243 $this->dbi->genericSqlQuery("
244 CREATE TABLE $session_tbl (
245 sess_id CHAR(32) NOT NULL DEFAULT '',
246 sess_data BLOB NOT NULL,
247 sess_date INT UNSIGNED NOT NULL,
248 sess_ip CHAR(15) NOT NULL,
249 PRIMARY KEY (sess_id),
253 $this->dbi->genericSqlQuery("
254 CREATE TABLE $session_tbl (
255 sess_id CHAR(32) NOT NULL DEFAULT '',
256 sess_data " . ($backend_type == 'pgsql' ? 'TEXT' : 'BLOB') . " NOT NULL,
258 sess_ip CHAR(15) NOT NULL
260 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX sess_id ON $session_tbl (sess_id)");
262 $this->dbi->genericSqlQuery("CREATE INDEX sess_date on session (sess_date)");
263 echo " ", _("CREATED");
266 $pref_tbl = $prefix . 'pref';
267 if ($backend_type == 'mysql') {
268 $this->dbi->genericSqlQuery("
269 CREATE TABLE $pref_tbl (
270 userid CHAR(48) BINARY NOT NULL UNIQUE,
271 prefs TEXT NULL DEFAULT '',
275 $this->dbi->genericSqlQuery("
276 CREATE TABLE $pref_tbl (
277 userid CHAR(48) NOT NULL,
278 prefs TEXT NULL DEFAULT ''
280 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX userid ON $pref_tbl (userid)");
282 echo " ", _("CREATED");
285 $member_tbl = $prefix . 'member';
286 if ($backend_type == 'mysql') {
287 $this->dbi->genericSqlQuery("
288 CREATE TABLE $member_tbl (
289 userid CHAR(48) BINARY NOT NULL,
290 groupname CHAR(48) BINARY NOT NULL DEFAULT 'users',
295 $this->dbi->genericSqlQuery("
296 CREATE TABLE $member_tbl (
297 userid CHAR(48) NOT NULL,
298 groupname CHAR(48) NOT NULL DEFAULT 'users'
300 $this->dbi->genericSqlQuery("CREATE INDEX userid ON $member_tbl (userid)");
301 $this->dbi->genericSqlQuery("CREATE INDEX groupname ON $member_tbl (groupname)");
303 echo " ", _("CREATED");
306 $rating_tbl = $prefix . 'rating';
307 if ($backend_type == 'mysql') {
308 $this->dbi->genericSqlQuery("
309 CREATE TABLE $rating_tbl (
310 dimension INT(4) NOT NULL,
311 raterpage INT(11) NOT NULL,
312 rateepage INT(11) NOT NULL,
313 ratingvalue FLOAT NOT NULL,
314 rateeversion INT(11) NOT NULL,
315 tstamp TIMESTAMP(14) NOT NULL,
316 PRIMARY KEY (dimension, raterpage, rateepage)
319 $this->dbi->genericSqlQuery("
320 CREATE TABLE $rating_tbl (
321 dimension INT(4) NOT NULL,
322 raterpage INT(11) NOT NULL,
323 rateepage INT(11) NOT NULL,
324 ratingvalue FLOAT NOT NULL,
325 rateeversion INT(11) NOT NULL,
326 tstamp TIMESTAMP(14) NOT NULL
328 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX rating"
329 . " ON $rating_tbl (dimension, raterpage, rateepage)");
331 echo " ", _("CREATED");
334 $log_tbl = $prefix . 'accesslog';
335 // fields according to http://www.outoforder.cc/projects/apache/mod_log_sql/docs-2.0/#id2756178
337 A User Agent agent varchar(255) Mozilla/4.0 (compat; MSIE 6.0; Windows)
338 a CGi request arguments request_args varchar(255) user=Smith&cart=1231&item=532
339 b Bytes transfered bytes_sent int unsigned 32561
340 c??? Text of cookie cookie varchar(255) Apache=sdyn.fooonline.net 1300102700823
341 f Local filename requested request_file varchar(255) /var/www/html/books-cycroad.html
342 H HTTP request_protocol request_protocol varchar(10) HTTP/1.1
343 h Name of remote host remote_host varchar(50) blah.foobar.com
344 I Request ID (from modd_unique_id) id char(19) POlFcUBRH30AAALdBG8
345 l Ident user info remote_logname varcgar(50) bobby
346 M Machine ID??? machine_id varchar(25) web01
347 m HTTP request method request_method varchar(10) GET
348 P httpd cchild PID child_pid smallint unsigned 3215
349 p http port server_port smallint unsigned 80
350 R Referer referer varchar(255) http://www.biglinks4u.com/linkpage.html
351 r Request in full form request_line varchar(255) GET /books-cycroad.html HTTP/1.1
352 S Time of request in UNIX time_t format time_stamp int unsigned 1005598029
353 T Seconds to service request request_duration smallint unsigned 2
354 t Time of request in human format request_time char(28) [02/Dec/2001:15:01:26 -0800]
355 U Request in simple form request_uri varchar(255) /books-cycroad.html
356 u User info from HTTP auth remote_user varchar(50) bobby
357 v Virtual host servicing the request virtual_host varchar(255)
359 $this->dbi->genericSqlQuery("
360 CREATE TABLE $log_tbl (
361 time_stamp int unsigned,
362 remote_host varchar(100),
363 remote_user varchar(50),
364 request_method varchar(10),
365 request_line varchar(255),
366 request_args varchar(255),
367 request_uri varchar(255),
368 request_time char(28),
369 status smallint unsigned,
370 bytes_sent smallint unsigned,
371 referer varchar(255),
373 request_duration float
375 $this->dbi->genericSqlQuery("CREATE INDEX log_time ON $log_tbl (time_stamp)");
376 $this->dbi->genericSqlQuery("CREATE INDEX log_host ON $log_tbl (remote_host)");
377 echo " ", _("CREATED");
384 * Update from ~1.3.4 to current.
385 * tables: Only session, user, pref and member
386 * jeffs-hacks database api (around 1.3.2) later:
387 * people should export/import their pages if using that old versions.
389 function CheckDatabaseUpdate()
391 global $DBAuthParams, $DBParams;
393 echo "<h2>", sprintf(_("Check for necessary %s updates"),
395 " - ", DATABASE_TYPE, "</h2>\n";
396 $dbadmin = $this->request->getArg('dbadmin');
399 if (isset($dbadmin['cancel'])) {
400 echo _("CANCEL"), " <br />\n";
404 echo "db version: we want ", $this->current_db_version, "\n<br />";
405 echo "db version: we have ", $this->db_version, "\n<br />";
406 if ($this->db_version >= $this->current_db_version) {
407 echo _("OK"), "<br />\n";
411 $backend_type = $this->dbi->_backend->backendType();
413 echo "<h4>", _("Backend type: "), $backend_type, "</h4>\n";
414 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
415 $tables = $this->dbi->_backend->listOfTables();
416 foreach (explode(':', 'session:pref:member') as $table) {
417 echo sprintf(_("Check for table %s"), $table), " ...";
418 if (!in_array($prefix . $table, $tables)) {
419 $this->installTable($table, $backend_type);
421 echo _("OK"), " <br />\n";
426 if ($this->phpwiki_version >= 1030.12200612 and $this->db_version < 1030.13) {
427 if ($this->isSQL and preg_match("/(pgsql|postgres)/", $backend_type)) {
428 trigger_error("You need to upgrade to schema/psql-initialize.sql manually!",
430 // $this->_upgrade_psql_tsearch2();
432 $this->_upgrade_relation_links();
435 if (ACCESS_LOG_SQL and $this->isSQL) {
436 $table = "accesslog";
437 echo sprintf(_("Check for table %s"), $table), " ...";
438 if (!in_array($prefix . $table, $tables)) {
439 $this->installTable($table, $backend_type);
441 echo _("OK"), " <br />\n";
444 if ($this->isSQL and (class_exists("RatingsUserFactory") or $this->dbi->isWikiPage(_("RateIt")))) {
446 echo sprintf(_("Check for table %s"), $table), " ...";
447 if (!in_array($prefix . $table, $tables)) {
448 $this->installTable($table, $backend_type);
450 echo _("OK"), " <br />\n";
453 $backend = &$this->dbi->_backend->_dbh;
455 extract($this->dbi->_backend->_table_names);
457 // 1.3.8 added session.sess_ip
458 if ($this->isSQL and $this->phpwiki_version >= 1030.08 and USE_DB_SESSION
459 and isset($this->request->_dbsession)
461 echo _("Check for new session.sess_ip column"), " ... ";
462 $database = $this->dbi->_backend->database();
463 assert(!empty($DBParams['db_session_table']));
464 $session_tbl = $prefix . $DBParams['db_session_table'];
465 $sess_fields = $this->dbi->_backend->listOfFields($database, $session_tbl);
468 } elseif (!strstr(strtolower(join(':', $sess_fields)), "sess_ip")) {
469 // TODO: postgres test (should be able to add columns at the end, but not in between)
470 echo "<b>", _("ADDING"), "</b>", " ... ";
471 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL");
472 $this->dbi->genericSqlQuery("CREATE INDEX sess_date ON $session_tbl (sess_date)");
477 if (substr($backend_type, 0, 5) == 'mysql') {
478 // upgrade to 4.1.8 destroyed my session table:
479 // sess_id => varchar(10), sess_data => varchar(5). For others obviously also.
480 echo _("Check for mysql session.sess_id sanity"), " ... ";
481 $result = $this->dbi->genericSqlQuery("DESCRIBE $session_tbl");
482 if (DATABASE_TYPE == 'SQL') {
483 $iter = new WikiDB_backend_PearDB_generic_iter($backend, $result);
484 } elseif (DATABASE_TYPE == 'ADODB') {
485 $iter = new WikiDB_backend_ADODB_generic_iter($backend, $result,
486 array("Field", "Type", "Null", "Key", "Default", "Extra"));
487 } elseif (DATABASE_TYPE == 'PDO') {
488 $iter = new WikiDB_backend_PDO_generic_iter($backend, $result);
490 while ($col = $iter->next()) {
491 if ($col["Field"] == 'sess_id' and !strstr(strtolower($col["Type"]), 'char(32)')) {
492 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_id"
493 . " sess_id CHAR(32) NOT NULL");
494 echo "sess_id ", $col["Type"], " ", _("fixed"), " => CHAR(32) ";
496 if ($col["Field"] == 'sess_ip' and !strstr(strtolower($col["Type"]), 'char(15)')) {
497 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_ip"
498 . " sess_ip CHAR(15) NOT NULL");
499 echo "sess_ip ", $col["Type"], " ", _("fixed"), " => CHAR(15) ";
502 echo _("OK"), "<br />\n";
507 ALTER TABLE link ADD relation INT DEFAULT 0;
508 CREATE INDEX linkrelation ON link (relation);
511 // mysql >= 4.0.4 requires LOCK TABLE privileges
512 if (substr($backend_type, 0, 5) == 'mysql') {
513 echo _("Check for mysql LOCK TABLE privilege"), " ...";
514 $mysql_version = $this->dbi->_backend->_serverinfo['version'];
515 if ($mysql_version > 400.40) {
516 if (!empty($this->dbi->_backend->_parsedDSN))
517 $parseDSN = $this->dbi->_backend->_parsedDSN;
518 elseif (function_exists('parseDSN')) // ADODB or PDO
519 $parseDSN = parseDSN($DBParams['dsn']); else // pear
520 $parseDSN = DB::parseDSN($DBParams['dsn']);
521 $username = $this->dbi->_backend->qstr($parseDSN['username']);
523 $query = "SELECT lock_tables_priv FROM mysql.db WHERE user='$username'";
524 //mysql_select_db("mysql", $this->dbi->_backend->connection());
525 $db_fields = $this->dbi->_backend->listOfFields("mysql", "db");
526 if (!strstr(strtolower(join(':', $db_fields)), "lock_tables_priv")) {
527 echo join(':', $db_fields);
528 die("lock_tables_priv missing. The DB Admin must run mysql_fix_privilege_tables");
530 $row = $this->dbi->_backend->getRow($query);
531 if (isset($row[0]) and $row[0] == 'N') {
532 $this->dbi->genericSqlQuery("UPDATE mysql.db SET lock_tables_priv='Y'"
533 . " WHERE mysql.user='$username'");
534 $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
535 echo "mysql.db user='$username'", _("fixed"), "<br />\n";
538 $query = "SELECT lock_tables_priv FROM mysql.user WHERE user='$username'";
539 $row = $this->dbi->_backend->getRow($query);
540 if ($row and $row[0] == 'N') {
541 $this->dbi->genericSqlQuery("UPDATE mysql.user SET lock_tables_priv='Y'"
542 . " WHERE mysql.user='$username'");
543 $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
544 echo "mysql.user user='$username'", _("fixed"), "<br />\n";
546 echo ' <span style="color: red; font-weight: bold;">' . _("FAILED") . "</span>"
547 . " Neither mysql.db nor mysql.user has a user='$username'"
548 . " or the lock_tables_priv field",
551 echo _("OK"), "<br />\n";
554 echo _("OK"), "<br />\n";
556 //mysql_select_db($this->dbi->_backend->database(), $this->dbi->_backend->connection());
558 echo sprintf(_("version <em>%s</em> not affected"), $mysql_version), "<br />\n";
562 // 1.3.10 mysql requires page.id auto_increment
563 // mysql, mysqli or mysqlt
564 if ($this->phpwiki_version >= 1030.099 and substr($backend_type, 0, 5) == 'mysql'
565 and DATABASE_TYPE != 'PDO'
567 echo _("Check for mysql page.id auto_increment flag"), " ...";
568 assert(!empty($page_tbl));
569 $database = $this->dbi->_backend->database();
570 $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
571 $columns = mysql_num_fields($fields);
572 for ($i = 0; $i < $columns; $i++) {
573 if (mysql_field_name($fields, $i) == 'id') {
574 $flags = mysql_field_flags($fields, $i);
575 //DONE: something was wrong with ADODB here.
576 if (!strstr(strtolower($flags), "auto_increment")) {
577 echo "<b>", _("ADDING"), "</b>", " ... ";
578 // MODIFY col_def valid since mysql 3.22.16,
579 // older mysql's need CHANGE old_col col_def
580 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE id"
581 . " id INT NOT NULL AUTO_INCREMENT");
582 $fields = mysql_list_fields($database, $page_tbl);
583 if (!strstr(strtolower(mysql_field_flags($fields, $i)), "auto_increment"))
584 echo ' <span style="color: red; font-weight: bold;">' . _("FAILED") . "</span><br />\n";
586 echo _("OK"), "<br />\n";
588 echo _("OK"), "<br />\n";
593 mysql_free_result($fields);
596 // Check for mysql 4.1.x/5.0.0a binary search problem.
597 // http://bugs.mysql.com/bug.php?id=4398
598 // "select * from page where LOWER(pagename) like '%search%'" does not apply LOWER!
599 // Confirmed for 4.1.0alpha,4.1.3-beta,5.0.0a; not yet tested for 4.1.2alpha,
600 // On windows only, though utf8 would be useful elsewhere also.
601 // Illegal mix of collations (latin1_bin,IMPLICIT) and
602 // (utf8_general_ci, COERCIBLE) for operation '='])
603 if (isWindows() and substr($backend_type, 0, 5) == 'mysql') {
604 echo _("Check for mysql 4.1.x/5.0.0 binary search on windows problem"), " ...";
605 $mysql_version = $this->dbi->_backend->_serverinfo['version'];
606 if ($mysql_version < 401.0) {
607 echo sprintf(_("version <em>%s</em>"), $mysql_version), " ",
608 _("not affected"), "<br />\n";
609 } elseif ($mysql_version >= 401.6) { // FIXME: since which version?
610 $row = $this->dbi->_backend->getRow("SHOW CREATE TABLE $page_tbl");
611 $result = join(" ", $row);
612 if (strstr(strtolower($result), "character set")
613 and strstr(strtolower($result), "collate")
615 echo _("OK"), "<br />\n";
617 //SET CHARACTER SET latin1
619 if ($charset == 'iso-8859-1') $charset = 'latin1';
620 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename "
621 . "pagename VARCHAR(100) "
622 . "CHARACTER SET '$charset' COLLATE '$charset" . "_bin' NOT NULL");
623 echo sprintf(_("version <em>%s</em>"), $mysql_version),
624 " <b>", _("FIXED"), "</b>",
627 } elseif (DATABASE_TYPE != 'PDO') {
628 // check if already fixed
629 extract($this->dbi->_backend->_table_names);
630 assert(!empty($page_tbl));
631 $database = $this->dbi->_backend->database();
632 $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
633 $columns = mysql_num_fields($fields);
634 for ($i = 0; $i < $columns; $i++) {
635 if (mysql_field_name($fields, $i) == 'pagename') {
636 $flags = mysql_field_flags($fields, $i);
637 // I think it was fixed with 4.1.6, but I tested it only with 4.1.8
638 if ($mysql_version > 401.0 and $mysql_version < 401.6) {
639 // remove the binary flag
640 if (strstr(strtolower($flags), "binary")) {
641 // FIXME: on duplicate pagenames this will fail!
642 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename"
643 . " pagename VARCHAR(100) NOT NULL");
644 echo sprintf(_("version <em>%s</em>"), $mysql_version),
645 "<b>", _("FIXED"), "</b>"
654 if ($this->isSQL and ACCESS_LOG_SQL & 2) {
655 echo _("Check for ACCESS_LOG_SQL passwords in POST requests"), " ...";
656 // Don't display passwords in POST requests (up to 2005-02-04 12:03:20)
657 $res = $this->dbi->genericSqlIter("SELECT time_stamp, remote_host, " .
658 "request_args FROM ${prefix}accesslog WHERE request_args LIKE " .
659 "'%s:6:\"passwd\"%' AND request_args NOT LIKE '%s:6:\"passwd\";" .
660 "s:15:\"<not displayed>\"%'");
662 while ($row = $res->next()) {
663 $args = preg_replace("/(s:6:\"passwd\";s:15:\").*(\")/",
664 "$1<not displayed>$2", $row["request_args"]);
665 $ts = $row["time_stamp"];
666 $rh = $row["remote_host"];
667 $this->dbi->genericSqlQuery("UPDATE ${prefix}accesslog SET " .
668 "request_args='$args' WHERE time_stamp=$ts AND " .
669 "remote_host='$rh'");
673 echo "<b>", _("FIXED"), "</b>", "<br />\n";
675 echo _("OK"), "<br />\n";
677 if ($this->phpwiki_version >= 1030.13) {
678 echo _("Check for ACCESS_LOG_SQL remote_host varchar(50)"), " ...";
679 $database = $this->dbi->_backend->database();
680 $accesslog_tbl = $prefix . 'accesslog';
681 $fields = $this->dbi->_backend->listOfFields($database, $accesslog_tbl);
684 } elseif (strstr(strtolower(join(':', $sess_fields)), "remote_host")) {
685 // TODO: how to check size, already done?
686 echo "<b>", _("FIXING"), "remote_host</b>", " ... ";
687 $this->dbi->genericSqlQuery("ALTER TABLE $accesslog_tbl CHANGE remote_host VARCHAR(100)");
694 $this->_upgrade_cached_html();
696 if ($this->db_version < $this->current_db_version) {
697 $this->dbi->set_db_version($this->current_db_version);
698 $this->db_version = $this->dbi->get_db_version();
699 echo "db version: upgrade to ", $this->db_version, " ";
700 echo _("OK"), "<br />\n";
708 * Filter SQL missing permissions errors.
710 * A wrong DBADMIN user will not be able to connect
711 * @see _is_false_error, ErrorManager
714 function _dbpermission_filter($err)
716 if ($err->isWarning()) {
717 global $ErrorManager;
718 $this->error_caught = 1;
719 $ErrorManager->_postponed_errors[] = $err;
725 function _try_dbadmin_user($user, $passwd)
727 global $DBParams, $DBAuthParams;
728 $AdminParams = $DBParams;
729 if (DATABASE_TYPE == 'SQL')
730 $dsn = DB::parseDSN($AdminParams['dsn']);
732 $dsn = parseDSN($AdminParams['dsn']);
734 $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
740 $AdminParams['_tryroot_from_upgrade'] = 1;
741 // add error handler to warn about missing permissions for DBADMIN_USER
742 global $ErrorManager;
743 $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_dbpermission_filter'));
744 $this->error_caught = 0;
745 $this->dbi = WikiDB::open($AdminParams);
746 if (!$this->error_caught) return true;
747 // FAILED: redo our connection with the wikiuser
748 $this->dbi = WikiDB::open($DBParams);
749 $ErrorManager->flushPostponedErrors();
750 $ErrorManager->popErrorHandler();
756 if (!$this->isSQL) return;
758 /* SQLite never needs admin params */
759 $backend_type = $this->dbi->_backend->backendType();
760 if (substr($backend_type, 0, 6) == "sqlite") {
763 $dbadmin_user = 'root';
764 if ($dbadmin = $this->request->getArg('dbadmin')) {
765 $dbadmin_user = $dbadmin['user'];
766 if (isset($dbadmin['cancel'])) {
768 } elseif (!empty($dbadmin_user)) {
769 if ($this->_try_dbadmin_user($dbadmin['user'], $dbadmin['passwd']))
772 } elseif (DBADMIN_USER) {
773 if ($this->_try_dbadmin_user(DBADMIN_USER, DBADMIN_PASSWD)) {
777 // Check if the privileges are enough. Need CREATE and ALTER perms.
778 // And on windows: SELECT FROM mysql, possibly: UPDATE mysql.
779 $form = HTML::form(array("method" => "post",
780 "action" => $this->request->getPostURL(),
781 "accept-charset" => $GLOBALS['charset']),
782 HTML::p(_("Upgrade requires database privileges to CREATE and ALTER the phpwiki database."),
784 _("And on windows at least the privilege to SELECT FROM mysql, and possibly UPDATE mysql")),
785 HiddenInputs(array('action' => 'upgrade',
786 'overwrite' => $this->request->getArg('overwrite'))),
787 HTML::table(array("cellspacing" => 4),
788 HTML::tr(HTML::td(array('align' => 'right'),
789 _("DB admin user:")),
790 HTML::td(HTML::input(array('name' => "dbadmin[user]",
793 'value' => $dbadmin_user)))),
794 HTML::tr(HTML::td(array('align' => 'right'),
795 _("DB admin password:")),
796 HTML::td(HTML::input(array('name' => "dbadmin[passwd]",
797 'type' => 'password',
799 'maxlength' => 256)))),
800 HTML::tr(HTML::td(array('align' => 'center', 'colspan' => 2),
801 Button("submit:", _("Submit"), 'wikiaction'),
803 Button("submit:dbadmin[cancel]", _("Cancel"),
806 echo "</div><!-- content -->\n";
807 echo asXML(Template("bottom"));
808 echo "</body></html>\n";
809 $this->request->finish();
814 * if page.cached_html does not exists:
815 * put _cached_html from pagedata into a new seperate blob,
816 * not into the huge serialized string.
818 * It is only rarelely needed: for current page only, if-not-modified,
819 * but was extracetd for every simple page iteration.
821 function _upgrade_cached_html($verbose = true)
824 if (!$this->isSQL) return 0;
826 if ($this->phpwiki_version >= 1030.10) {
828 echo _("Check for extra page.cached_html column"), " ... ";
829 $database = $this->dbi->_backend->database();
830 extract($this->dbi->_backend->_table_names);
831 $fields = $this->dbi->_backend->listOfFields($database, $page_tbl);
833 echo _("SKIP"), "<br />\n";
836 if (!strstr(strtolower(join(':', $fields)), "cached_html")) {
838 echo "<b>", _("ADDING"), "</b>", " ... ";
839 $backend_type = $this->dbi->_backend->backendType();
840 if (substr($backend_type, 0, 5) == 'mysql')
841 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html MEDIUMBLOB");
843 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html BLOB");
845 echo "<b>", _("CONVERTING"), "</b>", " ... ";
846 $count = _convert_cached_html();
848 echo $count, " ", _("OK"), "<br />\n";
851 echo _("OK"), "<br />\n";
858 * move _cached_html for all pages from pagedata into a new seperate blob.
859 * decoupled from action=upgrade, so that it can be used by a WikiAdminUtils button also.
861 function _convert_cached_html()
864 if (!$this->isSQL) return 0;
865 //if (!in_array(DATABASE_TYPE, array('SQL','ADODB'))) return;
867 $pages = $this->dbi->getAllPages();
868 $cache =& $this->dbi->_cache;
870 extract($this->dbi->_backend->_table_names);
871 while ($page = $pages->next()) {
872 $pagename = $page->getName();
873 $data = $this->dbi->_backend->get_pagedata($pagename);
874 if (!empty($data['_cached_html'])) {
875 $cached_html = $data['_cached_html'];
876 $data['_cached_html'] = '';
877 $cache->update_pagedata($pagename, $data);
878 // store as blob, not serialized
879 $this->dbi->genericSqlQuery("UPDATE $page_tbl SET cached_html=? WHERE pagename=?",
880 array($cached_html, $pagename));
888 * upgrade to 1.3.13 link structure.
890 function _upgrade_relation_links($verbose = true)
892 if ($this->phpwiki_version >= 1030.12200610 and $this->isSQL) {
893 echo _("Check for relation field in link table"), " ...";
894 $database = $this->dbi->_backend->database();
895 $link_tbl = $prefix . 'link';
896 $fields = $this->dbi->_backend->listOfFields($database, $link_tbl);
899 } elseif (strstr(strtolower(join(':', $fields)), "link")) {
900 echo "<b>", _("ADDING"), " relation</b>", " ... ";
901 $this->dbi->genericSqlQuery("ALTER TABLE $link_tbl ADD relation INT DEFAULT 0;");
902 $this->dbi->genericSqlQuery("CREATE INDEX link_relation ON $link_tbl (relation);");
908 if ($this->phpwiki_version >= 1030.12200610) {
909 echo _("Rebuild entire database to upgrade relation links"), " ... ";
910 if (DATABASE_TYPE == 'dba') {
911 echo "<b>", _("CONVERTING"), " dba linktable</b>", "(~2 min, max 4 min) ... ";
914 $this->dbi->_backend->_linkdb->rebuild();
918 $this->dbi->_backend->rebuild();
920 echo _("OK"), "<br />\n";
924 function CheckPluginUpdate()
928 echo "<h2>", sprintf(_("Check for necessary %s updates"),
929 _("plugin argument")), "</h2>\n";
931 $this->_configUpdates = array();
932 $this->_configUpdates[] = new UpgradePluginEntry
933 ($this, array('key' => 'plugin_randompage_numpages',
934 'fixed_with' => 1012.0,
935 //'header' => _("change RandomPage pages => numpages"),
936 //'notice' =>_("found RandomPage plugin"),
937 'check_args' => array("plugin RandomPage pages",
938 "/(<\?\s*plugin\s+ RandomPage\s+)pages/",
940 $this->_configUpdates[] = new UpgradePluginEntry
941 ($this, array('key' => 'plugin_createtoc_position',
942 'fixed_with' => 1013.0,
943 //'header' => _("change CreateToc align => position"),
944 //'notice' =>_("found CreateToc plugin"),
945 'check_args' => array("plugin CreateToc align",
946 "/(<\?\s*plugin\s+ CreateToc[^\?]+)align/",
949 if (empty($this->_configUpdates)) return;
950 foreach ($this->_configUpdates as $update) {
951 $pages = $this->dbi->fullSearch($this->check_args[0]);
952 while ($page = $allpages->next()) {
953 $current = $page->getCurrentRevision();
954 $pagetext = $current->getPackedContent();
955 $update->check($this->check_args[1], $this->check_args[2], $pagetext, $page, $current);
965 * preg_replace over local file.
966 * Only line-orientated matches possible.
968 function fixLocalFile($match, $replace, $filename)
970 $o_filename = $filename;
971 if (!file_exists($filename))
972 $filename = FindFile($filename);
973 if (!file_exists($filename))
974 return array(false, sprintf(_("file %s not found"), $o_filename));
976 if (is_writable($filename)) {
977 $in = fopen($filename, "rb");
978 $out = fopen($tmp = tempnam(getUploadFilePath(), "cfg"), "wb");
980 $tmp = str_replace("/", "\\", $tmp);
981 // Detect the existing linesep at first line. fgets strips it even if 'rb'.
982 // Before we simply assumed \r\n on windows local files.
983 $s = fread($in, 1024);
985 $linesep = (substr_count($s, "\r\n") > substr_count($s, "\n")) ? "\r\n" : "\n";
986 //$linesep = isWindows() ? "\r\n" : "\n";
987 while ($s = fgets($in)) {
988 // =>php-5.0.1 can fill count
989 //$new = preg_replace($match, $replace, $s, -1, $count);
990 $new = preg_replace($match, $replace, $s);
992 $s = $new . $linesep;
1001 $reason = sprintf(_("%s not found in %s"), $match, $filename);
1003 return array($found, $reason);
1005 @unlink("$file.bak");
1006 @rename($file, "$file.bak");
1007 if (!rename($tmp, $file))
1008 return array(false, sprintf(_("couldn't move %s to %s"), $tmp, $filename));
1012 return array(false, sprintf(_("file %s is not writable"), $filename));
1016 function CheckConfigUpdate()
1018 echo "<h2>", sprintf(_("Check for necessary %s updates"),
1019 "config.ini"), "</h2>\n";
1020 $entry = new UpgradeConfigEntry
1021 ($this, array('key' => 'cache_control_none',
1022 'fixed_with' => 1012.0,
1023 'header' => sprintf(_("Check for %s"), "CACHE_CONTROL = NONE"),
1024 'applicable_args' => 'CACHE_CONTROL',
1025 'notice' => _("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'"),
1026 'check_args' => array("/^\s*CACHE_CONTROL\s*=\s*NONE/", "CACHE_CONTROL = NO_CACHE")));
1027 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1028 $this->_configUpdates[] = $entry;
1030 $entry = new UpgradeConfigEntry
1031 ($this, array('key' => 'group_method_none',
1032 'fixed_with' => 1012.0,
1033 'header' => sprintf(_("Check for %s"), "GROUP_METHOD = NONE"),
1034 'applicable_args' => 'GROUP_METHOD',
1035 'notice' => _("GROUP_METHOD is set to NONE, and must be changed to \"NONE\""),
1036 'check_args' => array("/^\s*GROUP_METHOD\s*=\s*NONE/", "GROUP_METHOD = \"NONE\"")));
1037 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1038 $this->_configUpdates[] = $entry;
1040 $entry = new UpgradeConfigEntry
1041 ($this, array('key' => 'blog_empty_default_prefix',
1042 'fixed_with' => 1013.0,
1043 'header' => sprintf(_("Check for %s"), "BLOG_EMPTY_DEFAULT_PREFIX"),
1044 'applicable_args' => 'BLOG_EMPTY_DEFAULT_PREFIX',
1045 'notice' => _("fix BLOG_EMPTY_DEFAULT_PREFIX into BLOG_DEFAULT_EMPTY_PREFIX"),
1046 'check_args' => array("/BLOG_EMPTY_DEFAULT_PREFIX\s*=/", "BLOG_DEFAULT_EMPTY_PREFIX =")));
1047 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined'));
1048 $this->_configUpdates[] = $entry;
1050 // TODO: find extra file updates
1051 if (empty($this->_configUpdates)) return;
1052 foreach ($this->_configUpdates as $update) {
1062 * Add an upgrade item to be checked.
1064 * @param $parent object The parent Upgrade class to inherit the version properties
1065 * @param $key string A short unique key to store success in the WikiDB
1066 * @param $fixed_with double @see phpwiki_version() number
1067 * @param $header string Optional header to be printed always even if not applicable
1068 * @param $applicable WikiCallback Optional callback boolean applicable()
1069 * @param $notice string Description of the check
1070 * @param $method WikiCallback Optional callback array method(array)
1071 * //param All other args are passed to $method
1073 function UpgradeEntry(&$parent, $params)
1075 $this->parent =& $parent; // get the properties db_version
1076 foreach (array('key' => 'required',
1077 // the wikidb stores the version when we actually fixed that.
1078 'fixed_with' => 'required',
1079 'header' => '', // always printed
1080 'applicable_cb' => null, // method to check if applicable
1081 'applicable_args' => array(), // might be the config name
1083 'check_cb' => null, // method to apply
1084 'check_args' => array())
1086 if (!isset($params[$k])) { // default
1087 if ($v == 'required') trigger_error("Required arg $k missing", E_USER_ERROR);
1088 else $this->{$k} = $v;
1090 $this->{$k} = $params[$k];
1093 if (!is_array($this->applicable_args)) // single arg convenience shortcut
1094 $this->applicable_args = array($this->applicable_args);
1095 if (!is_array($this->check_args)) // single arg convenience shortcut
1096 $this->check_args = array($this->check_args);
1097 if ($this->notice === '' and count($this->applicable_args) > 0)
1098 $this->notice = 'Check for ' . join(', ', $this->applicable_args);
1099 $this->_db_key = "_upgrade";
1100 $this->upgrade = $this->parent->dbi->get($this->_db_key);
1104 function setApplicableCb($object)
1106 $this->applicable_cb =& $object;
1109 function _check_if_already_fixed()
1112 if (!isset($this->upgrade['name'])) return false;
1113 // override with force?
1114 if ($this->parent->request->getArg('force')) return false;
1115 // already fixed and with an ok version
1116 if ($this->upgrade['name'] >= $this->fixed_with) return $this->upgrade['name'];
1117 // already fixed but with an older version. do it again.
1123 // store in db no to fix again
1124 $this->upgrade['name'] = $this->parent->phpwiki_version;
1125 $this->parent->dbi->set($this->_db_key, $this->upgrade);
1126 echo "<b>", _("FIXED"), "</b>";
1127 if (isset($this->reason))
1128 echo _(": "), $this->reason;
1136 echo '<span style="color: red; font-weight: bold; ">' . _("FAILED") . "</span>";
1137 if (isset($this->reason))
1138 echo _(": "), $this->reason;
1146 if (isset($this->silent_skip)) return true;
1147 echo _(" Skipped"), ".<br />\n";
1152 function check($args = null)
1154 if ($this->header) echo $this->header, ' ... ';
1155 if ($when = $this->_check_if_already_fixed()) {
1156 // be totally silent if no header is defined.
1157 if ($this->header) echo _("fixed with"), " ", $when, "<br />\n";
1161 if (is_object($this->applicable_cb)) {
1162 if (!$this->applicable_cb->call_array($this->applicable_args))
1163 return $this->skip();
1165 if ($this->notice) {
1168 echo $this->notice, " ";
1171 if (!is_null($args)) $this->check_args =& $args;
1172 if (is_object($this->check_cb))
1173 $do = $this->method_cb->call_array($this->check_args);
1175 $do = $this->default_method($this->check_args);
1176 if (is_array($do)) {
1177 $this->reason = $do[1];
1180 return $do ? $this->pass() : $this->fail();
1182 } // class UpgradeEntry
1184 class UpgradeConfigEntry extends UpgradeEntry
1186 function _applicable_defined()
1188 return (boolean)defined($this->applicable_args[0]);
1191 function _applicable_defined_and_empty()
1193 $const = $this->applicable_args[0];
1194 return (boolean)(defined($const) and !constant($const));
1197 function default_method($args)
1200 $replace = $args[1];
1201 return $this->parent->fixLocalFile($match, $replace, "config/config.ini");
1203 } // class UpdateConfigEntry
1205 /* This is different */
1206 class UpgradePluginEntry extends UpgradeEntry
1210 * check all pages for a plugin match
1212 var $silent_skip = 1;
1214 function default_method(&$args)
1217 $replace = $args[1];
1218 $pagetext =& $args[2];
1220 $current =& $args[4];
1221 if (preg_match($match, $pagetext)) {
1222 echo $page->getName(), " ", $this->notice, " ... ";
1223 if ($newtext = preg_replace($match, $replace, $pagetext)) {
1224 $meta = $current->_data;
1225 $meta['summary'] = "upgrade: " . $this->header;
1226 $page->save($newtext, $current->getVersion() + 1, $meta);
1233 } // class UpdatePluginEntry
1236 * fix custom themes which are not in our distribution
1237 * this should be optional
1239 class UpgradeThemeEntry extends UpgradeEntry
1242 function default_method(&$args)
1245 $replace = $args[1];
1246 $template = $args[2];
1249 function fixThemeTemplate($match, $new, $template)
1251 // for all custom themes
1252 $ourthemes = explode(":", "blog:Crao:default:Hawaiian:MacOSX:MonoBook:Portland:shamino_com:SpaceWiki:wikilens:Wordpress");
1253 $themedir = NormalizeLocalFileName("themes");
1254 $dh = opendir($themedir);
1255 while ($r = readdir($dh)) {
1256 if (filetype($r) == 'dir' and $r[0] != '.' and !is_array($r, $ourthemes))
1257 $customthemes[] = $r;
1261 foreach ($customthemes as $customtheme) {
1262 $template = FindFile("themes/$customtheme/templates/$template");
1263 $do = $this->parent->fixLocalFile($match, $new, template);
1266 $errors .= $do[1] . " ";
1270 return array($success, $errors);
1277 * Upgrade: Base class for multipage worksteps
1278 * identify, validate, display options, next step
1283 // TODO: At which step are we?
1284 // validate and do it again or go on with next step.
1286 /** entry function from lib/main.php
1288 function DoUpgrade(&$request)
1291 if (!$request->_user->isAdmin()) {
1292 $request->_notAuthorized(WIKIAUTH_ADMIN);
1294 HTML::div(array('class' => 'disabled-plugin'),
1295 fmt("Upgrade disabled: user != isAdmin")));
1298 // TODO: StartLoadDump should turn on implicit_flush.
1299 @ini_set("implicit_flush", true);
1300 StartLoadDump($request, _("Upgrading this PhpWiki"));
1301 $upgrade = new Upgrade($request);
1302 //if (!$request->getArg('noindex'))
1303 // CheckOldIndexUpdate($request); // index.php => config.ini to upgrade from < 1.3.10
1304 if (!$request->getArg('nodb'))
1305 $upgrade->CheckDatabaseUpdate($request); // first check cached_html and friends
1306 if (!$request->getArg('nopgsrc')) {
1307 $upgrade->CheckPgsrcUpdate($request);
1308 $upgrade->CheckActionPageUpdate($request);
1310 if (!$request->getArg('noplugin'))
1311 $upgrade->CheckPluginUpdate($request);
1312 if (!$request->getArg('noconfig'))
1313 $upgrade->CheckConfigUpdate($request);
1314 // This is optional and should be linked. In EndLoadDump or PhpWikiAdministration?
1315 //if ($request->getArg('theme'))
1316 // $upgrade->CheckThemeUpdate($request);
1317 EndLoadDump($request);
1323 // c-basic-offset: 4
1324 // c-hanging-comment-ender-p: nil
1325 // indent-tabs-mode: nil