4 * Copyright 2004,2005,2006,2007 $ThePhpWikiProgrammingTeam
5 * Copyright 2008 Marc-Etienne Vargenau, Alcatel-Lucent
7 * This file is part of PhpWiki.
9 * PhpWiki is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * PhpWiki is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with PhpWiki; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 * Upgrade existing WikiDB and config settings after installing a new PhpWiki sofwtare version.
26 * Status: almost no queries for verification.
27 * simple merge conflict resolution, or Overwrite All.
29 * Installation on an existing PhpWiki database needs some
30 * additional worksteps. Each step will require multiple pages.
33 * 1. Check for new or changed database schema and update it
34 * according to some predefined upgrade tables. (medium, complete)
35 * 2. Check for new or changed (localized) pgsrc/ pages and ask
36 * for upgrading these. Check timestamps, upgrade silently or
37 * show diffs if existing. Overwrite or merge (easy, complete)
38 * 3. Check for new or changed or deprecated index.php/config.ini settings
39 * and help in upgrading these. (for newer changes since 1.3.11, not yet)
40 * 3a. Convert old-style index.php into config/config.ini. (easy, not yet)
41 * 4. Check for changed plugin invocation arguments. (medium, done)
42 * 5. Check for changed theme variables. (hard, not yet)
43 * 6. Convert the single-request upgrade to a class-based multi-page
46 * Done: overwrite=1 link on edit conflicts at first occurence "Overwrite all".
48 * @author: Reini Urban
50 require_once("lib/loadsave.php");
54 function Upgrade (&$request) {
55 $this->request =& $request;
56 $this->dbi =& $request->_dbi; // no reference for dbadmin ?
57 $this->phpwiki_version = $this->current_db_version = phpwiki_version();
58 //$this->current_db_version = 1030.13; // should be stored in the db. should be phpwiki_version
60 $this->db_version = $this->dbi->get_db_version();
61 $this->isSQL = $this->dbi->_backend->isSQL();
65 * TODO: check for the pgsrc_version number, not the revision mtime only
67 function doPgsrcUpdate($pagename, $path, $filename) {
68 // don't ever update the HomePage
69 if ((defined(HOME_PAGE) and ($pagename == HOME_PAGE))
70 or ($pagename == _("HomePage"))
71 or ($pagename == "HomePage")) {
72 echo "$path/$pagename: ",_("always skip the HomePage."),
73 _(" Skipped"),".<br />\n";
77 $page = $this->dbi->getPage($pagename);
78 if ($page->exists()) {
79 // check mtime: update automatically if pgsrc is newer
80 $rev = $page->getCurrentRevision();
81 $page_mtime = $rev->get('mtime');
82 $data = implode("", file($path."/".$filename));
83 if (($parts = ParseMimeifiedPages($data))) {
84 usort($parts, 'SortByPageVersion');
86 $pageinfo = $parts[0];
87 $stat = stat($path."/".$filename);
89 if (isset($pageinfo['versiondata']['mtime']))
90 $new_mtime = $pageinfo['versiondata']['mtime'];
91 if (!$new_mtime and isset($pageinfo['versiondata']['lastmodified']))
92 $new_mtime = $pageinfo['versiondata']['lastmodified'];
93 if (!$new_mtime and isset($pageinfo['pagedata']['date']))
94 $new_mtime = $pageinfo['pagedata']['date'];
96 $new_mtime = $stat[9];
97 if ($new_mtime > $page_mtime) {
98 echo "$path/$pagename: ",_("newer than the existing page."),
99 _(" replace "),"($new_mtime > $page_mtime)","<br />\n";
100 LoadAny($this->request, $path."/".$filename);
103 echo "$path/$pagename: ",_("older than the existing page."),
104 _(" Skipped"),".<br />\n";
107 echo "$path/$pagename: ",("unknown format."),
108 _(" Skipped"),".<br />\n";
111 echo sprintf(_("%s does not exist"),$pagename),"<br />\n";
112 LoadAny($this->request, $path."/".$filename);
117 function CheckActionPageUpdate() {
118 echo "<h3>",sprintf(_("Check for necessary %s updates"),
119 _("ActionPage")),"</h3>\n";
120 // 1.3.13 before we pull in all missing pages, we rename existing ones
121 $this->_rename_page_helper(_("_AuthInfo"), _("DebugAuthInfo"));
122 // this is in some templates. so we keep the old name
123 //$this->_rename_page_helper($this->dbi, _("DebugInfo"), _("DebugBackendInfo"));
124 $this->_rename_page_helper(_("_GroupInfo"), _("GroupAuthInfo")); //never officially existed
125 $this->_rename_page_helper("InterWikiKarte", "InterWikiListe"); // german only
127 $path = FindFile('pgsrc');
128 $pgsrc = new fileSet($path);
129 // most actionpages have the same name as the plugin
130 $loc_path = FindLocalizedFile('pgsrc');
131 foreach ($pgsrc->getFiles() as $filename) {
132 if (substr($filename,-1,1) == '~') continue;
133 if (substr($filename,-5,5) == '.orig') continue;
134 $pagename = urldecode($filename);
135 if (isActionPage($pagename)) {
136 $translation = gettext($pagename);
137 if ($translation == $pagename)
138 $this->doPgsrcUpdate($pagename, $path, $filename);
139 elseif (FindLocalizedFile('pgsrc/'.urlencode($translation),1))
140 $this->doPgsrcUpdate($translation, $loc_path, urlencode($translation));
142 $this->doPgsrcUpdate($pagename, $path, $filename);
147 // see loadsave.php for saving new pages.
148 function CheckPgsrcUpdate() {
149 // Check some theme specific pgsrc files (blog, wikilens, fusionforge, custom).
150 // We check theme specific pgsrc first in case the page is present in both
151 // theme specific and global pgsrc
153 $path = $WikiTheme->file("pgsrc");
154 // TBD: the call to fileSet prints a warning:
155 // Notice: Unable to open directory 'themes/MonoBook/pgsrc' for reading
156 $pgsrc = new fileSet($path);
157 if ($pgsrc->getFiles()) {
158 echo "<h3>",sprintf(_("Check for necessary theme %s updates"),
160 foreach ($pgsrc->getFiles() as $filename) {
161 if (substr($filename,-1,1) == '~') continue;
162 if (substr($filename,-5,5) == '.orig') continue;
163 $pagename = urldecode($filename);
164 $this->doPgsrcUpdate($pagename,$path,$filename);
168 echo "<h3>",sprintf(_("Check for necessary %s updates"),
170 if ($this->db_version < 1030.12200612) {
171 echo "<h4>",_("rename to Help: pages"),"</h4>\n";
173 $path = FindLocalizedFile(WIKI_PGSRC);
174 $pgsrc = new fileSet($path);
175 // fixme: verification, ...
176 foreach ($pgsrc->getFiles() as $filename) {
177 if (substr($filename,-1,1) == '~') continue;
178 if (substr($filename,-5,5) == '.orig') continue;
179 $pagename = urldecode($filename);
180 if (!isActionPage($filename)) {
181 // There're a lot of now unneeded pages around.
182 // At first rename the BlaPlugin pages to Help/<pagename> and then to the update.
183 if ($this->db_version < 1030.12200612) {
184 $this->_rename_to_help_page($pagename);
186 $this->doPgsrcUpdate($pagename,$path,$filename);
191 function _rename_page_helper($oldname, $pagename) {
192 echo sprintf(_("rename %s to %s"), $oldname, $pagename)," ...";
193 if ($this->dbi->isWikiPage($oldname) and !$this->dbi->isWikiPage($pagename)) {
194 if ($this->dbi->_backend->rename_page($oldname, $pagename))
195 echo _("OK")," <br />\n";
197 echo " <b><font color=\"red\">", _("FAILED"), "</font></b>",
200 echo _(" Skipped")," <br />\n";
204 function _rename_to_help_page($pagename) {
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);
212 * TODO: Search table definition in appropriate schema
214 * Supported: mysql and generic SQL, for ADODB and PearDB.
216 function installTable($table, $backend_type) {
218 if (!$this->isSQL) return;
219 echo _("MISSING")," ... \n";
220 $backend = &$this->dbi->_backend->_dbh;
222 $schema = findFile("schemas/${backend_type}.sql");
224 echo " ",_("FAILED"),": ",sprintf(_("no schema %s found"),
225 "schemas/${backend_type}.sql")," ... <br />\n";
229 extract($this->dbi->_backend->_table_names);
230 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
233 assert($session_tbl);
234 if ($backend_type == 'mysql') {
235 $this->dbi->genericSqlQuery("
236 CREATE TABLE $session_tbl (
237 sess_id CHAR(32) NOT NULL DEFAULT '',
238 sess_data BLOB NOT NULL,
239 sess_date INT UNSIGNED NOT NULL,
240 sess_ip CHAR(15) NOT NULL,
241 PRIMARY KEY (sess_id),
245 $this->dbi->genericSqlQuery("
246 CREATE TABLE $session_tbl (
247 sess_id CHAR(32) NOT NULL DEFAULT '',
248 sess_data ".($backend_type == 'pgsql'?'TEXT':'BLOB')." NOT NULL,
250 sess_ip CHAR(15) NOT NULL
252 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX sess_id ON $session_tbl (sess_id)");
254 $this->dbi->genericSqlQuery("CREATE INDEX sess_date on session (sess_date)");
255 echo " ",_("CREATED");
258 $pref_tbl = $prefix.'pref';
259 if ($backend_type == 'mysql') {
260 $this->dbi->genericSqlQuery("
261 CREATE TABLE $pref_tbl (
262 userid CHAR(48) BINARY NOT NULL UNIQUE,
263 prefs TEXT NULL DEFAULT '',
267 $this->dbi->genericSqlQuery("
268 CREATE TABLE $pref_tbl (
269 userid CHAR(48) NOT NULL,
270 prefs TEXT NULL DEFAULT ''
272 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX userid ON $pref_tbl (userid)");
274 echo " ",_("CREATED");
277 $member_tbl = $prefix.'member';
278 if ($backend_type == 'mysql') {
279 $this->dbi->genericSqlQuery("
280 CREATE TABLE $member_tbl (
281 userid CHAR(48) BINARY NOT NULL,
282 groupname CHAR(48) BINARY NOT NULL DEFAULT 'users',
287 $this->dbi->genericSqlQuery("
288 CREATE TABLE $member_tbl (
289 userid CHAR(48) NOT NULL,
290 groupname CHAR(48) NOT NULL DEFAULT 'users'
292 $this->dbi->genericSqlQuery("CREATE INDEX userid ON $member_tbl (userid)");
293 $this->dbi->genericSqlQuery("CREATE INDEX groupname ON $member_tbl (groupname)");
295 echo " ",_("CREATED");
298 $rating_tbl = $prefix.'rating';
299 if ($backend_type == 'mysql') {
300 $this->dbi->genericSqlQuery("
301 CREATE TABLE $rating_tbl (
302 dimension INT(4) NOT NULL,
303 raterpage INT(11) NOT NULL,
304 rateepage INT(11) NOT NULL,
305 ratingvalue FLOAT NOT NULL,
306 rateeversion INT(11) NOT NULL,
307 tstamp TIMESTAMP(14) NOT NULL,
308 PRIMARY KEY (dimension, raterpage, rateepage)
311 $this->dbi->genericSqlQuery("
312 CREATE TABLE $rating_tbl (
313 dimension INT(4) NOT NULL,
314 raterpage INT(11) NOT NULL,
315 rateepage INT(11) NOT NULL,
316 ratingvalue FLOAT NOT NULL,
317 rateeversion INT(11) NOT NULL,
318 tstamp TIMESTAMP(14) NOT NULL
320 $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX rating"
321 ." ON $rating_tbl (dimension, raterpage, rateepage)");
323 echo " ",_("CREATED");
326 $log_tbl = $prefix.'accesslog';
327 // fields according to http://www.outoforder.cc/projects/apache/mod_log_sql/docs-2.0/#id2756178
329 A User Agent agent varchar(255) Mozilla/4.0 (compat; MSIE 6.0; Windows)
330 a CGi request arguments request_args varchar(255) user=Smith&cart=1231&item=532
331 b Bytes transfered bytes_sent int unsigned 32561
332 c??? Text of cookie cookie varchar(255) Apache=sdyn.fooonline.net 1300102700823
333 f Local filename requested request_file varchar(255) /var/www/html/books-cycroad.html
334 H HTTP request_protocol request_protocol varchar(10) HTTP/1.1
335 h Name of remote host remote_host varchar(50) blah.foobar.com
336 I Request ID (from modd_unique_id) id char(19) POlFcUBRH30AAALdBG8
337 l Ident user info remote_logname varcgar(50) bobby
338 M Machine ID??? machine_id varchar(25) web01
339 m HTTP request method request_method varchar(10) GET
340 P httpd cchild PID child_pid smallint unsigned 3215
341 p http port server_port smallint unsigned 80
342 R Referer referer varchar(255) http://www.biglinks4u.com/linkpage.html
343 r Request in full form request_line varchar(255) GET /books-cycroad.html HTTP/1.1
344 S Time of request in UNIX time_t format time_stamp int unsigned 1005598029
345 T Seconds to service request request_duration smallint unsigned 2
346 t Time of request in human format request_time char(28) [02/Dec/2001:15:01:26 -0800]
347 U Request in simple form request_uri varchar(255) /books-cycroad.html
348 u User info from HTTP auth remote_user varchar(50) bobby
349 v Virtual host servicing the request virtual_host varchar(255)
351 $this->dbi->genericSqlQuery("
352 CREATE TABLE $log_tbl (
353 time_stamp int unsigned,
354 remote_host varchar(100),
355 remote_user varchar(50),
356 request_method varchar(10),
357 request_line varchar(255),
358 request_args varchar(255),
359 request_uri varchar(255),
360 request_time char(28),
361 status smallint unsigned,
362 bytes_sent smallint unsigned,
363 referer varchar(255),
365 request_duration float
367 $this->dbi->genericSqlQuery("CREATE INDEX log_time ON $log_tbl (time_stamp)");
368 $this->dbi->genericSqlQuery("CREATE INDEX log_host ON $log_tbl (remote_host)");
369 echo " ",_("CREATED");
376 * Update from ~1.3.4 to current.
377 * tables: Only session, user, pref and member
378 * jeffs-hacks database api (around 1.3.2) later:
379 * people should export/import their pages if using that old versions.
381 function CheckDatabaseUpdate() {
382 global $DBAuthParams, $DBParams;
384 echo "<h3>",sprintf(_("Check for necessary %s updates"),
386 " - ", DATABASE_TYPE,"</h3>\n";
387 $dbadmin = $this->request->getArg('dbadmin');
390 if (isset($dbadmin['cancel'])) {
391 echo _("CANCEL")," <br />\n";
395 echo "db version: we want ", $this->current_db_version, "\n<br />";
396 echo "db version: we have ", $this->db_version, "\n<br />";
397 if ($this->db_version >= $this->current_db_version) {
398 echo _("OK"), "<br />\n";
402 $backend_type = $this->dbi->_backend->backendType();
404 echo "<h4>",_("Backend type: "),$backend_type,"</h4>\n";
405 $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : '';
406 $tables = $this->dbi->_backend->listOfTables();
407 foreach (explode(':','session:pref:member') as $table) {
408 echo sprintf(_("Check for table %s"), $table)," ...";
409 if (!in_array($prefix.$table, $tables)) {
410 $this->installTable($table, $backend_type);
412 echo _("OK")," <br />\n";
417 if ($this->phpwiki_version >= 1030.12200612 and $this->db_version < 1030.13) {
418 if ($this->isSQL and preg_match("/(pgsql|postgres)/", $backend_type)) {
419 trigger_error("You need to upgrade to schema/psql-initialize.sql manually!",
421 // $this->_upgrade_psql_tsearch2();
423 $this->_upgrade_relation_links();
426 if (ACCESS_LOG_SQL and $this->isSQL) {
427 $table = "accesslog";
428 echo sprintf(_("Check for table %s"), $table)," ...";
429 if (!in_array($prefix.$table, $tables)) {
430 $this->installTable($table, $backend_type);
432 echo _("OK")," <br />\n";
435 if ($this->isSQL and (class_exists("RatingsUserFactory") or $this->dbi->isWikiPage(_("RateIt")))) {
437 echo sprintf(_("Check for table %s"), $table)," ...";
438 if (!in_array($prefix.$table, $tables)) {
439 $this->installTable($table, $backend_type);
441 echo _("OK")," <br />\n";
444 $backend = &$this->dbi->_backend->_dbh;
446 extract($this->dbi->_backend->_table_names);
448 // 1.3.8 added session.sess_ip
449 if ($this->isSQL and $this->phpwiki_version >= 1030.08 and USE_DB_SESSION
450 and isset($this->request->_dbsession))
452 echo _("Check for new session.sess_ip column")," ... ";
453 $database = $this->dbi->_backend->database();
454 assert(!empty($DBParams['db_session_table']));
455 $session_tbl = $prefix . $DBParams['db_session_table'];
456 $sess_fields = $this->dbi->_backend->listOfFields($database, $session_tbl);
459 } elseif (!strstr(strtolower(join(':', $sess_fields)), "sess_ip")) {
460 // TODO: postgres test (should be able to add columns at the end, but not in between)
461 echo "<b>",_("ADDING"),"</b>"," ... ";
462 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL");
463 $this->dbi->genericSqlQuery("CREATE INDEX sess_date ON $session_tbl (sess_date)");
468 if (substr($backend_type,0,5) == 'mysql') {
469 // upgrade to 4.1.8 destroyed my session table:
470 // sess_id => varchar(10), sess_data => varchar(5). For others obviously also.
471 echo _("Check for mysql session.sess_id sanity")," ... ";
472 $result = $this->dbi->genericSqlQuery("DESCRIBE $session_tbl");
473 if (DATABASE_TYPE == 'SQL') {
474 $iter = new WikiDB_backend_PearDB_generic_iter($backend, $result);
475 } elseif (DATABASE_TYPE == 'ADODB') {
476 $iter = new WikiDB_backend_ADODB_generic_iter($backend, $result,
477 array("Field", "Type", "Null", "Key", "Default", "Extra"));
478 } elseif (DATABASE_TYPE == 'PDO') {
479 $iter = new WikiDB_backend_PDO_generic_iter($backend, $result);
481 while ($col = $iter->next()) {
482 if ($col["Field"] == 'sess_id' and !strstr(strtolower($col["Type"]), 'char(32)')) {
483 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_id"
484 ." sess_id CHAR(32) NOT NULL");
485 echo "sess_id ", $col["Type"], " ", _("fixed"), " => CHAR(32) ";
487 if ($col["Field"] == 'sess_ip' and !strstr(strtolower($col["Type"]), 'char(15)')) {
488 $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_ip"
489 ." sess_ip CHAR(15) NOT NULL");
490 echo "sess_ip ", $col["Type"], " ", _("fixed"), " => CHAR(15) ";
493 echo _("OK"), "<br />\n";
498 ALTER TABLE link ADD relation INT DEFAULT 0;
499 CREATE INDEX linkrelation ON link (relation);
502 // mysql >= 4.0.4 requires LOCK TABLE privileges
503 if (substr($backend_type,0,5) == 'mysql') {
504 echo _("Check for mysql LOCK TABLE privilege")," ...";
505 $mysql_version = $this->dbi->_backend->_serverinfo['version'];
506 if ($mysql_version > 400.40) {
507 if (!empty($this->dbi->_backend->_parsedDSN))
508 $parseDSN = $this->dbi->_backend->_parsedDSN;
509 elseif (function_exists('parseDSN')) // ADODB or PDO
510 $parseDSN = parseDSN($DBParams['dsn']);
512 $parseDSN = DB::parseDSN($DBParams['dsn']);
513 $username = $this->dbi->_backend->qstr($parseDSN['username']);
515 $query = "SELECT lock_tables_priv FROM mysql.db WHERE user='$username'";
516 //mysql_select_db("mysql", $this->dbi->_backend->connection());
517 $db_fields = $this->dbi->_backend->listOfFields("mysql", "db");
518 if (!strstr(strtolower(join(':', $db_fields)), "lock_tables_priv")) {
519 echo join(':', $db_fields);
520 die("lock_tables_priv missing. The DB Admin must run mysql_fix_privilege_tables");
522 $row = $this->dbi->_backend->getRow($query);
523 if (isset($row[0]) and $row[0] == 'N') {
524 $this->dbi->genericSqlQuery("UPDATE mysql.db SET lock_tables_priv='Y'"
525 ." WHERE mysql.user='$username'");
526 $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
527 echo "mysql.db user='$username'", _("fixed"), "<br />\n";
530 $query = "SELECT lock_tables_priv FROM mysql.user WHERE user='$username'";
531 $row = $this->dbi->_backend->getRow($query);
532 if ($row and $row[0] == 'N') {
533 $this->dbi->genericSqlQuery("UPDATE mysql.user SET lock_tables_priv='Y'"
534 ." WHERE mysql.user='$username'");
535 $this->dbi->genericSqlQuery("FLUSH PRIVILEGES");
536 echo "mysql.user user='$username'", _("fixed"), "<br />\n";
538 echo " <b><font color=\"red\">", _("FAILED"), "</font></b>: ",
539 "Neither mysql.db nor mysql.user has a user='$username'"
540 ." or the lock_tables_priv field",
543 echo _("OK"), "<br />\n";
546 echo _("OK"), "<br />\n";
548 //mysql_select_db($this->dbi->_backend->database(), $this->dbi->_backend->connection());
550 echo sprintf(_("version <em>%s</em> not affected"), $mysql_version),"<br />\n";
554 // 1.3.10 mysql requires page.id auto_increment
555 // mysql, mysqli or mysqlt
556 if ($this->phpwiki_version >= 1030.099 and substr($backend_type,0,5) == 'mysql'
557 and DATABASE_TYPE != 'PDO')
559 echo _("Check for mysql page.id auto_increment flag")," ...";
560 assert(!empty($page_tbl));
561 $database = $this->dbi->_backend->database();
562 $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
563 $columns = mysql_num_fields($fields);
564 for ($i = 0; $i < $columns; $i++) {
565 if (mysql_field_name($fields, $i) == 'id') {
566 $flags = mysql_field_flags($fields, $i);
567 //DONE: something was wrong with ADODB here.
568 if (!strstr(strtolower($flags), "auto_increment")) {
569 echo "<b>",_("ADDING"),"</b>"," ... ";
570 // MODIFY col_def valid since mysql 3.22.16,
571 // older mysql's need CHANGE old_col col_def
572 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE id"
573 ." id INT NOT NULL AUTO_INCREMENT");
574 $fields = mysql_list_fields($database, $page_tbl);
575 if (!strstr(strtolower(mysql_field_flags($fields, $i)), "auto_increment"))
576 echo " <b><font color=\"red\">", _("FAILED"), "</font></b><br />\n";
578 echo _("OK"), "<br />\n";
580 echo _("OK"), "<br />\n";
585 mysql_free_result($fields);
588 // Check for mysql 4.1.x/5.0.0a binary search problem.
589 // http://bugs.mysql.com/bug.php?id=4398
590 // "select * from page where LOWER(pagename) like '%search%'" does not apply LOWER!
591 // Confirmed for 4.1.0alpha,4.1.3-beta,5.0.0a; not yet tested for 4.1.2alpha,
592 // On windows only, though utf8 would be useful elsewhere also.
593 // Illegal mix of collations (latin1_bin,IMPLICIT) and
594 // (utf8_general_ci, COERCIBLE) for operation '='])
595 if (isWindows() and substr($backend_type,0,5) == 'mysql') {
596 echo _("Check for mysql 4.1.x/5.0.0 binary search on windows problem")," ...";
597 $mysql_version = $this->dbi->_backend->_serverinfo['version'];
598 if ($mysql_version < 401.0) {
599 echo sprintf(_("version <em>%s</em>"), $mysql_version)," ",
600 _("not affected"),"<br />\n";
601 } elseif ($mysql_version >= 401.6) { // FIXME: since which version?
602 $row = $this->dbi->_backend->getRow("SHOW CREATE TABLE $page_tbl");
603 $result = join(" ", $row);
604 if (strstr(strtolower($result), "character set")
605 and strstr(strtolower($result), "collate"))
607 echo _("OK"), "<br />\n";
609 //SET CHARACTER SET latin1
611 if ($charset == 'iso-8859-1') $charset = 'latin1';
612 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename "
613 ."pagename VARCHAR(100) "
614 ."CHARACTER SET '$charset' COLLATE '$charset"."_bin' NOT NULL");
615 echo sprintf(_("version <em>%s</em>"), $mysql_version),
616 " <b>",_("FIXED"),"</b>",
619 } elseif (DATABASE_TYPE != 'PDO') {
620 // check if already fixed
621 extract($this->dbi->_backend->_table_names);
622 assert(!empty($page_tbl));
623 $database = $this->dbi->_backend->database();
624 $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection());
625 $columns = mysql_num_fields($fields);
626 for ($i = 0; $i < $columns; $i++) {
627 if (mysql_field_name($fields, $i) == 'pagename') {
628 $flags = mysql_field_flags($fields, $i);
629 // I think it was fixed with 4.1.6, but I tested it only with 4.1.8
630 if ($mysql_version > 401.0 and $mysql_version < 401.6) {
631 // remove the binary flag
632 if (strstr(strtolower($flags), "binary")) {
633 // FIXME: on duplicate pagenames this will fail!
634 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename"
635 ." pagename VARCHAR(100) NOT NULL");
636 echo sprintf(_("version <em>%s</em>"), $mysql_version),
637 "<b>",_("FIXED"),"</b>"
646 if ($this->isSQL and ACCESS_LOG_SQL & 2) {
647 echo _("Check for ACCESS_LOG_SQL passwords in POST requests")," ...";
648 // Don't display passwords in POST requests (up to 2005-02-04 12:03:20)
649 $res = $this->dbi->genericSqlIter("SELECT time_stamp, remote_host, " .
650 "request_args FROM ${prefix}accesslog WHERE request_args LIKE " .
651 "'%s:6:\"passwd\"%' AND request_args NOT LIKE '%s:6:\"passwd\";" .
652 "s:15:\"<not displayed>\"%'");
654 while ($row = $res->next()) {
655 $args = preg_replace("/(s:6:\"passwd\";s:15:\").*(\")/",
656 "$1<not displayed>$2", $row["request_args"]);
657 $ts = $row["time_stamp"];
658 $rh = $row["remote_host"];
659 $this->dbi->genericSqlQuery("UPDATE ${prefix}accesslog SET " .
660 "request_args='$args' WHERE time_stamp=$ts AND " .
661 "remote_host='$rh'");
665 echo "<b>",_("FIXED"),"</b>", "<br />\n";
667 echo _("OK"),"<br />\n";
669 if ($this->phpwiki_version >= 1030.13) {
670 echo _("Check for ACCESS_LOG_SQL remote_host varchar(50)")," ...";
671 $database = $this->dbi->_backend->database();
672 $accesslog_tbl = $prefix . 'accesslog';
673 $fields = $this->dbi->_backend->listOfFields($database, $accesslog_tbl);
676 } elseif (strstr(strtolower(join(':', $sess_fields)), "remote_host")) {
677 // TODO: how to check size, already done?
678 echo "<b>",_("FIXING"),"remote_host</b>"," ... ";
679 $this->dbi->genericSqlQuery("ALTER TABLE $accesslog_tbl CHANGE remote_host VARCHAR(100)");
686 $this->_upgrade_cached_html();
688 if ($this->db_version < $this->current_db_version) {
689 $this->dbi->set_db_version($this->current_db_version);
690 $this->db_version = $this->dbi->get_db_version();
691 echo "db version: upgrade to ", $this->db_version," ";
692 echo _("OK"), "<br />\n";
700 * Filter SQL missing permissions errors.
702 * A wrong DBADMIN user will not be able to connect
703 * @see _is_false_error, ErrorManager
706 function _dbpermission_filter($err) {
707 if ( $err->isWarning() ) {
708 global $ErrorManager;
709 $this->error_caught = 1;
710 $ErrorManager->_postponed_errors[] = $err;
716 function _try_dbadmin_user ($user, $passwd) {
717 global $DBParams, $DBAuthParams;
718 $AdminParams = $DBParams;
719 if (DATABASE_TYPE == 'SQL')
720 $dsn = DB::parseDSN($AdminParams['dsn']);
722 $dsn = parseDSN($AdminParams['dsn']);
724 $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s",
730 $AdminParams['_tryroot_from_upgrade'] = 1;
731 // add error handler to warn about missing permissions for DBADMIN_USER
732 global $ErrorManager;
733 $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_dbpermission_filter'));
734 $this->error_caught = 0;
735 $this->dbi = WikiDB::open($AdminParams);
736 if (!$this->error_caught) return true;
737 // FAILED: redo our connection with the wikiuser
738 $this->dbi = WikiDB::open($DBParams);
739 $ErrorManager->flushPostponedErrors();
740 $ErrorManager->popErrorHandler();
744 function _db_init () {
745 if (!$this->isSQL) return;
747 /* SQLite never needs admin params */
748 $backend_type = $this->dbi->_backend->backendType();
749 if (substr($backend_type,0,6)=="sqlite") {
752 $dbadmin_user = 'root';
753 if ($dbadmin = $this->request->getArg('dbadmin')) {
754 $dbadmin_user = $dbadmin['user'];
755 if (isset($dbadmin['cancel'])) {
757 } elseif (!empty($dbadmin_user)) {
758 if ($this->_try_dbadmin_user($dbadmin['user'], $dbadmin['passwd']))
761 } elseif (DBADMIN_USER) {
762 if ($this->_try_dbadmin_user(DBADMIN_USER, DBADMIN_PASSWD))
765 // Check if the privileges are enough. Need CREATE and ALTER perms.
766 // And on windows: SELECT FROM mysql, possibly: UPDATE mysql.
767 $form = HTML::form(array("method" => "post",
768 "action" => $this->request->getPostURL(),
769 "accept-charset"=>$GLOBALS['charset']),
770 HTML::p(_("Upgrade requires database privileges to CREATE and ALTER the phpwiki database."),
772 _("And on windows at least the privilege to SELECT FROM mysql, and possibly UPDATE mysql")),
773 HiddenInputs(array('action' => 'upgrade',
774 'overwrite' => $this->request->getArg('overwrite'))),
775 HTML::table(array("cellspacing"=>4),
776 HTML::tr(HTML::td(array('align'=>'right'),
777 _("DB admin user:")),
778 HTML::td(HTML::input(array('name'=>"dbadmin[user]",
781 'value'=>$dbadmin_user)))),
782 HTML::tr(HTML::td(array('align'=>'right'),
783 _("DB admin password:")),
784 HTML::td(HTML::input(array('name'=>"dbadmin[passwd]",
787 'maxlength'=>256)))),
788 HTML::tr(HTML::td(array('align'=>'center', 'colspan' => 2),
789 Button("submit:", _("Submit"), 'wikiaction'),
791 Button("submit:dbadmin[cancel]", _("Cancel"),
794 echo "</div><!-- content -->\n";
795 echo asXML(Template("bottom"));
796 echo "</body></html>\n";
797 $this->request->finish();
802 * if page.cached_html does not exists:
803 * put _cached_html from pagedata into a new seperate blob,
804 * not into the huge serialized string.
806 * It is only rarelely needed: for current page only, if-not-modified,
807 * but was extracetd for every simple page iteration.
809 function _upgrade_cached_html ( $verbose=true ) {
811 if (!$this->isSQL) return;
813 if ($this->phpwiki_version >= 1030.10) {
815 echo _("Check for extra page.cached_html column")," ... ";
816 $database = $this->dbi->_backend->database();
817 extract($this->dbi->_backend->_table_names);
818 $fields = $this->dbi->_backend->listOfFields($database, $page_tbl);
820 echo _("SKIP"), "<br />\n";
823 if (!strstr(strtolower(join(':', $fields)), "cached_html")) {
825 echo "<b>",_("ADDING"),"</b>"," ... ";
826 $backend_type = $this->dbi->_backend->backendType();
827 if (substr($backend_type,0,5) == 'mysql')
828 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html MEDIUMBLOB");
830 $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html BLOB");
832 echo "<b>",_("CONVERTING"),"</b>"," ... ";
833 $count = _convert_cached_html();
835 echo $count, " ", _("OK"), "<br />\n";
838 echo _("OK"), "<br />\n";
845 * move _cached_html for all pages from pagedata into a new seperate blob.
846 * decoupled from action=upgrade, so that it can be used by a WikiAdminUtils button also.
848 function _convert_cached_html () {
850 if (!$this->isSQL) return;
851 //if (!in_array(DATABASE_TYPE, array('SQL','ADODB'))) return;
853 $pages = $this->dbi->getAllPages();
854 $cache =& $this->dbi->_cache;
856 extract($this->dbi->_backend->_table_names);
857 while ($page = $pages->next()) {
858 $pagename = $page->getName();
859 $data = $this->dbi->_backend->get_pagedata($pagename);
860 if (!empty($data['_cached_html'])) {
861 $cached_html = $data['_cached_html'];
862 $data['_cached_html'] = '';
863 $cache->update_pagedata($pagename, $data);
864 // store as blob, not serialized
865 $this->dbi->genericSqlQuery("UPDATE $page_tbl SET cached_html=? WHERE pagename=?",
866 array($cached_html, $pagename));
874 * upgrade to 1.3.13 link structure.
876 function _upgrade_relation_links ( $verbose=true ) {
877 if ($this->phpwiki_version >= 1030.12200610 and $this->isSQL) {
878 echo _("Check for relation field in link table")," ...";
879 $database = $this->dbi->_backend->database();
880 $link_tbl = $prefix . 'link';
881 $fields = $this->dbi->_backend->listOfFields($database, $link_tbl);
884 } elseif (strstr(strtolower(join(':', $fields)), "link")) {
885 echo "<b>",_("ADDING")," relation</b>"," ... ";
886 $this->dbi->genericSqlQuery("ALTER TABLE $link_tbl ADD relation INT DEFAULT 0;");
887 $this->dbi->genericSqlQuery("CREATE INDEX link_relation ON $link_tbl (relation);");
893 if ($this->phpwiki_version >= 1030.12200610) {
894 echo _("Rebuild entire database to upgrade relation links")," ... ";
895 if (DATABASE_TYPE == 'dba') {
896 echo "<b>",_("CONVERTING")," dba linktable</b>","(~2 min, max 4 min) ... ";
899 $this->dbi->_backend->_linkdb->rebuild();
903 $this->dbi->_backend->rebuild();
905 echo _("OK"), "<br />\n";
909 function CheckPluginUpdate() {
912 echo "<h3>",sprintf(_("Check for necessary %s updates"),
913 _("plugin argument")),"</h3>\n";
915 $this->_configUpdates = array();
916 $this->_configUpdates[] = new UpgradePluginEntry
917 ($this, array('key' => 'plugin_randompage_numpages',
918 'fixed_with' => 1012.0,
919 //'header' => _("change RandomPage pages => numpages"),
920 //'notice' =>_("found RandomPage plugin"),
921 'check_args' => array("plugin RandomPage pages",
922 "/(<\?\s*plugin\s+ RandomPage\s+)pages/",
924 $this->_configUpdates[] = new UpgradePluginEntry
925 ($this, array('key' => 'plugin_createtoc_position',
926 'fixed_with' => 1013.0,
927 //'header' => _("change CreateToc align => position"),
928 //'notice' =>_("found CreateToc plugin"),
929 'check_args' => array("plugin CreateToc align",
930 "/(<\?\s*plugin\s+ CreateToc[^\?]+)align/",
933 if (empty($this->_configUpdates)) return;
934 foreach ($this->_configUpdates as $update) {
935 $pages = $this->dbi->fullSearch($this->check_args[0]);
936 while ($page = $allpages->next()) {
937 $current = $page->getCurrentRevision();
938 $pagetext = $current->getPackedContent();
939 $update->check($this->check_args[1], $this->check_args[2], $pagetext, $page, $current);
949 * preg_replace over local file.
950 * Only line-orientated matches possible.
952 function fixLocalFile($match, $replace, $filename) {
953 $o_filename = $filename;
954 if (!file_exists($filename))
955 $filename = FindFile($filename);
956 if (!file_exists($filename))
957 return array(false, sprintf(_("file %s not found"), $o_filename));
959 if (is_writable($filename)) {
960 $in = fopen($filename, "rb");
961 $out = fopen($tmp = tempnam(getUploadFilePath(),"cfg"), "wb");
963 $tmp = str_replace("/","\\",$tmp);
964 // Detect the existing linesep at first line. fgets strips it even if 'rb'.
965 // Before we simply assumed \r\n on windows local files.
966 $s = fread($in, 1024);
968 $linesep = (substr_count($s, "\r\n") > substr_count($s, "\n")) ? "\r\n" : "\n";
969 //$linesep = isWindows() ? "\r\n" : "\n";
970 while ($s = fgets($in)) {
971 // =>php-5.0.1 can fill count
972 //$new = preg_replace($match, $replace, $s, -1, $count);
973 $new = preg_replace($match, $replace, $s);
975 $s = $new . $linesep;
984 $reason = sprintf(_("%s not found in %s"), $match, $filename);
986 return array($found, $reason);
988 @unlink("$file.bak");
989 @rename($file,"$file.bak");
990 if (!rename($tmp, $file))
991 return array(false, sprintf(_("couldn't move %s to %s"), $tmp, $filename));
995 return array(false, sprintf(_("file %s is not writable"), $filename));
999 function CheckConfigUpdate () {
1000 echo "<h3>",sprintf(_("Check for necessary %s updates"),
1001 "config.ini"),"</h3>\n";
1002 $entry = new UpgradeConfigEntry
1003 ($this, array('key' => 'cache_control_none',
1004 'fixed_with' => 1012.0,
1005 'header' => sprintf(_("Check for %s"),"CACHE_CONTROL = NONE"),
1006 'applicable_args' => 'CACHE_CONTROL',
1007 'notice' => _("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'"),
1008 'check_args' => array("/^\s*CACHE_CONTROL\s*=\s*NONE/", "CACHE_CONTROL = NO_CACHE")));
1009 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1010 $this->_configUpdates[] = $entry;
1012 $entry = new UpgradeConfigEntry
1013 ($this, array('key' => 'group_method_none',
1014 'fixed_with' => 1012.0,
1015 'header' => sprintf(_("Check for %s"), "GROUP_METHOD = NONE"),
1016 'applicable_args' => 'GROUP_METHOD',
1017 'notice' =>_("GROUP_METHOD is set to NONE, and must be changed to \"NONE\""),
1018 'check_args' => array("/^\s*GROUP_METHOD\s*=\s*NONE/", "GROUP_METHOD = \"NONE\"")));
1019 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
1020 $this->_configUpdates[] = $entry;
1022 $entry = new UpgradeConfigEntry
1023 ($this, array('key' => 'blog_empty_default_prefix',
1024 'fixed_with' => 1013.0,
1025 'header' => sprintf(_("Check for %s"), "BLOG_EMPTY_DEFAULT_PREFIX"),
1026 'applicable_args' => 'BLOG_EMPTY_DEFAULT_PREFIX',
1027 'notice' =>_("fix BLOG_EMPTY_DEFAULT_PREFIX into BLOG_DEFAULT_EMPTY_PREFIX"),
1028 'check_args' => array("/BLOG_EMPTY_DEFAULT_PREFIX\s*=/","BLOG_DEFAULT_EMPTY_PREFIX =")));
1029 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined'));
1030 $this->_configUpdates[] = $entry;
1032 // TODO: find extra file updates
1033 if (empty($this->_configUpdates)) return;
1034 foreach ($this->_configUpdates as $update) {
1044 * Add an upgrade item to be checked.
1046 * @param $parent object The parent Upgrade class to inherit the version properties
1047 * @param $key string A short unique key to store success in the WikiDB
1048 * @param $fixed_with double @see phpwiki_version() number
1049 * @param $header string Optional header to be printed always even if not applicable
1050 * @param $applicable WikiCallback Optional callback boolean applicable()
1051 * @param $notice string Description of the check
1052 * @param $method WikiCallback Optional callback array method(array)
1053 * //param All other args are passed to $method
1055 function UpgradeEntry(&$parent, $params) {
1056 $this->parent =& $parent; // get the properties db_version
1057 foreach (array('key' => 'required',
1058 // the wikidb stores the version when we actually fixed that.
1059 'fixed_with' => 'required',
1060 'header' => '', // always printed
1061 'applicable_cb' => null, // method to check if applicable
1062 'applicable_args' => array(), // might be the config name
1064 'check_cb' => null, // method to apply
1065 'check_args' => array())
1068 if (!isset($params[$k])) { // default
1069 if ($v == 'required') trigger_error("Required arg $k missing", E_USER_ERROR);
1070 else $this->{$k} = $v;
1072 $this->{$k} = $params[$k];
1075 if (!is_array($this->applicable_args)) // single arg convenience shortcut
1076 $this->applicable_args = array($this->applicable_args);
1077 if (!is_array($this->check_args)) // single arg convenience shortcut
1078 $this->check_args = array($this->check_args);
1079 if ($this->notice === '' and count($this->applicable_args) > 0)
1080 $this->notice = 'Check for '.join(', ', $this->applicable_args);
1081 $this->_db_key = "_upgrade";
1082 $this->upgrade = $this->parent->dbi->get($this->_db_key);
1085 function setApplicableCb($object) {
1086 $this->applicable_cb =& $object;
1088 function _check_if_already_fixed() {
1090 if (!isset($this->upgrade['name'])) return false;
1091 // override with force?
1092 if ($this->parent->request->getArg('force')) return false;
1093 // already fixed and with an ok version
1094 if ($this->upgrade['name'] >= $this->fixed_with) return $this->upgrade['name'];
1095 // already fixed but with an older version. do it again.
1099 // store in db no to fix again
1100 $this->upgrade['name'] = $this->parent->phpwiki_version;
1101 $this->parent->dbi->set($this->_db_key, $this->upgrade);
1102 echo "<b>",_("FIXED"),"</b>";
1103 if (isset($this->reason))
1104 echo ": ", $this->reason;
1110 echo " <b><font color=\"red\">", _("FAILED"), "</font></b>";
1111 if (isset($this->reason))
1112 echo ": ", $this->reason;
1117 function skip() { // not applicable
1118 if (isset($this->silent_skip)) return true;
1119 echo _(" Skipped"),".<br />\n";
1123 function check($args = null) {
1124 if ($this->header) echo $this->header, ' ... ';
1125 if ($when = $this->_check_if_already_fixed()) {
1126 // be totally silent if no header is defined.
1127 if ($this->header) echo _("fixed with")," ",$when,"<br />\n";
1131 if (is_object($this->applicable_cb)) {
1132 if (!$this->applicable_cb->call_array($this->applicable_args))
1133 return $this->skip();
1135 if ($this->notice) {
1138 echo $this->notice," ";
1141 if (!is_null($args)) $this->check_args =& $args;
1142 if (is_object($this->check_cb))
1143 $do = $this->method_cb->call_array($this->check_args);
1145 $do = $this->default_method($this->check_args);
1146 if (is_array($do)) {
1147 $this->reason = $do[1];
1150 return $do ? $this->pass() : $this->fail();
1152 } // class UpgradeEntry
1154 class UpgradeConfigEntry extends UpgradeEntry {
1155 function _applicable_defined() {
1156 return (boolean)defined($this->applicable_args[0]);
1158 function _applicable_defined_and_empty() {
1159 $const = $this->applicable_args[0];
1160 return (boolean)(defined($const) and !constant($const));
1162 function default_method ($args) {
1164 $replace = $args[1];
1165 return $this->parent->fixLocalFile($match, $replace, "config/config.ini");
1167 } // class UpdateConfigEntry
1169 /* This is different */
1170 class UpgradePluginEntry extends UpgradeEntry {
1173 * check all pages for a plugin match
1175 var $silent_skip = 1;
1177 function default_method (&$args) {
1179 $replace = $args[1];
1180 $pagetext =& $args[2];
1182 $current =& $args[4];
1183 if (preg_match($match, $pagetext)) {
1184 echo $page->getName()," ",$this->notice," ... ";
1185 if ($newtext = preg_replace($match, $replace, $pagetext)) {
1186 $meta = $current->_data;
1187 $meta['summary'] = "upgrade: ".$this->header;
1188 $page->save($newtext, $current->getVersion() + 1, $meta);
1195 } // class UpdatePluginEntry
1198 * fix custom themes which are not in our distribution
1199 * this should be optional
1201 class UpgradeThemeEntry extends UpgradeEntry {
1203 function default_method (&$args) {
1205 $replace = $args[1];
1206 $template = $args[2];
1209 function fixThemeTemplate($match, $new, $template) {
1210 // for all custom themes
1211 $ourthemes = explode(":","blog:Crao:default:Hawaiian:MacOSX:MonoBook:Portland:shamino_com:SpaceWiki:wikilens:Wordpress");
1212 $themedir = NormalizeLocalFileName("themes");
1213 $dh = opendir($themedir);
1214 while ($r = readdir($dh)) {
1215 if (filetype($r) == 'dir' and $r[0] != '.' and !is_array($r, $ourthemes))
1216 $customthemes[] = $r;
1220 foreach ($customthemes as $customtheme) {
1221 $template = FindFile("themes/$customtheme/templates/$template");
1222 $do = $this->parent->fixLocalFile($match, $new, template);
1225 $errors .= $do[1]." ";
1229 return array($success, $errors);
1236 * Upgrade: Base class for multipage worksteps
1237 * identify, validate, display options, next step
1242 // TODO: At which step are we?
1243 // validate and do it again or go on with next step.
1245 /** entry function from lib/main.php
1247 function DoUpgrade(&$request) {
1249 if (!$request->_user->isAdmin()) {
1250 $request->_notAuthorized(WIKIAUTH_ADMIN);
1252 HTML::div(array('class' => 'disabled-plugin'),
1253 fmt("Upgrade disabled: user != isAdmin")));
1256 // TODO: StartLoadDump should turn on implicit_flush.
1257 @ini_set("implicit_flush", true);
1258 StartLoadDump($request, _("Upgrading this PhpWiki"));
1259 $upgrade = new Upgrade($request);
1260 //if (!$request->getArg('noindex'))
1261 // CheckOldIndexUpdate($request); // index.php => config.ini to upgrade from < 1.3.10
1262 if (!$request->getArg('nodb'))
1263 $upgrade->CheckDatabaseUpdate($request); // first check cached_html and friends
1264 if (!$request->getArg('nopgsrc')) {
1265 $upgrade->CheckPgsrcUpdate($request);
1266 $upgrade->CheckActionPageUpdate($request);
1268 if (!$request->getArg('noplugin'))
1269 $upgrade->CheckPluginUpdate($request);
1270 if (!$request->getArg('noconfig'))
1271 $upgrade->CheckConfigUpdate($request);
1272 // This is optional and should be linked. In EndLoadDump or PhpWikiAdministration?
1273 //if ($request->getArg('theme'))
1274 // $upgrade->CheckThemeUpdate($request);
1275 EndLoadDump($request);
1281 // c-basic-offset: 4
1282 // c-hanging-comment-ender-p: nil
1283 // indent-tabs-mode: nil