]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/upgrade.php
Remove pgsrc_version
[SourceForge/phpwiki.git] / lib / upgrade.php
1 <?php
2
3 /*
4  * Copyright 2004,2005,2006,2007 $ThePhpWikiProgrammingTeam
5  * Copyright 2008 Marc-Etienne Vargenau, Alcatel-Lucent
6  *
7  * This file is part of PhpWiki.
8  *
9  * PhpWiki is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * PhpWiki is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23
24 /**
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.
28  *
29  * Installation on an existing PhpWiki database needs some
30  * additional worksteps. Each step will require multiple pages.
31  *
32  * This is the plan:
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
44  *     version. (hard)
45  *
46  * Done: overwrite=1 link on edit conflicts at first occurence "Overwrite all".
47  *
48  * @author: Reini Urban
49  */
50 require_once 'lib/loadsave.php';
51
52 class Upgrade
53 {
54
55     function Upgrade(&$request)
56     {
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
61
62         $this->db_version = $this->dbi->get_db_version();
63         $this->isSQL = $this->dbi->_backend->isSQL();
64     }
65
66     function doPgsrcUpdate($pagename, $path, $filename)
67     {
68         // don't ever update the HomePage
69         if ((defined(HOME_PAGE) and ($pagename == HOME_PAGE))
70             or ($pagename == _("HomePage"))
71             or ($pagename == "HomePage")
72         ) {
73             echo "$path/$pagename: " . _("always skip the HomePage.") . " " . _("Skipped."), "<br />\n";
74             return;
75         }
76
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');
85                 reset($parts);
86                 $pageinfo = $parts[0];
87                 $stat = stat($path . "/" . $filename);
88                 $new_mtime = 0;
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'];
95                 if (!$new_mtime)
96                     $new_mtime = $stat[9];
97                 if ($new_mtime > $page_mtime) {
98                     echo "$path/$pagename: ", _("newer than the existing page."),
99                     _(" replace "), "($new_mtime &gt; $page_mtime)", "<br />\n";
100                     LoadAny($this->request, $path . "/" . $filename);
101                     echo "<br />\n";
102                 } else {
103                     echo "$path/$pagename: " . _("older than the existing page.") . " " . _("Skipped."), "<br />\n";
104                 }
105             } else {
106                 echo "$path/$pagename: " . _("unknown format.") . " " . _("Skipped.") . "<br />\n";
107             }
108         } else {
109             echo sprintf(_("%s does not exist"), $pagename), "<br />\n";
110             LoadAny($this->request, $path . "/" . $filename);
111             echo "<br />\n";
112         }
113     }
114
115     function CheckActionPageUpdate()
116     {
117         echo "<h2>", sprintf(_("Check for necessary %s updates"), _("ActionPage")), "</h2>\n";
118         // 1.3.13 before we pull in all missing pages, we rename existing ones
119         $this->_rename_page_helper(_("_AuthInfo"), _("DebugAuthInfo"));
120         // this is in some templates. so we keep the old name
121         //$this->_rename_page_helper($this->dbi, _("DebugInfo"), _("DebugBackendInfo"));
122         $this->_rename_page_helper(_("_GroupInfo"), _("GroupAuthInfo")); //never officially existed
123         $this->_rename_page_helper("InterWikiKarte", "InterWikiListe"); // german only
124
125         $path = FindFile('pgsrc');
126         $pgsrc = new fileSet($path);
127         // most actionpages have the same name as the plugin
128         $loc_path = FindLocalizedFile('pgsrc');
129         foreach ($pgsrc->getFiles() as $filename) {
130             if (substr($filename, -1, 1) == '~') continue;
131             if (substr($filename, -5, 5) == '.orig') continue;
132             $pagename = urldecode($filename);
133             if (isActionPage($pagename)) {
134                 $translation = gettext($pagename);
135                 if ($translation == $pagename)
136                     $this->doPgsrcUpdate($pagename, $path, $filename);
137                 elseif (FindLocalizedFile('pgsrc/' . urlencode($translation), 1))
138                     $this->doPgsrcUpdate($translation, $loc_path, urlencode($translation)); else
139                     $this->doPgsrcUpdate($pagename, $path, $filename);
140             }
141         }
142     }
143
144     // see loadsave.php for saving new pages.
145     function CheckPgsrcUpdate()
146     {
147         // Check some theme specific pgsrc files (blog, wikilens, fusionforge, custom).
148         // We check theme specific pgsrc first in case the page is present in both
149         // theme specific and global pgsrc
150         global $WikiTheme;
151         $path = $WikiTheme->file("pgsrc");
152         // TBD: the call to fileSet prints a warning:
153         // Notice: Unable to open directory 'themes/MonoBook/pgsrc' for reading
154         $pgsrc = new fileSet($path);
155         if ($pgsrc->getFiles()) {
156             echo "<h2>", sprintf(_("Check for necessary theme %s updates"),
157                 "pgsrc"), "</h2>\n";
158             foreach ($pgsrc->getFiles() as $filename) {
159                 if (substr($filename, -1, 1) == '~') continue;
160                 if (substr($filename, -5, 5) == '.orig') continue;
161                 $pagename = urldecode($filename);
162                 $this->doPgsrcUpdate($pagename, $path, $filename);
163             }
164         }
165
166         echo "<h2>", sprintf(_("Check for necessary %s updates"),
167             "pgsrc"), "</h2>\n";
168         if ($this->db_version < 1030.12200612) {
169             echo "<h4>", _("rename to Help: pages"), "</h4>\n";
170         }
171         $path = FindLocalizedFile(WIKI_PGSRC);
172         $pgsrc = new fileSet($path);
173         // fixme: verification, ...
174         foreach ($pgsrc->getFiles() as $filename) {
175             if (substr($filename, -1, 1) == '~') continue;
176             if (substr($filename, -5, 5) == '.orig') continue;
177             $pagename = urldecode($filename);
178             if (!isActionPage($filename)) {
179                 // There're a lot of now unneeded pages around.
180                 // At first rename the BlaPlugin pages to Help/<pagename> and then to the update.
181                 if ($this->db_version < 1030.12200612) {
182                     $this->_rename_to_help_page($pagename);
183                 }
184                 $this->doPgsrcUpdate($pagename, $path, $filename);
185             }
186         }
187     }
188
189     function _rename_page_helper($oldname, $pagename)
190     {
191         echo sprintf(_("rename %s to %s"), $oldname, $pagename), " ...";
192         if ($this->dbi->isWikiPage($oldname) and !$this->dbi->isWikiPage($pagename)) {
193             if ($this->dbi->_backend->rename_page($oldname, $pagename)) {
194                 echo _("OK"), " <br />\n";
195             } else {
196                 echo ' <span style="color: red; font-weight: bold;">' . _("FAILED") . "</span><br />\n";
197             }
198         } else {
199             echo " " . _("Skipped.") . "<br />\n";
200         }
201     }
202
203     function _rename_to_help_page($pagename)
204     {
205         $newprefix = _("Help") . "/";
206         if (substr($pagename, 0, strlen($newprefix)) != $newprefix) return;
207         $oldname = substr($pagename, strlen($newprefix));
208         $this->_rename_page_helper($oldname, $pagename);
209     }
210
211     /**
212      * TODO: Search table definition in appropriate schema
213      *       and create it.
214      * Supported: mysql and generic SQL, for ADODB and PearDB.
215      */
216     function installTable($table, $backend_type)
217     {
218         global $DBParams;
219         if (!$this->isSQL) return;
220         echo _("MISSING"), " ... \n";
221         $backend = &$this->dbi->_backend->_dbh;
222         /*
223           $schema = findFile("schemas/${backend_type}.sql");
224           if (!$schema) {
225           echo "  ",_("FAILED"),": ",sprintf(_("no schema %s found"),
226           "schemas/${backend_type}.sql")," ... <br />\n";
227           return false;
228           }
229         */
230         extract($this->dbi->_backend->_table_names);
231         $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
232         switch ($table) {
233             case 'session':
234                 assert($session_tbl);
235                 if ($backend_type == 'mysql') {
236                     $this->dbi->genericSqlQuery("
237 CREATE TABLE $session_tbl (
238         sess_id     CHAR(32) NOT NULL DEFAULT '',
239         sess_data     BLOB NOT NULL,
240         sess_date     INT UNSIGNED NOT NULL,
241         sess_ip     CHAR(15) NOT NULL,
242         PRIMARY KEY (sess_id),
243     INDEX (sess_date)
244 )");
245                 } else {
246                     $this->dbi->genericSqlQuery("
247 CREATE TABLE $session_tbl (
248     sess_id     CHAR(32) NOT NULL DEFAULT '',
249         sess_data     " . ($backend_type == 'pgsql' ? 'TEXT' : 'BLOB') . " NOT NULL,
250         sess_date     INT,
251         sess_ip     CHAR(15) NOT NULL
252 )");
253                     $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX sess_id ON $session_tbl (sess_id)");
254                 }
255                 $this->dbi->genericSqlQuery("CREATE INDEX sess_date on session (sess_date)");
256                 echo "  ", _("CREATED");
257                 break;
258             case 'pref':
259                 $pref_tbl = $prefix . 'pref';
260                 if ($backend_type == 'mysql') {
261                     $this->dbi->genericSqlQuery("
262 CREATE TABLE $pref_tbl (
263       userid     CHAR(48) BINARY NOT NULL UNIQUE,
264       prefs      TEXT NULL DEFAULT '',
265       PRIMARY KEY (userid)
266 )");
267                 } else {
268                     $this->dbi->genericSqlQuery("
269 CREATE TABLE $pref_tbl (
270       userid     CHAR(48) NOT NULL,
271       prefs      TEXT NULL DEFAULT ''
272 )");
273                     $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX userid ON $pref_tbl (userid)");
274                 }
275                 echo "  ", _("CREATED");
276                 break;
277             case 'member':
278                 $member_tbl = $prefix . 'member';
279                 if ($backend_type == 'mysql') {
280                     $this->dbi->genericSqlQuery("
281 CREATE TABLE $member_tbl (
282     userid    CHAR(48) BINARY NOT NULL,
283        groupname CHAR(48) BINARY NOT NULL DEFAULT 'users',
284        INDEX (userid),
285        INDEX (groupname)
286 )");
287                 } else {
288                     $this->dbi->genericSqlQuery("
289 CREATE TABLE $member_tbl (
290     userid    CHAR(48) NOT NULL,
291        groupname CHAR(48) NOT NULL DEFAULT 'users'
292 )");
293                     $this->dbi->genericSqlQuery("CREATE INDEX userid ON $member_tbl (userid)");
294                     $this->dbi->genericSqlQuery("CREATE INDEX groupname ON $member_tbl (groupname)");
295                 }
296                 echo "  ", _("CREATED");
297                 break;
298             case 'rating':
299                 $rating_tbl = $prefix . 'rating';
300                 if ($backend_type == 'mysql') {
301                     $this->dbi->genericSqlQuery("
302 CREATE TABLE $rating_tbl (
303         dimension INT(4) NOT NULL,
304         raterpage INT(11) NOT NULL,
305         rateepage INT(11) NOT NULL,
306         ratingvalue FLOAT NOT NULL,
307         rateeversion INT(11) NOT NULL,
308         tstamp TIMESTAMP(14) NOT NULL,
309         PRIMARY KEY (dimension, raterpage, rateepage)
310 )");
311                 } else {
312                     $this->dbi->genericSqlQuery("
313 CREATE TABLE $rating_tbl (
314         dimension INT(4) NOT NULL,
315         raterpage INT(11) NOT NULL,
316         rateepage INT(11) NOT NULL,
317         ratingvalue FLOAT NOT NULL,
318         rateeversion INT(11) NOT NULL,
319         tstamp TIMESTAMP(14) NOT NULL
320 )");
321                     $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX rating"
322                         . " ON $rating_tbl (dimension, raterpage, rateepage)");
323                 }
324                 echo "  ", _("CREATED");
325                 break;
326             case 'accesslog':
327                 $log_tbl = $prefix . 'accesslog';
328                 // fields according to http://www.outoforder.cc/projects/apache/mod_log_sql/docs-2.0/#id2756178
329                 /*
330                   A    User Agent agent    varchar(255)    Mozilla/4.0 (compat; MSIE 6.0; Windows)
331                   a    CGi request arguments    request_args    varchar(255)    user=Smith&cart=1231&item=532
332                   b    Bytes transfered    bytes_sent    int unsigned    32561
333                   c???    Text of cookie    cookie    varchar(255)    Apache=sdyn.fooonline.net 1300102700823
334                   f    Local filename requested    request_file    varchar(255)    /var/www/html/books-cycroad.html
335                   H    HTTP request_protocol    request_protocol    varchar(10)    HTTP/1.1
336                   h    Name of remote host    remote_host    varchar(50)    blah.foobar.com
337                   I    Request ID (from modd_unique_id)    id    char(19)    POlFcUBRH30AAALdBG8
338                   l    Ident user info    remote_logname    varcgar(50)    bobby
339                   M    Machine ID???    machine_id    varchar(25)    web01
340                   m    HTTP request method    request_method    varchar(10)    GET
341                   P    httpd cchild PID    child_pid    smallint unsigned    3215
342                   p    http port    server_port    smallint unsigned    80
343                   R    Referer    referer    varchar(255)    http://www.biglinks4u.com/linkpage.html
344                   r    Request in full form    request_line    varchar(255)    GET /books-cycroad.html HTTP/1.1
345                   S    Time of request in UNIX time_t format    time_stamp    int unsigned    1005598029
346                   T    Seconds to service request    request_duration    smallint unsigned    2
347                   t    Time of request in human format    request_time    char(28)    [02/Dec/2001:15:01:26 -0800]
348                   U    Request in simple form    request_uri    varchar(255)    /books-cycroad.html
349                   u    User info from HTTP auth    remote_user    varchar(50)    bobby
350                   v    Virtual host servicing the request    virtual_host    varchar(255)
351                 */
352                 $this->dbi->genericSqlQuery("
353 CREATE TABLE $log_tbl (
354         time_stamp    int unsigned,
355     remote_host   varchar(100),
356     remote_user   varchar(50),
357         request_method varchar(10),
358     request_line  varchar(255),
359     request_args  varchar(255),
360     request_uri   varchar(255),
361     request_time  char(28),
362     status           smallint unsigned,
363     bytes_sent    smallint unsigned,
364         referer       varchar(255),
365     agent         varchar(255),
366     request_duration float
367 )");
368                 $this->dbi->genericSqlQuery("CREATE INDEX log_time ON $log_tbl (time_stamp)");
369                 $this->dbi->genericSqlQuery("CREATE INDEX log_host ON $log_tbl (remote_host)");
370                 echo "  ", _("CREATED");
371                 break;
372         }
373         echo "<br />\n";
374     }
375
376     /**
377      * Update from ~1.3.4 to current.
378      * tables: Only session, user, pref and member
379      * jeffs-hacks database api (around 1.3.2) later:
380      *   people should export/import their pages if using that old versions.
381      */
382     function CheckDatabaseUpdate()
383     {
384         global $DBAuthParams, $DBParams;
385
386         echo "<h2>", sprintf(_("Check for necessary %s updates"),
387             _("database")),
388         " - ", DATABASE_TYPE, "</h2>\n";
389         $dbadmin = $this->request->getArg('dbadmin');
390         if ($this->isSQL) {
391             $this->_db_init();
392             if (isset($dbadmin['cancel'])) {
393                 echo _("CANCEL"), " <br />\n";
394                 return;
395             }
396         }
397         echo "db version: we want ", $this->current_db_version, "\n<br />";
398         echo "db version: we have ", $this->db_version, "\n<br />";
399         if ($this->db_version >= $this->current_db_version) {
400             echo _("OK"), "<br />\n";
401             return;
402         }
403
404         $backend_type = $this->dbi->_backend->backendType();
405         if ($this->isSQL) {
406             echo "<h4>", _("Backend type: "), $backend_type, "</h4>\n";
407             $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
408             $tables = $this->dbi->_backend->listOfTables();
409             foreach (explode(':', 'session:pref:member') as $table) {
410                 echo sprintf(_("Check for table %s"), $table), " ...";
411                 if (!in_array($prefix . $table, $tables)) {
412                     $this->installTable($table, $backend_type);
413                 } else {
414                     echo _("OK"), " <br />\n";
415                 }
416             }
417         }
418
419         if ($this->phpwiki_version >= 1030.12200612 and $this->db_version < 1030.13) {
420             if ($this->isSQL and preg_match("/(pgsql|postgres)/", $backend_type)) {
421                 trigger_error("You need to upgrade to schema/psql-initialize.sql manually!",
422                     E_USER_WARNING);
423                 // $this->_upgrade_psql_tsearch2();
424             }
425             $this->_upgrade_relation_links();
426         }
427
428         if (ACCESS_LOG_SQL and $this->isSQL) {
429             $table = "accesslog";
430             echo sprintf(_("Check for table %s"), $table), " ...";
431             if (!in_array($prefix . $table, $tables)) {
432                 $this->installTable($table, $backend_type);
433             } else {
434                 echo _("OK"), " <br />\n";
435             }
436         }
437         if ($this->isSQL and (class_exists("RatingsUserFactory") or $this->dbi->isWikiPage(_("RateIt")))) {
438             $table = "rating";
439             echo sprintf(_("Check for table %s"), $table), " ...";
440             if (!in_array($prefix . $table, $tables)) {
441                 $this->installTable($table, $backend_type);
442             } else {
443                 echo _("OK"), " <br />\n";
444             }
445         }
446         $backend = &$this->dbi->_backend->_dbh;
447         if ($this->isSQL)
448             extract($this->dbi->_backend->_table_names);
449
450         // 1.3.8 added session.sess_ip
451         if ($this->isSQL and $this->phpwiki_version >= 1030.08 and USE_DB_SESSION
452             and isset($this->request->_dbsession)
453         ) {
454             echo _("Check for new session.sess_ip column"), " ... ";
455             $database = $this->dbi->_backend->database();
456             assert(!empty($DBParams['db_session_table']));
457             $session_tbl = $prefix . $DBParams['db_session_table'];
458             $sess_fields = $this->dbi->_backend->listOfFields($database, $session_tbl);
459             if (!$sess_fields) {
460                 echo _("SKIP");
461             } elseif (!strstr(strtolower(join(':', $sess_fields)), "sess_ip")) {
462                 // TODO: postgres test (should be able to add columns at the end, but not in between)
463                 echo "<b>", _("ADDING"), "</b>", " ... ";
464                 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL");
465                 $this->dbi->genericSqlQuery("CREATE INDEX sess_date ON $session_tbl (sess_date)");
466             } else {
467                 echo _("OK");
468             }
469             echo "<br />\n";
470             if (substr($backend_type, 0, 5) == 'mysql') {
471                 // upgrade to 4.1.8 destroyed my session table:
472                 // sess_id => varchar(10), sess_data => varchar(5). For others obviously also.
473                 echo _("Check for mysql session.sess_id sanity"), " ... ";
474                 $result = $this->dbi->genericSqlQuery("DESCRIBE $session_tbl");
475                 if (DATABASE_TYPE == 'SQL') {
476                     $iter = new WikiDB_backend_PearDB_generic_iter($backend, $result);
477                 } elseif (DATABASE_TYPE == 'ADODB') {
478                     $iter = new WikiDB_backend_ADODB_generic_iter($backend, $result,
479                         array("Field", "Type", "Null", "Key", "Default", "Extra"));
480                 } elseif (DATABASE_TYPE == 'PDO') {
481                     $iter = new WikiDB_backend_PDO_generic_iter($backend, $result);
482                 }
483                 while ($col = $iter->next()) {
484                     if ($col["Field"] == 'sess_id' and !strstr(strtolower($col["Type"]), 'char(32)')) {
485                         $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_id"
486                             . " sess_id CHAR(32) NOT NULL");
487                         echo "sess_id ", $col["Type"], " ", _("fixed"), " =&gt; CHAR(32) ";
488                     }
489                     if ($col["Field"] == 'sess_ip' and !strstr(strtolower($col["Type"]), 'char(15)')) {
490                         $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_ip"
491                             . " sess_ip CHAR(15) NOT NULL");
492                         echo "sess_ip ", $col["Type"], " ", _("fixed"), " =&gt; CHAR(15) ";
493                     }
494                 }
495                 echo _("OK"), "<br />\n";
496             }
497         }
498
499         /* TODO:
500            ALTER TABLE link ADD relation INT DEFAULT 0;
501            CREATE INDEX linkrelation ON link (relation);
502         */
503
504         // mysql >= 4.0.4 requires LOCK TABLE privileges
505         if (substr($backend_type, 0, 5) == 'mysql') {
506             echo _("Check for mysql LOCK TABLE privilege"), " ...";
507             $mysql_version = $this->dbi->_backend->_serverinfo['version'];
508             if ($mysql_version > 400.40) {
509                 if (!empty($this->dbi->_backend->_parsedDSN))
510                     $parseDSN = $this->dbi->_backend->_parsedDSN;
511                 elseif (function_exists('parseDSN')) // ADODB or PDO
512                     $parseDSN = parseDSN($DBParams['dsn']); else // pear
513                     $parseDSN = DB::parseDSN($DBParams['dsn']);
514                 $username = $this->dbi->_backend->qstr($parseDSN['username']);
515                 // on db level
516                 $query = "SELECT lock_tables_priv FROM mysql.db WHERE user='$username'";
517                 //mysql_select_db("mysql", $this->dbi->_backend->connection());
518                 $db_fields = $this->dbi->_backend->listOfFields("mysql", "db");
519                 if (!strstr(strtolower(join(':', $db_fields)), "lock_tables_priv")) {
520                     echo join(':', $db_fields);
521                     die("lock_tables_priv missing. The DB Admin must run mysql_fix_privilege_tables");
522                 }
523                 $row = $this->dbi->_backend->getRow($query);
524                 if (isset($row[0]) and $row[0] == 'N') {
525                     $this->dbi->genericSqlQuery("UPDATE mysql.db SET lock_tables_priv='Y'"
526                         . " WHERE mysql.user='$username'");
527                     $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
528                     echo "mysql.db user='$username'", _("fixed"), "<br />\n";
529                 } elseif (!$row) {
530                     // or on user level
531                     $query = "SELECT lock_tables_priv FROM mysql.user WHERE user='$username'";
532                     $row = $this->dbi->_backend->getRow($query);
533                     if ($row and $row[0] == 'N') {
534                         $this->dbi->genericSqlQuery("UPDATE mysql.user SET lock_tables_priv='Y'"
535                             . " WHERE mysql.user='$username'");
536                         $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
537                         echo "mysql.user user='$username'", _("fixed"), "<br />\n";
538                     } elseif (!$row) {
539                         echo ' <span style="color: red; font-weight: bold;">' . _("FAILED") . "</span>"
540                             . " Neither mysql.db nor mysql.user has a user='$username'"
541                             . " or the lock_tables_priv field",
542                         "<br />\n";
543                     } else {
544                         echo _("OK"), "<br />\n";
545                     }
546                 } else {
547                     echo _("OK"), "<br />\n";
548                 }
549                 //mysql_select_db($this->dbi->_backend->database(), $this->dbi->_backend->connection());
550             } else {
551                 echo sprintf(_("version <em>%s</em> not affected"), $mysql_version), "<br />\n";
552             }
553         }
554
555         // 1.3.10 mysql requires page.id auto_increment
556         // mysql, mysqli or mysqlt
557         if ($this->phpwiki_version >= 1030.099 and substr($backend_type, 0, 5) == 'mysql'
558             and DATABASE_TYPE != 'PDO'
559         ) {
560             echo _("Check for mysql page.id auto_increment flag"), " ...";
561             assert(!empty($page_tbl));
562             $database = $this->dbi->_backend->database();
563             $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
564             $columns = mysql_num_fields($fields);
565             for ($i = 0; $i < $columns; $i++) {
566                 if (mysql_field_name($fields, $i) == 'id') {
567                     $flags = mysql_field_flags($fields, $i);
568                     //DONE: something was wrong with ADODB here.
569                     if (!strstr(strtolower($flags), "auto_increment")) {
570                         echo "<b>", _("ADDING"), "</b>", " ... ";
571                         // MODIFY col_def valid since mysql 3.22.16,
572                         // older mysql's need CHANGE old_col col_def
573                         $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE id"
574                             . " id INT NOT NULL AUTO_INCREMENT");
575                         $fields = mysql_list_fields($database, $page_tbl);
576                         if (!strstr(strtolower(mysql_field_flags($fields, $i)), "auto_increment"))
577                             echo ' <span style="color: red; font-weight: bold;">' . _("FAILED") . "</span><br />\n";
578                         else
579                             echo _("OK"), "<br />\n";
580                     } else {
581                         echo _("OK"), "<br />\n";
582                     }
583                     break;
584                 }
585             }
586             mysql_free_result($fields);
587         }
588
589         // Check for mysql 4.1.x/5.0.0a binary search problem.
590         //   http://bugs.mysql.com/bug.php?id=4398
591         // "select * from page where LOWER(pagename) like '%search%'" does not apply LOWER!
592         // Confirmed for 4.1.0alpha,4.1.3-beta,5.0.0a; not yet tested for 4.1.2alpha,
593         // On windows only, though utf8 would be useful elsewhere also.
594         // Illegal mix of collations (latin1_bin,IMPLICIT) and
595         // (utf8_general_ci, COERCIBLE) for operation '='])
596         if (isWindows() and substr($backend_type, 0, 5) == 'mysql') {
597             echo _("Check for mysql 4.1.x/5.0.0 binary search on Windows problem"), " ...";
598             $mysql_version = $this->dbi->_backend->_serverinfo['version'];
599             if ($mysql_version < 401.0) {
600                 echo sprintf(_("version <em>%s</em>"), $mysql_version), " ",
601                 _("not affected"), "<br />\n";
602             } elseif ($mysql_version >= 401.6) { // FIXME: since which version?
603                 $row = $this->dbi->_backend->getRow("SHOW CREATE TABLE $page_tbl");
604                 $result = join(" ", $row);
605                 if (strstr(strtolower($result), "character set")
606                     and strstr(strtolower($result), "collate")
607                 ) {
608                     echo _("OK"), "<br />\n";
609                 } else {
610                     //SET CHARACTER SET latin1
611                     $charset = CHARSET;
612                     if ($charset == 'iso-8859-1') $charset = 'latin1';
613                     $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename "
614                         . "pagename VARCHAR(100) "
615                         . "CHARACTER SET '$charset' COLLATE '$charset" . "_bin' NOT NULL");
616                     echo sprintf(_("version <em>%s</em>"), $mysql_version),
617                     " <b>", _("FIXED"), "</b>",
618                     "<br />\n";
619                 }
620             } elseif (DATABASE_TYPE != 'PDO') {
621                 // check if already fixed
622                 extract($this->dbi->_backend->_table_names);
623                 assert(!empty($page_tbl));
624                 $database = $this->dbi->_backend->database();
625                 $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
626                 $columns = mysql_num_fields($fields);
627                 for ($i = 0; $i < $columns; $i++) {
628                     if (mysql_field_name($fields, $i) == 'pagename') {
629                         $flags = mysql_field_flags($fields, $i);
630                         // I think it was fixed with 4.1.6, but I tested it only with 4.1.8
631                         if ($mysql_version > 401.0 and $mysql_version < 401.6) {
632                             // remove the binary flag
633                             if (strstr(strtolower($flags), "binary")) {
634                                 // FIXME: on duplicate pagenames this will fail!
635                                 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename"
636                                     . " pagename VARCHAR(100) NOT NULL");
637                                 echo sprintf(_("version <em>%s</em>"), $mysql_version),
638                                 "<b>", _("FIXED"), "</b>"
639                                 , "<br />\n";
640                             }
641                         }
642                         break;
643                     }
644                 }
645             }
646         }
647         if ($this->isSQL and ACCESS_LOG_SQL & 2) {
648             echo _("Check for ACCESS_LOG_SQL passwords in POST requests"), " ...";
649             // Don't display passwords in POST requests (up to 2005-02-04 12:03:20)
650             $res = $this->dbi->genericSqlIter("SELECT time_stamp, remote_host, " .
651                 "request_args FROM ${prefix}accesslog WHERE request_args LIKE " .
652                 "'%s:6:\"passwd\"%' AND request_args NOT LIKE '%s:6:\"passwd\";" .
653                 "s:15:\"<not displayed>\"%'");
654             $count = 0;
655             while ($row = $res->next()) {
656                 $args = preg_replace("/(s:6:\"passwd\";s:15:\").*(\")/",
657                     "$1<not displayed>$2", $row["request_args"]);
658                 $ts = $row["time_stamp"];
659                 $rh = $row["remote_host"];
660                 $this->dbi->genericSqlQuery("UPDATE ${prefix}accesslog SET " .
661                     "request_args='$args' WHERE time_stamp=$ts AND " .
662                     "remote_host='$rh'");
663                 $count++;
664             }
665             if ($count > 0)
666                 echo "<b>", _("FIXED"), "</b>", "<br />\n";
667             else
668                 echo _("OK"), "<br />\n";
669
670             if ($this->phpwiki_version >= 1030.13) {
671                 echo _("Check for ACCESS_LOG_SQL remote_host varchar(50)"), " ...";
672                 $database = $this->dbi->_backend->database();
673                 $accesslog_tbl = $prefix . 'accesslog';
674                 $fields = $this->dbi->_backend->listOfFields($database, $accesslog_tbl);
675                 if (!$fields) {
676                     echo _("SKIP");
677                 } elseif (strstr(strtolower(join(':', $sess_fields)), "remote_host")) {
678                     // TODO: how to check size, already done?
679                     echo "<b>", _("FIXING"), "remote_host</b>", " ... ";
680                     $this->dbi->genericSqlQuery("ALTER TABLE $accesslog_tbl CHANGE remote_host VARCHAR(100)");
681                 } else {
682                     echo _("FAILED");
683                 }
684                 echo "<br />\n";
685             }
686         }
687         $this->_upgrade_cached_html();
688
689         if ($this->db_version < $this->current_db_version) {
690             $this->dbi->set_db_version($this->current_db_version);
691             $this->db_version = $this->dbi->get_db_version();
692             echo "db version: upgrade to ", $this->db_version, "  ";
693             echo _("OK"), "<br />\n";
694             flush();
695         }
696
697         return;
698     }
699
700     /**
701      * Filter SQL missing permissions errors.
702      *
703      * A wrong DBADMIN user will not be able to connect
704      * @see _is_false_error, ErrorManager
705      * @access private
706      */
707     function _dbpermission_filter($err)
708     {
709         if ($err->isWarning()) {
710             global $ErrorManager;
711             $this->error_caught = 1;
712             $ErrorManager->_postponed_errors[] = $err;
713             return true;
714         }
715         return false;
716     }
717
718     function _try_dbadmin_user($user, $passwd)
719     {
720         global $DBParams, $DBAuthParams;
721         $AdminParams = $DBParams;
722         if (DATABASE_TYPE == 'SQL')
723             $dsn = DB::parseDSN($AdminParams['dsn']);
724         else {
725             $dsn = parseDSN($AdminParams['dsn']);
726         }
727         $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
728             $dsn['phptype'],
729             $user,
730             $passwd,
731             $dsn['hostspec'],
732             $dsn['database']);
733         $AdminParams['_tryroot_from_upgrade'] = 1;
734         // add error handler to warn about missing permissions for DBADMIN_USER
735         global $ErrorManager;
736         $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_dbpermission_filter'));
737         $this->error_caught = 0;
738         $this->dbi = WikiDB::open($AdminParams);
739         if (!$this->error_caught) return true;
740         // FAILED: redo our connection with the wikiuser
741         $this->dbi = WikiDB::open($DBParams);
742         $ErrorManager->flushPostponedErrors();
743         $ErrorManager->popErrorHandler();
744         return false;
745     }
746
747     function _db_init()
748     {
749         if (!$this->isSQL) return;
750
751         /* SQLite never needs admin params */
752         $backend_type = $this->dbi->_backend->backendType();
753         if (substr($backend_type, 0, 6) == "sqlite") {
754             return;
755         }
756         $dbadmin_user = 'root';
757         if ($dbadmin = $this->request->getArg('dbadmin')) {
758             $dbadmin_user = $dbadmin['user'];
759             if (isset($dbadmin['cancel'])) {
760                 return;
761             } elseif (!empty($dbadmin_user)) {
762                 if ($this->_try_dbadmin_user($dbadmin['user'], $dbadmin['passwd']))
763                     return;
764             }
765         } elseif (DBADMIN_USER) {
766             if ($this->_try_dbadmin_user(DBADMIN_USER, DBADMIN_PASSWD)) {
767                 return;
768             }
769         }
770         // Check if the privileges are enough. Need CREATE and ALTER perms.
771         // And on Windows: SELECT FROM mysql, possibly: UPDATE mysql.
772         $form = HTML::form(array("method" => "post",
773                 "action" => $this->request->getPostURL(),
774                 "accept-charset" => $GLOBALS['charset']),
775             HTML::p(_("Upgrade requires database privileges to CREATE and ALTER the phpwiki database."),
776                 HTML::br(),
777                 _("And on Windows at least the privilege to SELECT FROM mysql, and possibly UPDATE mysql")),
778             HiddenInputs(array('action' => 'upgrade',
779                 'overwrite' => $this->request->getArg('overwrite'))),
780             HTML::table(array("cellspacing" => 4),
781                 HTML::tr(HTML::td(array('align' => 'right'),
782                         _("DB admin user:")),
783                     HTML::td(HTML::input(array('name' => "dbadmin[user]",
784                         'size' => 12,
785                         'maxlength' => 256,
786                         'value' => $dbadmin_user)))),
787                 HTML::tr(HTML::td(array('align' => 'right'),
788                         _("DB admin password:")),
789                     HTML::td(HTML::input(array('name' => "dbadmin[passwd]",
790                         'type' => 'password',
791                         'size' => 12,
792                         'maxlength' => 256)))),
793                 HTML::tr(HTML::td(array('align' => 'center', 'colspan' => 2),
794                     Button("submit:", _("Submit"), 'wikiaction'),
795                     HTML::raw('&nbsp;'),
796                     Button("submit:dbadmin[cancel]", _("Cancel"),
797                         'button')))));
798         $form->printXml();
799         echo "</div><!-- content -->\n";
800         echo asXML(Template("bottom"));
801         echo "</body></html>\n";
802         $this->request->finish();
803         exit();
804     }
805
806     /**
807      * if page.cached_html does not exists:
808      *   put _cached_html from pagedata into a new seperate blob,
809      *   not into the huge serialized string.
810      *
811      * It is only rarelely needed: for current page only, if-not-modified,
812      * but was extracetd for every simple page iteration.
813      */
814     function _upgrade_cached_html($verbose = true)
815     {
816         global $DBParams;
817         if (!$this->isSQL) return 0;
818         $count = 0;
819         if ($this->phpwiki_version >= 1030.10) {
820             if ($verbose)
821                 echo _("Check for extra page.cached_html column"), " ... ";
822             $database = $this->dbi->_backend->database();
823             extract($this->dbi->_backend->_table_names);
824             $fields = $this->dbi->_backend->listOfFields($database, $page_tbl);
825             if (!$fields) {
826                 echo _("SKIP"), "<br />\n";
827                 return 0;
828             }
829             if (!strstr(strtolower(join(':', $fields)), "cached_html")) {
830                 if ($verbose)
831                     echo "<b>", _("ADDING"), "</b>", " ... ";
832                 $backend_type = $this->dbi->_backend->backendType();
833                 if (substr($backend_type, 0, 5) == 'mysql')
834                     $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html MEDIUMBLOB");
835                 else
836                     $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html BLOB");
837                 if ($verbose)
838                     echo "<b>", _("CONVERTING"), "</b>", " ... ";
839                 $count = _convert_cached_html();
840                 if ($verbose)
841                     echo $count, " ", _("OK"), "<br />\n";
842             } else {
843                 if ($verbose)
844                     echo _("OK"), "<br />\n";
845             }
846         }
847         return $count;
848     }
849
850     /**
851      * move _cached_html for all pages from pagedata into a new seperate blob.
852      * decoupled from action=upgrade, so that it can be used by a WikiAdminUtils button also.
853      */
854     function _convert_cached_html()
855     {
856         global $DBParams;
857         if (!$this->isSQL) return 0;
858         //if (!in_array(DATABASE_TYPE, array('SQL','ADODB'))) return;
859
860         $pages = $this->dbi->getAllPages();
861         $cache =& $this->dbi->_cache;
862         $count = 0;
863         extract($this->dbi->_backend->_table_names);
864         while ($page = $pages->next()) {
865             $pagename = $page->getName();
866             $data = $this->dbi->_backend->get_pagedata($pagename);
867             if (!empty($data['_cached_html'])) {
868                 $cached_html = $data['_cached_html'];
869                 $data['_cached_html'] = '';
870                 $cache->update_pagedata($pagename, $data);
871                 // store as blob, not serialized
872                 $this->dbi->genericSqlQuery("UPDATE $page_tbl SET cached_html=? WHERE pagename=?",
873                     array($cached_html, $pagename));
874                 $count++;
875             }
876         }
877         return $count;
878     }
879
880     /**
881      * upgrade to 1.3.13 link structure.
882      */
883     function _upgrade_relation_links($verbose = true)
884     {
885         if ($this->phpwiki_version >= 1030.12200610 and $this->isSQL) {
886             echo _("Check for relation field in link table"), " ...";
887             $database = $this->dbi->_backend->database();
888             $link_tbl = $prefix . 'link';
889             $fields = $this->dbi->_backend->listOfFields($database, $link_tbl);
890             if (!$fields) {
891                 echo _("SKIP");
892             } elseif (strstr(strtolower(join(':', $fields)), "link")) {
893                 echo "<b>", _("ADDING"), " relation</b>", " ... ";
894                 $this->dbi->genericSqlQuery("ALTER TABLE $link_tbl ADD relation INT DEFAULT 0;");
895                 $this->dbi->genericSqlQuery("CREATE INDEX link_relation ON $link_tbl (relation);");
896             } else {
897                 echo _("FAILED");
898             }
899             echo "<br />\n";
900         }
901         if ($this->phpwiki_version >= 1030.12200610) {
902             echo _("Rebuild entire database to upgrade relation links"), " ... ";
903             if (DATABASE_TYPE == 'dba') {
904                 echo "<b>", _("CONVERTING"), " dba linktable</b>", "(~2 min, max 4 min) ... ";
905                 flush();
906                 longer_timeout(240);
907                 $this->dbi->_backend->_linkdb->rebuild();
908             } else {
909                 flush();
910                 longer_timeout(180);
911                 $this->dbi->_backend->rebuild();
912             }
913             echo _("OK"), "<br />\n";
914         }
915     }
916
917     function CheckPluginUpdate()
918     {
919         echo "<h2>", sprintf(_("Check for necessary %s updates"),
920             _("plugin argument")), "</h2>\n";
921
922         $this->_configUpdates = array();
923         $this->_configUpdates[] = new UpgradePluginEntry
924         ($this, array('key' => 'plugin_randompage_numpages',
925             'fixed_with' => 1012.0,
926             //'header' => _("change RandomPage pages => numpages"),
927             //'notice'  =>_("found RandomPage plugin"),
928             'check_args' => array("plugin RandomPage pages",
929                 "/(<\?\s*plugin\s+ RandomPage\s+)pages/",
930                 "\\1numpages")));
931         $this->_configUpdates[] = new UpgradePluginEntry
932         ($this, array('key' => 'plugin_createtoc_position',
933             'fixed_with' => 1013.0,
934             //'header' => _("change CreateToc align => position"),
935             //'notice'  =>_("found CreateToc plugin"),
936             'check_args' => array("plugin CreateToc align",
937                 "/(<\?\s*plugin\s+ CreateToc[^\?]+)align/",
938                 "\\1position")));
939
940         if (empty($this->_configUpdates)) return;
941         foreach ($this->_configUpdates as $update) {
942             $pages = $this->dbi->fullSearch($this->check_args[0]);
943             while ($page = $allpages->next()) {
944                 $current = $page->getCurrentRevision();
945                 $pagetext = $current->getPackedContent();
946                 $update->check($this->check_args[1], $this->check_args[2], $pagetext, $page, $current);
947             }
948         }
949         free($allpages);
950         unset($pagetext);
951         unset($current);
952         unset($page);
953     }
954
955     /**
956      * preg_replace over local file.
957      * Only line-orientated matches possible.
958      */
959     function fixLocalFile($match, $replace, $filename)
960     {
961         $o_filename = $filename;
962         if (!file_exists($filename))
963             $filename = FindFile($filename);
964         if (!file_exists($filename))
965             return array(false, sprintf(_("file %s not found"), $o_filename));
966         $found = false;
967         if (is_writable($filename)) {
968             $in = fopen($filename, "rb");
969             $out = fopen($tmp = tempnam(getUploadFilePath(), "cfg"), "wb");
970             if (isWindows())
971                 $tmp = str_replace("/", "\\", $tmp);
972             // Detect the existing linesep at first line. fgets strips it even if 'rb'.
973             // Before we simply assumed \r\n on Windows local files.
974             $s = fread($in, 1024);
975             rewind($in);
976             $linesep = (substr_count($s, "\r\n") > substr_count($s, "\n")) ? "\r\n" : "\n";
977             //$linesep = isWindows() ? "\r\n" : "\n";
978             while ($s = fgets($in)) {
979                 // =>php-5.0.1 can fill count
980                 //$new = preg_replace($match, $replace, $s, -1, $count);
981                 $new = preg_replace($match, $replace, $s);
982                 if ($new != $s) {
983                     $s = $new . $linesep;
984                     $found = true;
985                 }
986                 fputs($out, $s);
987             }
988             fclose($in);
989             fclose($out);
990             if (!$found) {
991                 // todo: skip
992                 $reason = sprintf(_("%s not found in %s"), $match, $filename);
993                 unlink($out);
994                 return array($found, $reason);
995             } else {
996                 @unlink("$file.bak");
997                 @rename($file, "$file.bak");
998                 if (!rename($tmp, $file))
999                     return array(false, sprintf(_("couldn't move %s to %s"), $tmp, $filename));
1000                 return true;
1001             }
1002         } else {
1003             return array(false, sprintf(_("file %s is not writable"), $filename));
1004         }
1005     }
1006
1007     function CheckConfigUpdate()
1008     {
1009         echo "<h2>", sprintf(_("Check for necessary %s updates"),
1010             "config.ini"), "</h2>\n";
1011         $entry = new UpgradeConfigEntry
1012         ($this, array('key' => 'cache_control_none',
1013             'fixed_with' => 1012.0,
1014             'header' => sprintf(_("Check for %s"), "CACHE_CONTROL = NONE"),
1015             'applicable_args' => 'CACHE_CONTROL',
1016             'notice' => _("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'"),
1017             'check_args' => array("/^\s*CACHE_CONTROL\s*=\s*NONE/", "CACHE_CONTROL = NO_CACHE")));
1018         $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1019         $this->_configUpdates[] = $entry;
1020
1021         $entry = new UpgradeConfigEntry
1022         ($this, array('key' => 'group_method_none',
1023             'fixed_with' => 1012.0,
1024             'header' => sprintf(_("Check for %s"), "GROUP_METHOD = NONE"),
1025             'applicable_args' => 'GROUP_METHOD',
1026             'notice' => _("GROUP_METHOD is set to NONE, and must be changed to \"NONE\""),
1027             'check_args' => array("/^\s*GROUP_METHOD\s*=\s*NONE/", "GROUP_METHOD = \"NONE\"")));
1028         $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1029         $this->_configUpdates[] = $entry;
1030
1031         $entry = new UpgradeConfigEntry
1032         ($this, array('key' => 'blog_empty_default_prefix',
1033             'fixed_with' => 1013.0,
1034             'header' => sprintf(_("Check for %s"), "BLOG_EMPTY_DEFAULT_PREFIX"),
1035             'applicable_args' => 'BLOG_EMPTY_DEFAULT_PREFIX',
1036             'notice' => _("fix BLOG_EMPTY_DEFAULT_PREFIX into BLOG_DEFAULT_EMPTY_PREFIX"),
1037             'check_args' => array("/BLOG_EMPTY_DEFAULT_PREFIX\s*=/", "BLOG_DEFAULT_EMPTY_PREFIX =")));
1038         $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined'));
1039         $this->_configUpdates[] = $entry;
1040
1041         // TODO: find extra file updates
1042         if (empty($this->_configUpdates)) return;
1043         foreach ($this->_configUpdates as $update) {
1044             $update->check();
1045         }
1046     }
1047
1048 } // class Upgrade
1049
1050 class UpgradeEntry
1051 {
1052     /**
1053      * Add an upgrade item to be checked.
1054      *
1055      * @param $parent object The parent Upgrade class to inherit the version properties
1056      * @param $key string    A short unique key to store success in the WikiDB
1057      * @param $fixed_with double @see phpwiki_version() number
1058      * @param $header string Optional header to be printed always even if not applicable
1059      * @param $applicable WikiCallback Optional callback boolean applicable()
1060      * @param $notice string Description of the check
1061      * @param $method WikiCallback Optional callback array method(array)
1062      * //param All other args are passed to $method
1063      */
1064     function UpgradeEntry(&$parent, $params)
1065     {
1066         $this->parent =& $parent; // get the properties db_version
1067         foreach (array('key' => 'required',
1068                      // the wikidb stores the version when we actually fixed that.
1069                      'fixed_with' => 'required',
1070                      'header' => '', // always printed
1071                      'applicable_cb' => null, // method to check if applicable
1072                      'applicable_args' => array(), // might be the config name
1073                      'notice' => '',
1074                      'check_cb' => null, // method to apply
1075                      'check_args' => array())
1076                  as $k => $v) {
1077             if (!isset($params[$k])) { // default
1078                 if ($v == 'required') trigger_error("Required arg $k missing", E_USER_ERROR);
1079                 else $this->{$k} = $v;
1080             } else {
1081                 $this->{$k} = $params[$k];
1082             }
1083         }
1084         if (!is_array($this->applicable_args)) // single arg convenience shortcut
1085             $this->applicable_args = array($this->applicable_args);
1086         if (!is_array($this->check_args)) // single arg convenience shortcut
1087             $this->check_args = array($this->check_args);
1088         if ($this->notice === '' and count($this->applicable_args) > 0)
1089             $this->notice = 'Check for ' . join(', ', $this->applicable_args);
1090         $this->_db_key = "_upgrade";
1091         $this->upgrade = $this->parent->dbi->get($this->_db_key);
1092     }
1093
1094     /* needed ? */
1095     function setApplicableCb($object)
1096     {
1097         $this->applicable_cb =& $object;
1098     }
1099
1100     function _check_if_already_fixed()
1101     {
1102         // not yet fixed?
1103         if (!isset($this->upgrade['name'])) return false;
1104         // override with force?
1105         if ($this->parent->request->getArg('force')) return false;
1106         // already fixed and with an ok version
1107         if ($this->upgrade['name'] >= $this->fixed_with) return $this->upgrade['name'];
1108         // already fixed but with an older version. do it again.
1109         return false;
1110     }
1111
1112     function pass()
1113     {
1114         // store in db no to fix again
1115         $this->upgrade['name'] = $this->parent->phpwiki_version;
1116         $this->parent->dbi->set($this->_db_key, $this->upgrade);
1117         echo "<b>", _("FIXED"), "</b>";
1118         if (isset($this->reason))
1119             echo _(": "), $this->reason;
1120         echo "<br />\n";
1121         flush();
1122         return true;
1123     }
1124
1125     function fail()
1126     {
1127         echo '<span style="color: red; font-weight: bold; ">' . _("FAILED") . "</span>";
1128         if (isset($this->reason))
1129             echo _(": "), $this->reason;
1130         echo "<br />\n";
1131         flush();
1132         return false;
1133     }
1134
1135     function skip()
1136     { // not applicable
1137         if (isset($this->silent_skip)) return true;
1138         echo " " . _("Skipped.") . "<br />\n";
1139         flush();
1140         return true;
1141     }
1142
1143     function check($args = null)
1144     {
1145         if ($this->header) echo $this->header, ' ... ';
1146         if ($when = $this->_check_if_already_fixed()) {
1147             // be totally silent if no header is defined.
1148             if ($this->header) echo _("fixed with"), " ", $when, "<br />\n";
1149             flush();
1150             return true;
1151         }
1152         if (is_object($this->applicable_cb)) {
1153             if (!$this->applicable_cb->call_array($this->applicable_args))
1154                 return $this->skip();
1155         }
1156         if ($this->notice) {
1157             if ($this->header)
1158                 echo "<br />\n";
1159             echo $this->notice, " ";
1160             flush();
1161         }
1162         if (!is_null($args)) $this->check_args =& $args;
1163         if (is_object($this->check_cb))
1164             $do = $this->method_cb->call_array($this->check_args);
1165         else
1166             $do = $this->default_method($this->check_args);
1167         if (is_array($do)) {
1168             $this->reason = $do[1];
1169             $do = $do[0];
1170         }
1171         return $do ? $this->pass() : $this->fail();
1172     }
1173 } // class UpgradeEntry
1174
1175 class UpgradeConfigEntry extends UpgradeEntry
1176 {
1177     function _applicable_defined()
1178     {
1179         return (boolean)defined($this->applicable_args[0]);
1180     }
1181
1182     function _applicable_defined_and_empty()
1183     {
1184         $const = $this->applicable_args[0];
1185         return (boolean)(defined($const) and !constant($const));
1186     }
1187
1188     function default_method($args)
1189     {
1190         $match = $args[0];
1191         $replace = $args[1];
1192         return $this->parent->fixLocalFile($match, $replace, "config/config.ini");
1193     }
1194 } // class UpdateConfigEntry
1195
1196 /* This is different */
1197 class UpgradePluginEntry extends UpgradeEntry
1198 {
1199
1200     /**
1201      * check all pages for a plugin match
1202      */
1203     var $silent_skip = 1;
1204
1205     function default_method(&$args)
1206     {
1207         $match = $args[0];
1208         $replace = $args[1];
1209         $pagetext =& $args[2];
1210         $page =& $args[3];
1211         $current =& $args[4];
1212         if (preg_match($match, $pagetext)) {
1213             echo $page->getName(), " ", $this->notice, " ... ";
1214             if ($newtext = preg_replace($match, $replace, $pagetext)) {
1215                 $meta = $current->_data;
1216                 $meta['summary'] = "upgrade: " . $this->header;
1217                 $page->save($newtext, $current->getVersion() + 1, $meta);
1218                 $this->pass();
1219             } else {
1220                 $this->fail();
1221             }
1222         }
1223     }
1224 } // class UpdatePluginEntry
1225
1226 /**
1227  * fix custom themes which are not in our distribution
1228  * this should be optional
1229  */
1230 class UpgradeThemeEntry extends UpgradeEntry
1231 {
1232
1233     function default_method(&$args)
1234     {
1235         $match = $args[0];
1236         $replace = $args[1];
1237         $template = $args[2];
1238     }
1239
1240     function fixThemeTemplate($match, $new, $template)
1241     {
1242         // for all custom themes
1243         $ourthemes = explode(":", "blog:Crao:default:Hawaiian:MacOSX:MonoBook:Portland:shamino_com:SpaceWiki:wikilens:Wordpress");
1244         $themedir = NormalizeLocalFileName("themes");
1245         $dh = opendir($themedir);
1246         while ($r = readdir($dh)) {
1247             if (filetype($r) == 'dir' and $r[0] != '.' and !is_array($r, $ourthemes))
1248                 $customthemes[] = $r;
1249         }
1250         $success = true;
1251         $errors = '';
1252         foreach ($customthemes as $customtheme) {
1253             $template = FindFile("themes/$customtheme/templates/$template");
1254             $do = $this->parent->fixLocalFile($match, $new, template);
1255             if (!$do[0]) {
1256                 $success = false;
1257                 $errors .= $do[1] . " ";
1258                 echo $do[1];
1259             }
1260         }
1261         return array($success, $errors);
1262     }
1263 }
1264
1265 /**
1266  * TODO:
1267  *
1268  * Upgrade: Base class for multipage worksteps
1269  * identify, validate, display options, next step
1270  */
1271 /*
1272 */
1273
1274 // TODO: At which step are we?
1275 // validate and do it again or go on with next step.
1276
1277 /** entry function from lib/main.php
1278  */
1279 function DoUpgrade(&$request)
1280 {
1281
1282     if (!$request->_user->isAdmin()) {
1283         $request->_notAuthorized(WIKIAUTH_ADMIN);
1284         $request->finish(
1285             HTML::div(array('class' => 'disabled-plugin'),
1286                 fmt("Upgrade disabled: user != isAdmin")));
1287         return;
1288     }
1289     // TODO: StartLoadDump should turn on implicit_flush.
1290     @ini_set("implicit_flush", true);
1291     StartLoadDump($request, _("Upgrading this PhpWiki"));
1292     $upgrade = new Upgrade($request);
1293     //if (!$request->getArg('noindex'))
1294     //    CheckOldIndexUpdate($request); // index.php => config.ini to upgrade from < 1.3.10
1295     if (!$request->getArg('nodb')) {
1296         $upgrade->CheckDatabaseUpdate($request); // first check cached_html and friends
1297     }
1298     if (!$request->getArg('nopgsrc')) {
1299         $upgrade->CheckPgsrcUpdate($request);
1300         $upgrade->CheckActionPageUpdate($request);
1301     }
1302     // if (!$request->getArg('noplugin')) {
1303         // $upgrade->CheckPluginUpdate($request);
1304     // }
1305     if (!$request->getArg('noconfig')) {
1306         $upgrade->CheckConfigUpdate($request);
1307     }
1308     // This is optional and should be linked. In EndLoadDump or PhpWikiAdministration?
1309     //if ($request->getArg('theme'))
1310     //      $upgrade->CheckThemeUpdate($request);
1311     EndLoadDump($request);
1312 }
1313
1314 // Local Variables:
1315 // mode: php
1316 // tab-width: 8
1317 // c-basic-offset: 4
1318 // c-hanging-comment-ender-p: nil
1319 // indent-tabs-mode: nil
1320 // End: