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.") . " " . _("Skipped."), "<br />\n";
80 $page = $this->dbi->getPage($pagename);
81 if ($page->exists()) {
82 // check mtime: update automatically if pgsrc is newer
83 $rev = $page->getCurrentRevision();
84 $page_mtime = $rev->get('mtime');
85 $data = implode("", file($path . "/" . $filename));
86 if (($parts = ParseMimeifiedPages($data))) {
87 usort($parts, 'SortByPageVersion');
89 $pageinfo = $parts[0];
90 $stat = stat($path . "/" . $filename);
92 if (isset($pageinfo['versiondata']['mtime']))
93 $new_mtime = $pageinfo['versiondata']['mtime'];
94 if (!$new_mtime and isset($pageinfo['versiondata']['lastmodified']))
95 $new_mtime = $pageinfo['versiondata']['lastmodified'];
96 if (!$new_mtime and isset($pageinfo['pagedata']['date']))
97 $new_mtime = $pageinfo['pagedata']['date'];
99 $new_mtime = $stat[9];
100 if ($new_mtime > $page_mtime) {
101 echo "$path/$pagename: ", _("newer than the existing page."),
102 _(" replace "), "($new_mtime > $page_mtime)", "<br />\n";
103 LoadAny($this->request, $path . "/" . $filename);
106 echo "$path/$pagename: " . _("older than the existing page.") . " " . _("Skipped."), "<br />\n";
109 echo "$path/$pagename: " . _("unknown format.") . " " . _("Skipped.") . "<br />\n";
112 echo sprintf(_("%s does not exist"), $pagename), "<br />\n";
113 LoadAny($this->request, $path . "/" . $filename);
118 function CheckActionPageUpdate()
120 echo "<h2>", sprintf(_("Check for necessary %s updates"), _("ActionPage")), "</h2>\n";
121 // 1.3.13 before we pull in all missing pages, we rename existing ones
122 $this->_rename_page_helper(_("_AuthInfo"), _("DebugAuthInfo"));
123 // this is in some templates. so we keep the old name
124 //$this->_rename_page_helper($this->dbi, _("DebugInfo"), _("DebugBackendInfo"));
125 $this->_rename_page_helper(_("_GroupInfo"), _("GroupAuthInfo")); //never officially existed
126 $this->_rename_page_helper("InterWikiKarte", "InterWikiListe"); // german only
128 $path = FindFile('pgsrc');
129 $pgsrc = new fileSet($path);
130 // most actionpages have the same name as the plugin
131 $loc_path = FindLocalizedFile('pgsrc');
132 foreach ($pgsrc->getFiles() as $filename) {
133 if (substr($filename, -1, 1) == '~') continue;
134 if (substr($filename, -5, 5) == '.orig') continue;
135 $pagename = urldecode($filename);
136 if (isActionPage($pagename)) {
137 $translation = gettext($pagename);
138 if ($translation == $pagename)
139 $this->doPgsrcUpdate($pagename, $path, $filename);
140 elseif (FindLocalizedFile('pgsrc/' . urlencode($translation), 1))
141 $this->doPgsrcUpdate($translation, $loc_path, urlencode($translation)); else
142 $this->doPgsrcUpdate($pagename, $path, $filename);
147 // see loadsave.php for saving new pages.
148 function CheckPgsrcUpdate()
150 // Check some theme specific pgsrc files (blog, wikilens, fusionforge, custom).
151 // We check theme specific pgsrc first in case the page is present in both
152 // theme specific and global pgsrc
154 $path = $WikiTheme->file("pgsrc");
155 // TBD: the call to fileSet prints a warning:
156 // Notice: Unable to open directory 'themes/MonoBook/pgsrc' for reading
157 $pgsrc = new fileSet($path);
158 if ($pgsrc->getFiles()) {
159 echo "<h2>", sprintf(_("Check for necessary theme %s updates"),
161 foreach ($pgsrc->getFiles() as $filename) {
162 if (substr($filename, -1, 1) == '~') continue;
163 if (substr($filename, -5, 5) == '.orig') continue;
164 $pagename = urldecode($filename);
165 $this->doPgsrcUpdate($pagename, $path, $filename);
169 echo "<h2>", sprintf(_("Check for necessary %s updates"),
171 if ($this->db_version < 1030.12200612) {
172 echo "<h4>", _("rename to Help: pages"), "</h4>\n";
174 $path = FindLocalizedFile(WIKI_PGSRC);
175 $pgsrc = new fileSet($path);
176 // fixme: verification, ...
177 foreach ($pgsrc->getFiles() as $filename) {
178 if (substr($filename, -1, 1) == '~') continue;
179 if (substr($filename, -5, 5) == '.orig') continue;
180 $pagename = urldecode($filename);
181 if (!isActionPage($filename)) {
182 // There're a lot of now unneeded pages around.
183 // At first rename the BlaPlugin pages to Help/<pagename> and then to the update.
184 if ($this->db_version < 1030.12200612) {
185 $this->_rename_to_help_page($pagename);
187 $this->doPgsrcUpdate($pagename, $path, $filename);
192 function _rename_page_helper($oldname, $pagename)
194 echo sprintf(_("rename %s to %s"), $oldname, $pagename), " ...";
195 if ($this->dbi->isWikiPage($oldname) and !$this->dbi->isWikiPage($pagename)) {
196 if ($this->dbi->_backend->rename_page($oldname, $pagename)) {
197 echo _("OK"), " <br />\n";
199 echo ' <span style="color: red; font-weight: bold;">' . _("FAILED") . "</span><br />\n";
202 echo " " . _("Skipped.") . "<br />\n";
206 function _rename_to_help_page($pagename)
208 $newprefix = _("Help") . "/";
209 if (substr($pagename, 0, strlen($newprefix)) != $newprefix) return;
210 $oldname = substr($pagename, strlen($newprefix));
211 $this->_rename_page_helper($oldname, $pagename);
215 * TODO: Search table definition in appropriate schema
217 * Supported: mysql and generic SQL, for ADODB and PearDB.
219 function installTable($table, $backend_type)
222 if (!$this->isSQL) return;
223 echo _("MISSING"), " ... \n";
224 $backend = &$this->dbi->_backend->_dbh;
226 $schema = findFile("schemas/${backend_type}.sql");
228 echo " ",_("FAILED"),": ",sprintf(_("no schema %s found"),
229 "schemas/${backend_type}.sql")," ... <br />\n";
233 extract($this->dbi->_backend->_table_names);
234 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
237 assert($session_tbl);
238 if ($backend_type == 'mysql') {
239 $this->dbi->genericSqlQuery("
240 CREATE TABLE $session_tbl (
241 sess_id CHAR(32) NOT NULL DEFAULT '',
242 sess_data BLOB NOT NULL,
243 sess_date INT UNSIGNED NOT NULL,
244 sess_ip CHAR(15) NOT NULL,
245 PRIMARY KEY (sess_id),
249 $this->dbi->genericSqlQuery("
250 CREATE TABLE $session_tbl (
251 sess_id CHAR(32) NOT NULL DEFAULT '',
252 sess_data " . ($backend_type == 'pgsql' ? 'TEXT' : 'BLOB') . " NOT NULL,
254 sess_ip CHAR(15) NOT NULL
256 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX sess_id ON $session_tbl (sess_id)");
258 $this->dbi->genericSqlQuery("CREATE INDEX sess_date on session (sess_date)");
259 echo " ", _("CREATED");
262 $pref_tbl = $prefix . 'pref';
263 if ($backend_type == 'mysql') {
264 $this->dbi->genericSqlQuery("
265 CREATE TABLE $pref_tbl (
266 userid CHAR(48) BINARY NOT NULL UNIQUE,
267 prefs TEXT NULL DEFAULT '',
271 $this->dbi->genericSqlQuery("
272 CREATE TABLE $pref_tbl (
273 userid CHAR(48) NOT NULL,
274 prefs TEXT NULL DEFAULT ''
276 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX userid ON $pref_tbl (userid)");
278 echo " ", _("CREATED");
281 $member_tbl = $prefix . 'member';
282 if ($backend_type == 'mysql') {
283 $this->dbi->genericSqlQuery("
284 CREATE TABLE $member_tbl (
285 userid CHAR(48) BINARY NOT NULL,
286 groupname CHAR(48) BINARY NOT NULL DEFAULT 'users',
291 $this->dbi->genericSqlQuery("
292 CREATE TABLE $member_tbl (
293 userid CHAR(48) NOT NULL,
294 groupname CHAR(48) NOT NULL DEFAULT 'users'
296 $this->dbi->genericSqlQuery("CREATE INDEX userid ON $member_tbl (userid)");
297 $this->dbi->genericSqlQuery("CREATE INDEX groupname ON $member_tbl (groupname)");
299 echo " ", _("CREATED");
302 $rating_tbl = $prefix . 'rating';
303 if ($backend_type == 'mysql') {
304 $this->dbi->genericSqlQuery("
305 CREATE TABLE $rating_tbl (
306 dimension INT(4) NOT NULL,
307 raterpage INT(11) NOT NULL,
308 rateepage INT(11) NOT NULL,
309 ratingvalue FLOAT NOT NULL,
310 rateeversion INT(11) NOT NULL,
311 tstamp TIMESTAMP(14) NOT NULL,
312 PRIMARY KEY (dimension, raterpage, rateepage)
315 $this->dbi->genericSqlQuery("
316 CREATE TABLE $rating_tbl (
317 dimension INT(4) NOT NULL,
318 raterpage INT(11) NOT NULL,
319 rateepage INT(11) NOT NULL,
320 ratingvalue FLOAT NOT NULL,
321 rateeversion INT(11) NOT NULL,
322 tstamp TIMESTAMP(14) NOT NULL
324 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX rating"
325 . " ON $rating_tbl (dimension, raterpage, rateepage)");
327 echo " ", _("CREATED");
330 $log_tbl = $prefix . 'accesslog';
331 // fields according to http://www.outoforder.cc/projects/apache/mod_log_sql/docs-2.0/#id2756178
333 A User Agent agent varchar(255) Mozilla/4.0 (compat; MSIE 6.0; Windows)
334 a CGi request arguments request_args varchar(255) user=Smith&cart=1231&item=532
335 b Bytes transfered bytes_sent int unsigned 32561
336 c??? Text of cookie cookie varchar(255) Apache=sdyn.fooonline.net 1300102700823
337 f Local filename requested request_file varchar(255) /var/www/html/books-cycroad.html
338 H HTTP request_protocol request_protocol varchar(10) HTTP/1.1
339 h Name of remote host remote_host varchar(50) blah.foobar.com
340 I Request ID (from modd_unique_id) id char(19) POlFcUBRH30AAALdBG8
341 l Ident user info remote_logname varcgar(50) bobby
342 M Machine ID??? machine_id varchar(25) web01
343 m HTTP request method request_method varchar(10) GET
344 P httpd cchild PID child_pid smallint unsigned 3215
345 p http port server_port smallint unsigned 80
346 R Referer referer varchar(255) http://www.biglinks4u.com/linkpage.html
347 r Request in full form request_line varchar(255) GET /books-cycroad.html HTTP/1.1
348 S Time of request in UNIX time_t format time_stamp int unsigned 1005598029
349 T Seconds to service request request_duration smallint unsigned 2
350 t Time of request in human format request_time char(28) [02/Dec/2001:15:01:26 -0800]
351 U Request in simple form request_uri varchar(255) /books-cycroad.html
352 u User info from HTTP auth remote_user varchar(50) bobby
353 v Virtual host servicing the request virtual_host varchar(255)
355 $this->dbi->genericSqlQuery("
356 CREATE TABLE $log_tbl (
357 time_stamp int unsigned,
358 remote_host varchar(100),
359 remote_user varchar(50),
360 request_method varchar(10),
361 request_line varchar(255),
362 request_args varchar(255),
363 request_uri varchar(255),
364 request_time char(28),
365 status smallint unsigned,
366 bytes_sent smallint unsigned,
367 referer varchar(255),
369 request_duration float
371 $this->dbi->genericSqlQuery("CREATE INDEX log_time ON $log_tbl (time_stamp)");
372 $this->dbi->genericSqlQuery("CREATE INDEX log_host ON $log_tbl (remote_host)");
373 echo " ", _("CREATED");
380 * Update from ~1.3.4 to current.
381 * tables: Only session, user, pref and member
382 * jeffs-hacks database api (around 1.3.2) later:
383 * people should export/import their pages if using that old versions.
385 function CheckDatabaseUpdate()
387 global $DBAuthParams, $DBParams;
389 echo "<h2>", sprintf(_("Check for necessary %s updates"),
391 " - ", DATABASE_TYPE, "</h2>\n";
392 $dbadmin = $this->request->getArg('dbadmin');
395 if (isset($dbadmin['cancel'])) {
396 echo _("CANCEL"), " <br />\n";
400 echo "db version: we want ", $this->current_db_version, "\n<br />";
401 echo "db version: we have ", $this->db_version, "\n<br />";
402 if ($this->db_version >= $this->current_db_version) {
403 echo _("OK"), "<br />\n";
407 $backend_type = $this->dbi->_backend->backendType();
409 echo "<h4>", _("Backend type: "), $backend_type, "</h4>\n";
410 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
411 $tables = $this->dbi->_backend->listOfTables();
412 foreach (explode(':', 'session:pref:member') as $table) {
413 echo sprintf(_("Check for table %s"), $table), " ...";
414 if (!in_array($prefix . $table, $tables)) {
415 $this->installTable($table, $backend_type);
417 echo _("OK"), " <br />\n";
422 if ($this->phpwiki_version >= 1030.12200612 and $this->db_version < 1030.13) {
423 if ($this->isSQL and preg_match("/(pgsql|postgres)/", $backend_type)) {
424 trigger_error("You need to upgrade to schema/psql-initialize.sql manually!",
426 // $this->_upgrade_psql_tsearch2();
428 $this->_upgrade_relation_links();
431 if (ACCESS_LOG_SQL and $this->isSQL) {
432 $table = "accesslog";
433 echo sprintf(_("Check for table %s"), $table), " ...";
434 if (!in_array($prefix . $table, $tables)) {
435 $this->installTable($table, $backend_type);
437 echo _("OK"), " <br />\n";
440 if ($this->isSQL and (class_exists("RatingsUserFactory") or $this->dbi->isWikiPage(_("RateIt")))) {
442 echo sprintf(_("Check for table %s"), $table), " ...";
443 if (!in_array($prefix . $table, $tables)) {
444 $this->installTable($table, $backend_type);
446 echo _("OK"), " <br />\n";
449 $backend = &$this->dbi->_backend->_dbh;
451 extract($this->dbi->_backend->_table_names);
453 // 1.3.8 added session.sess_ip
454 if ($this->isSQL and $this->phpwiki_version >= 1030.08 and USE_DB_SESSION
455 and isset($this->request->_dbsession)
457 echo _("Check for new session.sess_ip column"), " ... ";
458 $database = $this->dbi->_backend->database();
459 assert(!empty($DBParams['db_session_table']));
460 $session_tbl = $prefix . $DBParams['db_session_table'];
461 $sess_fields = $this->dbi->_backend->listOfFields($database, $session_tbl);
464 } elseif (!strstr(strtolower(join(':', $sess_fields)), "sess_ip")) {
465 // TODO: postgres test (should be able to add columns at the end, but not in between)
466 echo "<b>", _("ADDING"), "</b>", " ... ";
467 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL");
468 $this->dbi->genericSqlQuery("CREATE INDEX sess_date ON $session_tbl (sess_date)");
473 if (substr($backend_type, 0, 5) == 'mysql') {
474 // upgrade to 4.1.8 destroyed my session table:
475 // sess_id => varchar(10), sess_data => varchar(5). For others obviously also.
476 echo _("Check for mysql session.sess_id sanity"), " ... ";
477 $result = $this->dbi->genericSqlQuery("DESCRIBE $session_tbl");
478 if (DATABASE_TYPE == 'SQL') {
479 $iter = new WikiDB_backend_PearDB_generic_iter($backend, $result);
480 } elseif (DATABASE_TYPE == 'ADODB') {
481 $iter = new WikiDB_backend_ADODB_generic_iter($backend, $result,
482 array("Field", "Type", "Null", "Key", "Default", "Extra"));
483 } elseif (DATABASE_TYPE == 'PDO') {
484 $iter = new WikiDB_backend_PDO_generic_iter($backend, $result);
486 while ($col = $iter->next()) {
487 if ($col["Field"] == 'sess_id' and !strstr(strtolower($col["Type"]), 'char(32)')) {
488 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_id"
489 . " sess_id CHAR(32) NOT NULL");
490 echo "sess_id ", $col["Type"], " ", _("fixed"), " => CHAR(32) ";
492 if ($col["Field"] == 'sess_ip' and !strstr(strtolower($col["Type"]), 'char(15)')) {
493 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_ip"
494 . " sess_ip CHAR(15) NOT NULL");
495 echo "sess_ip ", $col["Type"], " ", _("fixed"), " => CHAR(15) ";
498 echo _("OK"), "<br />\n";
503 ALTER TABLE link ADD relation INT DEFAULT 0;
504 CREATE INDEX linkrelation ON link (relation);
507 // mysql >= 4.0.4 requires LOCK TABLE privileges
508 if (substr($backend_type, 0, 5) == 'mysql') {
509 echo _("Check for mysql LOCK TABLE privilege"), " ...";
510 $mysql_version = $this->dbi->_backend->_serverinfo['version'];
511 if ($mysql_version > 400.40) {
512 if (!empty($this->dbi->_backend->_parsedDSN))
513 $parseDSN = $this->dbi->_backend->_parsedDSN;
514 elseif (function_exists('parseDSN')) // ADODB or PDO
515 $parseDSN = parseDSN($DBParams['dsn']); else // pear
516 $parseDSN = DB::parseDSN($DBParams['dsn']);
517 $username = $this->dbi->_backend->qstr($parseDSN['username']);
519 $query = "SELECT lock_tables_priv FROM mysql.db WHERE user='$username'";
520 //mysql_select_db("mysql", $this->dbi->_backend->connection());
521 $db_fields = $this->dbi->_backend->listOfFields("mysql", "db");
522 if (!strstr(strtolower(join(':', $db_fields)), "lock_tables_priv")) {
523 echo join(':', $db_fields);
524 die("lock_tables_priv missing. The DB Admin must run mysql_fix_privilege_tables");
526 $row = $this->dbi->_backend->getRow($query);
527 if (isset($row[0]) and $row[0] == 'N') {
528 $this->dbi->genericSqlQuery("UPDATE mysql.db SET lock_tables_priv='Y'"
529 . " WHERE mysql.user='$username'");
530 $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
531 echo "mysql.db user='$username'", _("fixed"), "<br />\n";
534 $query = "SELECT lock_tables_priv FROM mysql.user WHERE user='$username'";
535 $row = $this->dbi->_backend->getRow($query);
536 if ($row and $row[0] == 'N') {
537 $this->dbi->genericSqlQuery("UPDATE mysql.user SET lock_tables_priv='Y'"
538 . " WHERE mysql.user='$username'");
539 $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
540 echo "mysql.user user='$username'", _("fixed"), "<br />\n";
542 echo ' <span style="color: red; font-weight: bold;">' . _("FAILED") . "</span>"
543 . " Neither mysql.db nor mysql.user has a user='$username'"
544 . " or the lock_tables_priv field",
547 echo _("OK"), "<br />\n";
550 echo _("OK"), "<br />\n";
552 //mysql_select_db($this->dbi->_backend->database(), $this->dbi->_backend->connection());
554 echo sprintf(_("version <em>%s</em> not affected"), $mysql_version), "<br />\n";
558 // 1.3.10 mysql requires page.id auto_increment
559 // mysql, mysqli or mysqlt
560 if ($this->phpwiki_version >= 1030.099 and substr($backend_type, 0, 5) == 'mysql'
561 and DATABASE_TYPE != 'PDO'
563 echo _("Check for mysql page.id auto_increment flag"), " ...";
564 assert(!empty($page_tbl));
565 $database = $this->dbi->_backend->database();
566 $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
567 $columns = mysql_num_fields($fields);
568 for ($i = 0; $i < $columns; $i++) {
569 if (mysql_field_name($fields, $i) == 'id') {
570 $flags = mysql_field_flags($fields, $i);
571 //DONE: something was wrong with ADODB here.
572 if (!strstr(strtolower($flags), "auto_increment")) {
573 echo "<b>", _("ADDING"), "</b>", " ... ";
574 // MODIFY col_def valid since mysql 3.22.16,
575 // older mysql's need CHANGE old_col col_def
576 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE id"
577 . " id INT NOT NULL AUTO_INCREMENT");
578 $fields = mysql_list_fields($database, $page_tbl);
579 if (!strstr(strtolower(mysql_field_flags($fields, $i)), "auto_increment"))
580 echo ' <span style="color: red; font-weight: bold;">' . _("FAILED") . "</span><br />\n";
582 echo _("OK"), "<br />\n";
584 echo _("OK"), "<br />\n";
589 mysql_free_result($fields);
592 // Check for mysql 4.1.x/5.0.0a binary search problem.
593 // http://bugs.mysql.com/bug.php?id=4398
594 // "select * from page where LOWER(pagename) like '%search%'" does not apply LOWER!
595 // Confirmed for 4.1.0alpha,4.1.3-beta,5.0.0a; not yet tested for 4.1.2alpha,
596 // On windows only, though utf8 would be useful elsewhere also.
597 // Illegal mix of collations (latin1_bin,IMPLICIT) and
598 // (utf8_general_ci, COERCIBLE) for operation '='])
599 if (isWindows() and substr($backend_type, 0, 5) == 'mysql') {
600 echo _("Check for mysql 4.1.x/5.0.0 binary search on Windows problem"), " ...";
601 $mysql_version = $this->dbi->_backend->_serverinfo['version'];
602 if ($mysql_version < 401.0) {
603 echo sprintf(_("version <em>%s</em>"), $mysql_version), " ",
604 _("not affected"), "<br />\n";
605 } elseif ($mysql_version >= 401.6) { // FIXME: since which version?
606 $row = $this->dbi->_backend->getRow("SHOW CREATE TABLE $page_tbl");
607 $result = join(" ", $row);
608 if (strstr(strtolower($result), "character set")
609 and strstr(strtolower($result), "collate")
611 echo _("OK"), "<br />\n";
613 //SET CHARACTER SET latin1
615 if ($charset == 'iso-8859-1') $charset = 'latin1';
616 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename "
617 . "pagename VARCHAR(100) "
618 . "CHARACTER SET '$charset' COLLATE '$charset" . "_bin' NOT NULL");
619 echo sprintf(_("version <em>%s</em>"), $mysql_version),
620 " <b>", _("FIXED"), "</b>",
623 } elseif (DATABASE_TYPE != 'PDO') {
624 // check if already fixed
625 extract($this->dbi->_backend->_table_names);
626 assert(!empty($page_tbl));
627 $database = $this->dbi->_backend->database();
628 $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
629 $columns = mysql_num_fields($fields);
630 for ($i = 0; $i < $columns; $i++) {
631 if (mysql_field_name($fields, $i) == 'pagename') {
632 $flags = mysql_field_flags($fields, $i);
633 // I think it was fixed with 4.1.6, but I tested it only with 4.1.8
634 if ($mysql_version > 401.0 and $mysql_version < 401.6) {
635 // remove the binary flag
636 if (strstr(strtolower($flags), "binary")) {
637 // FIXME: on duplicate pagenames this will fail!
638 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename"
639 . " pagename VARCHAR(100) NOT NULL");
640 echo sprintf(_("version <em>%s</em>"), $mysql_version),
641 "<b>", _("FIXED"), "</b>"
650 if ($this->isSQL and ACCESS_LOG_SQL & 2) {
651 echo _("Check for ACCESS_LOG_SQL passwords in POST requests"), " ...";
652 // Don't display passwords in POST requests (up to 2005-02-04 12:03:20)
653 $res = $this->dbi->genericSqlIter("SELECT time_stamp, remote_host, " .
654 "request_args FROM ${prefix}accesslog WHERE request_args LIKE " .
655 "'%s:6:\"passwd\"%' AND request_args NOT LIKE '%s:6:\"passwd\";" .
656 "s:15:\"<not displayed>\"%'");
658 while ($row = $res->next()) {
659 $args = preg_replace("/(s:6:\"passwd\";s:15:\").*(\")/",
660 "$1<not displayed>$2", $row["request_args"]);
661 $ts = $row["time_stamp"];
662 $rh = $row["remote_host"];
663 $this->dbi->genericSqlQuery("UPDATE ${prefix}accesslog SET " .
664 "request_args='$args' WHERE time_stamp=$ts AND " .
665 "remote_host='$rh'");
669 echo "<b>", _("FIXED"), "</b>", "<br />\n";
671 echo _("OK"), "<br />\n";
673 if ($this->phpwiki_version >= 1030.13) {
674 echo _("Check for ACCESS_LOG_SQL remote_host varchar(50)"), " ...";
675 $database = $this->dbi->_backend->database();
676 $accesslog_tbl = $prefix . 'accesslog';
677 $fields = $this->dbi->_backend->listOfFields($database, $accesslog_tbl);
680 } elseif (strstr(strtolower(join(':', $sess_fields)), "remote_host")) {
681 // TODO: how to check size, already done?
682 echo "<b>", _("FIXING"), "remote_host</b>", " ... ";
683 $this->dbi->genericSqlQuery("ALTER TABLE $accesslog_tbl CHANGE remote_host VARCHAR(100)");
690 $this->_upgrade_cached_html();
692 if ($this->db_version < $this->current_db_version) {
693 $this->dbi->set_db_version($this->current_db_version);
694 $this->db_version = $this->dbi->get_db_version();
695 echo "db version: upgrade to ", $this->db_version, " ";
696 echo _("OK"), "<br />\n";
704 * Filter SQL missing permissions errors.
706 * A wrong DBADMIN user will not be able to connect
707 * @see _is_false_error, ErrorManager
710 function _dbpermission_filter($err)
712 if ($err->isWarning()) {
713 global $ErrorManager;
714 $this->error_caught = 1;
715 $ErrorManager->_postponed_errors[] = $err;
721 function _try_dbadmin_user($user, $passwd)
723 global $DBParams, $DBAuthParams;
724 $AdminParams = $DBParams;
725 if (DATABASE_TYPE == 'SQL')
726 $dsn = DB::parseDSN($AdminParams['dsn']);
728 $dsn = parseDSN($AdminParams['dsn']);
730 $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
736 $AdminParams['_tryroot_from_upgrade'] = 1;
737 // add error handler to warn about missing permissions for DBADMIN_USER
738 global $ErrorManager;
739 $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_dbpermission_filter'));
740 $this->error_caught = 0;
741 $this->dbi = WikiDB::open($AdminParams);
742 if (!$this->error_caught) return true;
743 // FAILED: redo our connection with the wikiuser
744 $this->dbi = WikiDB::open($DBParams);
745 $ErrorManager->flushPostponedErrors();
746 $ErrorManager->popErrorHandler();
752 if (!$this->isSQL) return;
754 /* SQLite never needs admin params */
755 $backend_type = $this->dbi->_backend->backendType();
756 if (substr($backend_type, 0, 6) == "sqlite") {
759 $dbadmin_user = 'root';
760 if ($dbadmin = $this->request->getArg('dbadmin')) {
761 $dbadmin_user = $dbadmin['user'];
762 if (isset($dbadmin['cancel'])) {
764 } elseif (!empty($dbadmin_user)) {
765 if ($this->_try_dbadmin_user($dbadmin['user'], $dbadmin['passwd']))
768 } elseif (DBADMIN_USER) {
769 if ($this->_try_dbadmin_user(DBADMIN_USER, DBADMIN_PASSWD)) {
773 // Check if the privileges are enough. Need CREATE and ALTER perms.
774 // And on Windows: SELECT FROM mysql, possibly: UPDATE mysql.
775 $form = HTML::form(array("method" => "post",
776 "action" => $this->request->getPostURL(),
777 "accept-charset" => $GLOBALS['charset']),
778 HTML::p(_("Upgrade requires database privileges to CREATE and ALTER the phpwiki database."),
780 _("And on Windows at least the privilege to SELECT FROM mysql, and possibly UPDATE mysql")),
781 HiddenInputs(array('action' => 'upgrade',
782 'overwrite' => $this->request->getArg('overwrite'))),
783 HTML::table(array("cellspacing" => 4),
784 HTML::tr(HTML::td(array('align' => 'right'),
785 _("DB admin user:")),
786 HTML::td(HTML::input(array('name' => "dbadmin[user]",
789 'value' => $dbadmin_user)))),
790 HTML::tr(HTML::td(array('align' => 'right'),
791 _("DB admin password:")),
792 HTML::td(HTML::input(array('name' => "dbadmin[passwd]",
793 'type' => 'password',
795 'maxlength' => 256)))),
796 HTML::tr(HTML::td(array('align' => 'center', 'colspan' => 2),
797 Button("submit:", _("Submit"), 'wikiaction'),
799 Button("submit:dbadmin[cancel]", _("Cancel"),
802 echo "</div><!-- content -->\n";
803 echo asXML(Template("bottom"));
804 echo "</body></html>\n";
805 $this->request->finish();
810 * if page.cached_html does not exists:
811 * put _cached_html from pagedata into a new seperate blob,
812 * not into the huge serialized string.
814 * It is only rarelely needed: for current page only, if-not-modified,
815 * but was extracetd for every simple page iteration.
817 function _upgrade_cached_html($verbose = true)
820 if (!$this->isSQL) return 0;
822 if ($this->phpwiki_version >= 1030.10) {
824 echo _("Check for extra page.cached_html column"), " ... ";
825 $database = $this->dbi->_backend->database();
826 extract($this->dbi->_backend->_table_names);
827 $fields = $this->dbi->_backend->listOfFields($database, $page_tbl);
829 echo _("SKIP"), "<br />\n";
832 if (!strstr(strtolower(join(':', $fields)), "cached_html")) {
834 echo "<b>", _("ADDING"), "</b>", " ... ";
835 $backend_type = $this->dbi->_backend->backendType();
836 if (substr($backend_type, 0, 5) == 'mysql')
837 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html MEDIUMBLOB");
839 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html BLOB");
841 echo "<b>", _("CONVERTING"), "</b>", " ... ";
842 $count = _convert_cached_html();
844 echo $count, " ", _("OK"), "<br />\n";
847 echo _("OK"), "<br />\n";
854 * move _cached_html for all pages from pagedata into a new seperate blob.
855 * decoupled from action=upgrade, so that it can be used by a WikiAdminUtils button also.
857 function _convert_cached_html()
860 if (!$this->isSQL) return 0;
861 //if (!in_array(DATABASE_TYPE, array('SQL','ADODB'))) return;
863 $pages = $this->dbi->getAllPages();
864 $cache =& $this->dbi->_cache;
866 extract($this->dbi->_backend->_table_names);
867 while ($page = $pages->next()) {
868 $pagename = $page->getName();
869 $data = $this->dbi->_backend->get_pagedata($pagename);
870 if (!empty($data['_cached_html'])) {
871 $cached_html = $data['_cached_html'];
872 $data['_cached_html'] = '';
873 $cache->update_pagedata($pagename, $data);
874 // store as blob, not serialized
875 $this->dbi->genericSqlQuery("UPDATE $page_tbl SET cached_html=? WHERE pagename=?",
876 array($cached_html, $pagename));
884 * upgrade to 1.3.13 link structure.
886 function _upgrade_relation_links($verbose = true)
888 if ($this->phpwiki_version >= 1030.12200610 and $this->isSQL) {
889 echo _("Check for relation field in link table"), " ...";
890 $database = $this->dbi->_backend->database();
891 $link_tbl = $prefix . 'link';
892 $fields = $this->dbi->_backend->listOfFields($database, $link_tbl);
895 } elseif (strstr(strtolower(join(':', $fields)), "link")) {
896 echo "<b>", _("ADDING"), " relation</b>", " ... ";
897 $this->dbi->genericSqlQuery("ALTER TABLE $link_tbl ADD relation INT DEFAULT 0;");
898 $this->dbi->genericSqlQuery("CREATE INDEX link_relation ON $link_tbl (relation);");
904 if ($this->phpwiki_version >= 1030.12200610) {
905 echo _("Rebuild entire database to upgrade relation links"), " ... ";
906 if (DATABASE_TYPE == 'dba') {
907 echo "<b>", _("CONVERTING"), " dba linktable</b>", "(~2 min, max 4 min) ... ";
910 $this->dbi->_backend->_linkdb->rebuild();
914 $this->dbi->_backend->rebuild();
916 echo _("OK"), "<br />\n";
920 function CheckPluginUpdate()
924 echo "<h2>", sprintf(_("Check for necessary %s updates"),
925 _("plugin argument")), "</h2>\n";
927 $this->_configUpdates = array();
928 $this->_configUpdates[] = new UpgradePluginEntry
929 ($this, array('key' => 'plugin_randompage_numpages',
930 'fixed_with' => 1012.0,
931 //'header' => _("change RandomPage pages => numpages"),
932 //'notice' =>_("found RandomPage plugin"),
933 'check_args' => array("plugin RandomPage pages",
934 "/(<\?\s*plugin\s+ RandomPage\s+)pages/",
936 $this->_configUpdates[] = new UpgradePluginEntry
937 ($this, array('key' => 'plugin_createtoc_position',
938 'fixed_with' => 1013.0,
939 //'header' => _("change CreateToc align => position"),
940 //'notice' =>_("found CreateToc plugin"),
941 'check_args' => array("plugin CreateToc align",
942 "/(<\?\s*plugin\s+ CreateToc[^\?]+)align/",
945 if (empty($this->_configUpdates)) return;
946 foreach ($this->_configUpdates as $update) {
947 $pages = $this->dbi->fullSearch($this->check_args[0]);
948 while ($page = $allpages->next()) {
949 $current = $page->getCurrentRevision();
950 $pagetext = $current->getPackedContent();
951 $update->check($this->check_args[1], $this->check_args[2], $pagetext, $page, $current);
961 * preg_replace over local file.
962 * Only line-orientated matches possible.
964 function fixLocalFile($match, $replace, $filename)
966 $o_filename = $filename;
967 if (!file_exists($filename))
968 $filename = FindFile($filename);
969 if (!file_exists($filename))
970 return array(false, sprintf(_("file %s not found"), $o_filename));
972 if (is_writable($filename)) {
973 $in = fopen($filename, "rb");
974 $out = fopen($tmp = tempnam(getUploadFilePath(), "cfg"), "wb");
976 $tmp = str_replace("/", "\\", $tmp);
977 // Detect the existing linesep at first line. fgets strips it even if 'rb'.
978 // Before we simply assumed \r\n on Windows local files.
979 $s = fread($in, 1024);
981 $linesep = (substr_count($s, "\r\n") > substr_count($s, "\n")) ? "\r\n" : "\n";
982 //$linesep = isWindows() ? "\r\n" : "\n";
983 while ($s = fgets($in)) {
984 // =>php-5.0.1 can fill count
985 //$new = preg_replace($match, $replace, $s, -1, $count);
986 $new = preg_replace($match, $replace, $s);
988 $s = $new . $linesep;
997 $reason = sprintf(_("%s not found in %s"), $match, $filename);
999 return array($found, $reason);
1001 @unlink("$file.bak");
1002 @rename($file, "$file.bak");
1003 if (!rename($tmp, $file))
1004 return array(false, sprintf(_("couldn't move %s to %s"), $tmp, $filename));
1008 return array(false, sprintf(_("file %s is not writable"), $filename));
1012 function CheckConfigUpdate()
1014 echo "<h2>", sprintf(_("Check for necessary %s updates"),
1015 "config.ini"), "</h2>\n";
1016 $entry = new UpgradeConfigEntry
1017 ($this, array('key' => 'cache_control_none',
1018 'fixed_with' => 1012.0,
1019 'header' => sprintf(_("Check for %s"), "CACHE_CONTROL = NONE"),
1020 'applicable_args' => 'CACHE_CONTROL',
1021 'notice' => _("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'"),
1022 'check_args' => array("/^\s*CACHE_CONTROL\s*=\s*NONE/", "CACHE_CONTROL = NO_CACHE")));
1023 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1024 $this->_configUpdates[] = $entry;
1026 $entry = new UpgradeConfigEntry
1027 ($this, array('key' => 'group_method_none',
1028 'fixed_with' => 1012.0,
1029 'header' => sprintf(_("Check for %s"), "GROUP_METHOD = NONE"),
1030 'applicable_args' => 'GROUP_METHOD',
1031 'notice' => _("GROUP_METHOD is set to NONE, and must be changed to \"NONE\""),
1032 'check_args' => array("/^\s*GROUP_METHOD\s*=\s*NONE/", "GROUP_METHOD = \"NONE\"")));
1033 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1034 $this->_configUpdates[] = $entry;
1036 $entry = new UpgradeConfigEntry
1037 ($this, array('key' => 'blog_empty_default_prefix',
1038 'fixed_with' => 1013.0,
1039 'header' => sprintf(_("Check for %s"), "BLOG_EMPTY_DEFAULT_PREFIX"),
1040 'applicable_args' => 'BLOG_EMPTY_DEFAULT_PREFIX',
1041 'notice' => _("fix BLOG_EMPTY_DEFAULT_PREFIX into BLOG_DEFAULT_EMPTY_PREFIX"),
1042 'check_args' => array("/BLOG_EMPTY_DEFAULT_PREFIX\s*=/", "BLOG_DEFAULT_EMPTY_PREFIX =")));
1043 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined'));
1044 $this->_configUpdates[] = $entry;
1046 // TODO: find extra file updates
1047 if (empty($this->_configUpdates)) return;
1048 foreach ($this->_configUpdates as $update) {
1058 * Add an upgrade item to be checked.
1060 * @param $parent object The parent Upgrade class to inherit the version properties
1061 * @param $key string A short unique key to store success in the WikiDB
1062 * @param $fixed_with double @see phpwiki_version() number
1063 * @param $header string Optional header to be printed always even if not applicable
1064 * @param $applicable WikiCallback Optional callback boolean applicable()
1065 * @param $notice string Description of the check
1066 * @param $method WikiCallback Optional callback array method(array)
1067 * //param All other args are passed to $method
1069 function UpgradeEntry(&$parent, $params)
1071 $this->parent =& $parent; // get the properties db_version
1072 foreach (array('key' => 'required',
1073 // the wikidb stores the version when we actually fixed that.
1074 'fixed_with' => 'required',
1075 'header' => '', // always printed
1076 'applicable_cb' => null, // method to check if applicable
1077 'applicable_args' => array(), // might be the config name
1079 'check_cb' => null, // method to apply
1080 'check_args' => array())
1082 if (!isset($params[$k])) { // default
1083 if ($v == 'required') trigger_error("Required arg $k missing", E_USER_ERROR);
1084 else $this->{$k} = $v;
1086 $this->{$k} = $params[$k];
1089 if (!is_array($this->applicable_args)) // single arg convenience shortcut
1090 $this->applicable_args = array($this->applicable_args);
1091 if (!is_array($this->check_args)) // single arg convenience shortcut
1092 $this->check_args = array($this->check_args);
1093 if ($this->notice === '' and count($this->applicable_args) > 0)
1094 $this->notice = 'Check for ' . join(', ', $this->applicable_args);
1095 $this->_db_key = "_upgrade";
1096 $this->upgrade = $this->parent->dbi->get($this->_db_key);
1100 function setApplicableCb($object)
1102 $this->applicable_cb =& $object;
1105 function _check_if_already_fixed()
1108 if (!isset($this->upgrade['name'])) return false;
1109 // override with force?
1110 if ($this->parent->request->getArg('force')) return false;
1111 // already fixed and with an ok version
1112 if ($this->upgrade['name'] >= $this->fixed_with) return $this->upgrade['name'];
1113 // already fixed but with an older version. do it again.
1119 // store in db no to fix again
1120 $this->upgrade['name'] = $this->parent->phpwiki_version;
1121 $this->parent->dbi->set($this->_db_key, $this->upgrade);
1122 echo "<b>", _("FIXED"), "</b>";
1123 if (isset($this->reason))
1124 echo _(": "), $this->reason;
1132 echo '<span style="color: red; font-weight: bold; ">' . _("FAILED") . "</span>";
1133 if (isset($this->reason))
1134 echo _(": "), $this->reason;
1142 if (isset($this->silent_skip)) return true;
1143 echo " " . _("Skipped.") . "<br />\n";
1148 function check($args = null)
1150 if ($this->header) echo $this->header, ' ... ';
1151 if ($when = $this->_check_if_already_fixed()) {
1152 // be totally silent if no header is defined.
1153 if ($this->header) echo _("fixed with"), " ", $when, "<br />\n";
1157 if (is_object($this->applicable_cb)) {
1158 if (!$this->applicable_cb->call_array($this->applicable_args))
1159 return $this->skip();
1161 if ($this->notice) {
1164 echo $this->notice, " ";
1167 if (!is_null($args)) $this->check_args =& $args;
1168 if (is_object($this->check_cb))
1169 $do = $this->method_cb->call_array($this->check_args);
1171 $do = $this->default_method($this->check_args);
1172 if (is_array($do)) {
1173 $this->reason = $do[1];
1176 return $do ? $this->pass() : $this->fail();
1178 } // class UpgradeEntry
1180 class UpgradeConfigEntry extends UpgradeEntry
1182 function _applicable_defined()
1184 return (boolean)defined($this->applicable_args[0]);
1187 function _applicable_defined_and_empty()
1189 $const = $this->applicable_args[0];
1190 return (boolean)(defined($const) and !constant($const));
1193 function default_method($args)
1196 $replace = $args[1];
1197 return $this->parent->fixLocalFile($match, $replace, "config/config.ini");
1199 } // class UpdateConfigEntry
1201 /* This is different */
1202 class UpgradePluginEntry extends UpgradeEntry
1206 * check all pages for a plugin match
1208 var $silent_skip = 1;
1210 function default_method(&$args)
1213 $replace = $args[1];
1214 $pagetext =& $args[2];
1216 $current =& $args[4];
1217 if (preg_match($match, $pagetext)) {
1218 echo $page->getName(), " ", $this->notice, " ... ";
1219 if ($newtext = preg_replace($match, $replace, $pagetext)) {
1220 $meta = $current->_data;
1221 $meta['summary'] = "upgrade: " . $this->header;
1222 $page->save($newtext, $current->getVersion() + 1, $meta);
1229 } // class UpdatePluginEntry
1232 * fix custom themes which are not in our distribution
1233 * this should be optional
1235 class UpgradeThemeEntry extends UpgradeEntry
1238 function default_method(&$args)
1241 $replace = $args[1];
1242 $template = $args[2];
1245 function fixThemeTemplate($match, $new, $template)
1247 // for all custom themes
1248 $ourthemes = explode(":", "blog:Crao:default:Hawaiian:MacOSX:MonoBook:Portland:shamino_com:SpaceWiki:wikilens:Wordpress");
1249 $themedir = NormalizeLocalFileName("themes");
1250 $dh = opendir($themedir);
1251 while ($r = readdir($dh)) {
1252 if (filetype($r) == 'dir' and $r[0] != '.' and !is_array($r, $ourthemes))
1253 $customthemes[] = $r;
1257 foreach ($customthemes as $customtheme) {
1258 $template = FindFile("themes/$customtheme/templates/$template");
1259 $do = $this->parent->fixLocalFile($match, $new, template);
1262 $errors .= $do[1] . " ";
1266 return array($success, $errors);
1273 * Upgrade: Base class for multipage worksteps
1274 * identify, validate, display options, next step
1279 // TODO: At which step are we?
1280 // validate and do it again or go on with next step.
1282 /** entry function from lib/main.php
1284 function DoUpgrade(&$request)
1287 if (!$request->_user->isAdmin()) {
1288 $request->_notAuthorized(WIKIAUTH_ADMIN);
1290 HTML::div(array('class' => 'disabled-plugin'),
1291 fmt("Upgrade disabled: user != isAdmin")));
1294 // TODO: StartLoadDump should turn on implicit_flush.
1295 @ini_set("implicit_flush", true);
1296 StartLoadDump($request, _("Upgrading this PhpWiki"));
1297 $upgrade = new Upgrade($request);
1298 //if (!$request->getArg('noindex'))
1299 // CheckOldIndexUpdate($request); // index.php => config.ini to upgrade from < 1.3.10
1300 if (!$request->getArg('nodb'))
1301 $upgrade->CheckDatabaseUpdate($request); // first check cached_html and friends
1302 if (!$request->getArg('nopgsrc')) {
1303 $upgrade->CheckPgsrcUpdate($request);
1304 $upgrade->CheckActionPageUpdate($request);
1306 if (!$request->getArg('noplugin'))
1307 $upgrade->CheckPluginUpdate($request);
1308 if (!$request->getArg('noconfig'))
1309 $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