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