request =& $request; $this->dbi =& $request->_dbi; // no reference for dbadmin ? $this->phpwiki_version = $this->current_db_version = phpwiki_version(); //$this->current_db_version = 1030.13; // should be stored in the db. should be phpwiki_version $this->db_version = $this->dbi->get_db_version(); $this->isSQL = $this->dbi->_backend->isSQL(); } function doPgsrcUpdate($pagename, $path, $filename) { // don't ever update the HomePage if ((defined(HOME_PAGE) and ($pagename == HOME_PAGE)) or ($pagename == _("HomePage")) or ($pagename == "HomePage") ) { echo "$path/$pagename: " . _("always skip the HomePage.") . " " . _("Skipped."), "
\n"; return; } $page = $this->dbi->getPage($pagename); if ($page->exists()) { // check mtime: update automatically if pgsrc is newer $rev = $page->getCurrentRevision(); $page_mtime = $rev->get('mtime'); $data = implode("", file($path . "/" . $filename)); if (($parts = ParseMimeifiedPages($data))) { usort($parts, 'SortByPageVersion'); reset($parts); $pageinfo = $parts[0]; $stat = stat($path . "/" . $filename); $new_mtime = 0; if (isset($pageinfo['versiondata']['mtime'])) $new_mtime = $pageinfo['versiondata']['mtime']; if (!$new_mtime and isset($pageinfo['versiondata']['lastmodified'])) $new_mtime = $pageinfo['versiondata']['lastmodified']; if (!$new_mtime and isset($pageinfo['pagedata']['date'])) $new_mtime = $pageinfo['pagedata']['date']; if (!$new_mtime) $new_mtime = $stat[9]; if ($new_mtime > $page_mtime) { echo "$path/$pagename" . _(": ") . _("newer than the existing page.") . " " . _("Replace") . " " . "($new_mtime > $page_mtime)" . "
\n"; LoadAny($this->request, $path . "/" . $filename); echo "
\n"; } else { echo "$path/$pagename" . _(": ") . _("older than the existing page.") . " " . _("Skipped."), "
\n"; } } else { echo "$path/$pagename" . _(": ") . _("unknown format.") . " " . _("Skipped.") . "
\n"; } } else { echo sprintf(_("%s does not exist"), $pagename), "
\n"; LoadAny($this->request, $path . "/" . $filename); echo "
\n"; } } function CheckActionPageUpdate() { echo "

", sprintf(_("Check for necessary %s updates"), _("ActionPage")), "

\n"; // 1.3.13 before we pull in all missing pages, we rename existing ones $this->_rename_page_helper(_("_AuthInfo"), _("DebugAuthInfo")); // this is in some templates. so we keep the old name //$this->_rename_page_helper($this->dbi, _("DebugInfo"), _("DebugBackendInfo")); $this->_rename_page_helper(_("_GroupInfo"), _("GroupAuthInfo")); //never officially existed $this->_rename_page_helper("InterWikiKarte", "InterWikiListe"); // german only $path = FindFile('pgsrc'); $pgsrc = new fileSet($path); // most actionpages have the same name as the plugin $loc_path = FindLocalizedFile('pgsrc'); foreach ($pgsrc->getFiles() as $filename) { if (substr($filename, -1, 1) == '~') continue; if (substr($filename, -5, 5) == '.orig') continue; $pagename = urldecode($filename); if (isActionPage($pagename)) { $translation = gettext($pagename); if ($translation == $pagename) $this->doPgsrcUpdate($pagename, $path, $filename); elseif (FindLocalizedFile('pgsrc/' . urlencode($translation), 1)) $this->doPgsrcUpdate($translation, $loc_path, urlencode($translation)); else $this->doPgsrcUpdate($pagename, $path, $filename); } } } // see loadsave.php for saving new pages. function CheckPgsrcUpdate() { // Check some theme specific pgsrc files (blog, wikilens, fusionforge, custom). // We check theme specific pgsrc first in case the page is present in both // theme specific and global pgsrc global $WikiTheme; $path = $WikiTheme->file("pgsrc"); // TBD: the call to fileSet prints a warning: // Notice: Unable to open directory 'themes/MonoBook/pgsrc' for reading $pgsrc = new fileSet($path); if ($pgsrc->getFiles()) { echo "

", sprintf(_("Check for necessary theme %s updates"), "pgsrc"), "

\n"; foreach ($pgsrc->getFiles() as $filename) { if (substr($filename, -1, 1) == '~') continue; if (substr($filename, -5, 5) == '.orig') continue; $pagename = urldecode($filename); $this->doPgsrcUpdate($pagename, $path, $filename); } } echo "

", sprintf(_("Check for necessary %s updates"), "pgsrc"), "

\n"; if ($this->db_version < 1030.12200612) { echo "

", _("rename to Help: pages"), "

\n"; } $path = FindLocalizedFile(WIKI_PGSRC); $pgsrc = new fileSet($path); // fixme: verification, ... foreach ($pgsrc->getFiles() as $filename) { if (substr($filename, -1, 1) == '~') continue; if (substr($filename, -5, 5) == '.orig') continue; $pagename = urldecode($filename); if (!isActionPage($filename)) { // There're a lot of now unneeded pages around. // At first rename the BlaPlugin pages to Help/ and then to the update. if ($this->db_version < 1030.12200612) { $this->_rename_to_help_page($pagename); } $this->doPgsrcUpdate($pagename, $path, $filename); } } } function _rename_page_helper($oldname, $pagename) { echo sprintf(_("rename %s to %s"), $oldname, $pagename), " ..."; if ($this->dbi->isWikiPage($oldname) and !$this->dbi->isWikiPage($pagename)) { if ($this->dbi->_backend->rename_page($oldname, $pagename)) { echo _("OK"), "
\n"; } else { echo ' ' . _("FAILED") . "
\n"; } } else { echo " " . _("Skipped.") . "
\n"; } } function _rename_to_help_page($pagename) { $newprefix = _("Help") . "/"; if (substr($pagename, 0, strlen($newprefix)) != $newprefix) return; $oldname = substr($pagename, strlen($newprefix)); $this->_rename_page_helper($oldname, $pagename); } /** * TODO: Search table definition in appropriate schema * and create it. * Supported: mysql and generic SQL, for ADODB and PearDB. */ function installTable($table, $backend_type) { global $DBParams; if (!$this->isSQL) return; echo _("MISSING"), " ... \n"; $backend = &$this->dbi->_backend->_dbh; /* $schema = findFile("schemas/${backend_type}.sql"); if (!$schema) { echo " ",_("FAILED"),": ",sprintf(_("no schema %s found"), "schemas/${backend_type}.sql")," ...
\n"; return false; } */ extract($this->dbi->_backend->_table_names); $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : ''; switch ($table) { case 'session': assert($session_tbl); if ($backend_type == 'mysql') { $this->dbi->genericSqlQuery(" CREATE TABLE $session_tbl ( sess_id CHAR(32) NOT NULL DEFAULT '', sess_data BLOB NOT NULL, sess_date INT UNSIGNED NOT NULL, sess_ip CHAR(15) NOT NULL, PRIMARY KEY (sess_id), INDEX (sess_date) )"); } else { $this->dbi->genericSqlQuery(" CREATE TABLE $session_tbl ( sess_id CHAR(32) NOT NULL DEFAULT '', sess_data " . ($backend_type == 'pgsql' ? 'TEXT' : 'BLOB') . " NOT NULL, sess_date INT, sess_ip CHAR(15) NOT NULL )"); $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX sess_id ON $session_tbl (sess_id)"); } $this->dbi->genericSqlQuery("CREATE INDEX sess_date on session (sess_date)"); echo " ", _("CREATED"); break; case 'pref': $pref_tbl = $prefix . 'pref'; if ($backend_type == 'mysql') { $this->dbi->genericSqlQuery(" CREATE TABLE $pref_tbl ( userid CHAR(48) BINARY NOT NULL UNIQUE, prefs TEXT NULL DEFAULT '', PRIMARY KEY (userid) )"); } else { $this->dbi->genericSqlQuery(" CREATE TABLE $pref_tbl ( userid CHAR(48) NOT NULL, prefs TEXT NULL DEFAULT '' )"); $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX userid ON $pref_tbl (userid)"); } echo " ", _("CREATED"); break; case 'member': $member_tbl = $prefix . 'member'; if ($backend_type == 'mysql') { $this->dbi->genericSqlQuery(" CREATE TABLE $member_tbl ( userid CHAR(48) BINARY NOT NULL, groupname CHAR(48) BINARY NOT NULL DEFAULT 'users', INDEX (userid), INDEX (groupname) )"); } else { $this->dbi->genericSqlQuery(" CREATE TABLE $member_tbl ( userid CHAR(48) NOT NULL, groupname CHAR(48) NOT NULL DEFAULT 'users' )"); $this->dbi->genericSqlQuery("CREATE INDEX userid ON $member_tbl (userid)"); $this->dbi->genericSqlQuery("CREATE INDEX groupname ON $member_tbl (groupname)"); } echo " ", _("CREATED"); break; case 'rating': $rating_tbl = $prefix . 'rating'; if ($backend_type == 'mysql') { $this->dbi->genericSqlQuery(" CREATE TABLE $rating_tbl ( dimension INT(4) NOT NULL, raterpage INT(11) NOT NULL, rateepage INT(11) NOT NULL, ratingvalue FLOAT NOT NULL, rateeversion INT(11) NOT NULL, tstamp TIMESTAMP(14) NOT NULL, PRIMARY KEY (dimension, raterpage, rateepage) )"); } else { $this->dbi->genericSqlQuery(" CREATE TABLE $rating_tbl ( dimension INT(4) NOT NULL, raterpage INT(11) NOT NULL, rateepage INT(11) NOT NULL, ratingvalue FLOAT NOT NULL, rateeversion INT(11) NOT NULL, tstamp TIMESTAMP(14) NOT NULL )"); $this->dbi->genericSqlQuery("CREATE UNIQUE INDEX rating" . " ON $rating_tbl (dimension, raterpage, rateepage)"); } echo " ", _("CREATED"); break; case 'accesslog': $log_tbl = $prefix . 'accesslog'; // fields according to http://www.outoforder.cc/projects/apache/mod_log_sql/docs-2.0/#id2756178 /* A User Agent agent varchar(255) Mozilla/4.0 (compat; MSIE 6.0; Windows) a CGi request arguments request_args varchar(255) user=Smith&cart=1231&item=532 b Bytes transfered bytes_sent int unsigned 32561 c??? Text of cookie cookie varchar(255) Apache=sdyn.fooonline.net 1300102700823 f Local filename requested request_file varchar(255) /var/www/html/books-cycroad.html H HTTP request_protocol request_protocol varchar(10) HTTP/1.1 h Name of remote host remote_host varchar(50) blah.foobar.com I Request ID (from modd_unique_id) id char(19) POlFcUBRH30AAALdBG8 l Ident user info remote_logname varcgar(50) bobby M Machine ID??? machine_id varchar(25) web01 m HTTP request method request_method varchar(10) GET P httpd cchild PID child_pid smallint unsigned 3215 p http port server_port smallint unsigned 80 R Referer referer varchar(255) http://www.biglinks4u.com/linkpage.html r Request in full form request_line varchar(255) GET /books-cycroad.html HTTP/1.1 S Time of request in UNIX time_t format time_stamp int unsigned 1005598029 T Seconds to service request request_duration smallint unsigned 2 t Time of request in human format request_time char(28) [02/Dec/2001:15:01:26 -0800] U Request in simple form request_uri varchar(255) /books-cycroad.html u User info from HTTP auth remote_user varchar(50) bobby v Virtual host servicing the request virtual_host varchar(255) */ $this->dbi->genericSqlQuery(" CREATE TABLE $log_tbl ( time_stamp int unsigned, remote_host varchar(100), remote_user varchar(50), request_method varchar(10), request_line varchar(255), request_args varchar(255), request_uri varchar(255), request_time char(28), status smallint unsigned, bytes_sent smallint unsigned, referer varchar(255), agent varchar(255), request_duration float )"); $this->dbi->genericSqlQuery("CREATE INDEX log_time ON $log_tbl (time_stamp)"); $this->dbi->genericSqlQuery("CREATE INDEX log_host ON $log_tbl (remote_host)"); echo " ", _("CREATED"); break; } echo "
\n"; } /** * Update from ~1.3.4 to current. * tables: Only session, user, pref and member * jeffs-hacks database api (around 1.3.2) later: * people should export/import their pages if using that old versions. */ function CheckDatabaseUpdate() { global $DBAuthParams, $DBParams; echo "

", sprintf(_("Check for necessary %s updates"), _("database")), " - ", DATABASE_TYPE, "

\n"; $dbadmin = $this->request->getArg('dbadmin'); if ($this->isSQL) { $this->_db_init(); if (isset($dbadmin['cancel'])) { echo _("Cancel"), "
\n"; return; } } echo "db version: we want ", $this->current_db_version, "\n
"; echo "db version: we have ", $this->db_version, "\n
"; if ($this->db_version >= $this->current_db_version) { echo _("OK"), "
\n"; return; } $backend_type = $this->dbi->_backend->backendType(); if ($this->isSQL) { echo "

", _("Backend type: "), $backend_type, "

\n"; $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : ''; $tables = $this->dbi->_backend->listOfTables(); foreach (explode(':', 'session:pref:member') as $table) { echo sprintf(_("Check for table %s"), $table), " ..."; if (!in_array($prefix . $table, $tables)) { $this->installTable($table, $backend_type); } else { echo _("OK"), "
\n"; } } } if ($this->phpwiki_version >= 1030.12200612 and $this->db_version < 1030.13) { if ($this->isSQL and preg_match("/(pgsql|postgres)/", $backend_type)) { trigger_error("You need to upgrade to schema/psql-initialize.sql manually!", E_USER_WARNING); // $this->_upgrade_psql_tsearch2(); } $this->_upgrade_relation_links(); } if (ACCESS_LOG_SQL and $this->isSQL) { $table = "accesslog"; echo sprintf(_("Check for table %s"), $table), " ..."; if (!in_array($prefix . $table, $tables)) { $this->installTable($table, $backend_type); } else { echo _("OK"), "
\n"; } } if ($this->isSQL and (class_exists("RatingsUserFactory") or $this->dbi->isWikiPage(_("RateIt")))) { $table = "rating"; echo sprintf(_("Check for table %s"), $table), " ..."; if (!in_array($prefix . $table, $tables)) { $this->installTable($table, $backend_type); } else { echo _("OK"), "
\n"; } } $backend = &$this->dbi->_backend->_dbh; if ($this->isSQL) extract($this->dbi->_backend->_table_names); // 1.3.8 added session.sess_ip if ($this->isSQL and $this->phpwiki_version >= 1030.08 and USE_DB_SESSION and isset($this->request->_dbsession) ) { echo _("Check for new session.sess_ip column"), " ... "; $database = $this->dbi->_backend->database(); assert(!empty($DBParams['db_session_table'])); $session_tbl = $prefix . $DBParams['db_session_table']; $sess_fields = $this->dbi->_backend->listOfFields($database, $session_tbl); if (!$sess_fields) { echo _("SKIP"); } elseif (!strstr(strtolower(join(':', $sess_fields)), "sess_ip")) { // TODO: postgres test (should be able to add columns at the end, but not in between) echo "", _("ADDING"), "", " ... "; $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl ADD sess_ip CHAR(15) NOT NULL"); $this->dbi->genericSqlQuery("CREATE INDEX sess_date ON $session_tbl (sess_date)"); } else { echo _("OK"); } echo "
\n"; if (substr($backend_type, 0, 5) == 'mysql') { // upgrade to 4.1.8 destroyed my session table: // sess_id => varchar(10), sess_data => varchar(5). For others obviously also. echo _("Check for mysql session.sess_id sanity"), " ... "; $result = $this->dbi->genericSqlQuery("DESCRIBE $session_tbl"); if (DATABASE_TYPE == 'SQL') { $iter = new WikiDB_backend_PearDB_generic_iter($backend, $result); } elseif (DATABASE_TYPE == 'ADODB') { $iter = new WikiDB_backend_ADODB_generic_iter($backend, $result, array("Field", "Type", "Null", "Key", "Default", "Extra")); } elseif (DATABASE_TYPE == 'PDO') { $iter = new WikiDB_backend_PDO_generic_iter($backend, $result); } while ($col = $iter->next()) { if ($col["Field"] == 'sess_id' and !strstr(strtolower($col["Type"]), 'char(32)')) { $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_id" . " sess_id CHAR(32) NOT NULL"); echo "sess_id ", $col["Type"], " ", _("fixed"), " => CHAR(32) "; } if ($col["Field"] == 'sess_ip' and !strstr(strtolower($col["Type"]), 'char(15)')) { $this->dbi->genericSqlQuery("ALTER TABLE $session_tbl CHANGE sess_ip" . " sess_ip CHAR(15) NOT NULL"); echo "sess_ip ", $col["Type"], " ", _("fixed"), " => CHAR(15) "; } } echo _("OK"), "
\n"; } } /* TODO: ALTER TABLE link ADD relation INT DEFAULT 0; CREATE INDEX linkrelation ON link (relation); */ // mysql >= 4.0.4 requires LOCK TABLE privileges if (substr($backend_type, 0, 5) == 'mysql') { echo _("Check for mysql LOCK TABLE privilege"), " ..."; $mysql_version = $this->dbi->_backend->_serverinfo['version']; if ($mysql_version > 400.40) { if (!empty($this->dbi->_backend->_parsedDSN)) $parseDSN = $this->dbi->_backend->_parsedDSN; elseif (function_exists('parseDSN')) // ADODB or PDO $parseDSN = parseDSN($DBParams['dsn']); else // pear $parseDSN = DB::parseDSN($DBParams['dsn']); $username = $this->dbi->_backend->qstr($parseDSN['username']); // on db level $query = "SELECT lock_tables_priv FROM mysql.db WHERE user='$username'"; //mysql_select_db("mysql", $this->dbi->_backend->connection()); $db_fields = $this->dbi->_backend->listOfFields("mysql", "db"); if (!strstr(strtolower(join(':', $db_fields)), "lock_tables_priv")) { echo join(':', $db_fields); die("lock_tables_priv missing. The DB Admin must run mysql_fix_privilege_tables"); } $row = $this->dbi->_backend->getRow($query); if (isset($row[0]) and $row[0] == 'N') { $this->dbi->genericSqlQuery("UPDATE mysql.db SET lock_tables_priv='Y'" . " WHERE mysql.user='$username'"); $this->dbi->genericSqlQuery("FLUSH PRIVILEGES"); echo "mysql.db user='$username'", _("fixed"), "
\n"; } elseif (!$row) { // or on user level $query = "SELECT lock_tables_priv FROM mysql.user WHERE user='$username'"; $row = $this->dbi->_backend->getRow($query); if ($row and $row[0] == 'N') { $this->dbi->genericSqlQuery("UPDATE mysql.user SET lock_tables_priv='Y'" . " WHERE mysql.user='$username'"); $this->dbi->genericSqlQuery("FLUSH PRIVILEGES"); echo "mysql.user user='$username'", _("fixed"), "
\n"; } elseif (!$row) { echo ' ' . _("FAILED") . "" . " Neither mysql.db nor mysql.user has a user='$username'" . " or the lock_tables_priv field", "
\n"; } else { echo _("OK"), "
\n"; } } else { echo _("OK"), "
\n"; } //mysql_select_db($this->dbi->_backend->database(), $this->dbi->_backend->connection()); } else { echo sprintf(_("version %s not affected"), $mysql_version), "
\n"; } } // 1.3.10 mysql requires page.id auto_increment // mysql, mysqli or mysqlt if ($this->phpwiki_version >= 1030.099 and substr($backend_type, 0, 5) == 'mysql' and DATABASE_TYPE != 'PDO' ) { echo _("Check for mysql page.id auto_increment flag"), " ..."; assert(!empty($page_tbl)); $database = $this->dbi->_backend->database(); $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection()); $columns = mysql_num_fields($fields); for ($i = 0; $i < $columns; $i++) { if (mysql_field_name($fields, $i) == 'id') { $flags = mysql_field_flags($fields, $i); //DONE: something was wrong with ADODB here. if (!strstr(strtolower($flags), "auto_increment")) { echo "", _("ADDING"), "", " ... "; // MODIFY col_def valid since mysql 3.22.16, // older mysql's need CHANGE old_col col_def $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE id" . " id INT NOT NULL AUTO_INCREMENT"); $fields = mysql_list_fields($database, $page_tbl); if (!strstr(strtolower(mysql_field_flags($fields, $i)), "auto_increment")) echo ' ' . _("FAILED") . "
\n"; else echo _("OK"), "
\n"; } else { echo _("OK"), "
\n"; } break; } } mysql_free_result($fields); } // Check for mysql 4.1.x/5.0.0a binary search problem. // http://bugs.mysql.com/bug.php?id=4398 // "select * from page where LOWER(pagename) like '%search%'" does not apply LOWER! // Confirmed for 4.1.0alpha,4.1.3-beta,5.0.0a; not yet tested for 4.1.2alpha, // On windows only, though utf8 would be useful elsewhere also. // Illegal mix of collations (latin1_bin,IMPLICIT) and // (utf8_general_ci, COERCIBLE) for operation '=']) if (isWindows() and substr($backend_type, 0, 5) == 'mysql') { echo _("Check for mysql 4.1.x/5.0.0 binary search on Windows problem"), " ..."; $mysql_version = $this->dbi->_backend->_serverinfo['version']; if ($mysql_version < 401.0) { echo sprintf(_("version %s"), $mysql_version), " ", _("not affected"), "
\n"; } elseif ($mysql_version >= 401.6) { // FIXME: since which version? $row = $this->dbi->_backend->getRow("SHOW CREATE TABLE $page_tbl"); $result = join(" ", $row); if (strstr(strtolower($result), "character set") and strstr(strtolower($result), "collate") ) { echo _("OK"), "
\n"; } else { $charset = 'UTF-8'; $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename " . "pagename VARCHAR(100) " . "CHARACTER SET '$charset' COLLATE '$charset" . "_bin' NOT NULL"); echo sprintf(_("version %s"), $mysql_version), " ", _("FIXED"), "", "
\n"; } } elseif (DATABASE_TYPE != 'PDO') { // check if already fixed extract($this->dbi->_backend->_table_names); assert(!empty($page_tbl)); $database = $this->dbi->_backend->database(); $fields = mysql_list_fields($database, $page_tbl, $this->dbi->_backend->connection()); $columns = mysql_num_fields($fields); for ($i = 0; $i < $columns; $i++) { if (mysql_field_name($fields, $i) == 'pagename') { $flags = mysql_field_flags($fields, $i); // I think it was fixed with 4.1.6, but I tested it only with 4.1.8 if ($mysql_version > 401.0 and $mysql_version < 401.6) { // remove the binary flag if (strstr(strtolower($flags), "binary")) { // FIXME: on duplicate pagenames this will fail! $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl CHANGE pagename" . " pagename VARCHAR(100) NOT NULL"); echo sprintf(_("version %s"), $mysql_version), "", _("FIXED"), "" , "
\n"; } } break; } } } } if ($this->isSQL and ACCESS_LOG_SQL & 2) { echo _("Check for ACCESS_LOG_SQL passwords in POST requests"), " ..."; // Don't display passwords in POST requests (up to 2005-02-04 12:03:20) $res = $this->dbi->genericSqlIter("SELECT time_stamp, remote_host, " . "request_args FROM ${prefix}accesslog WHERE request_args LIKE " . "'%s:6:\"passwd\"%' AND request_args NOT LIKE '%s:6:\"passwd\";" . "s:15:\"\"%'"); $count = 0; while ($row = $res->next()) { $args = preg_replace("/(s:6:\"passwd\";s:15:\").*(\")/", "$1$2", $row["request_args"]); $ts = $row["time_stamp"]; $rh = $row["remote_host"]; $this->dbi->genericSqlQuery("UPDATE ${prefix}accesslog SET " . "request_args='$args' WHERE time_stamp=$ts AND " . "remote_host='$rh'"); $count++; } if ($count > 0) echo "", _("FIXED"), "", "
\n"; else echo _("OK"), "
\n"; if ($this->phpwiki_version >= 1030.13) { echo _("Check for ACCESS_LOG_SQL remote_host varchar(50)"), " ..."; $database = $this->dbi->_backend->database(); $accesslog_tbl = $prefix . 'accesslog'; $fields = $this->dbi->_backend->listOfFields($database, $accesslog_tbl); if (!$fields) { echo _("SKIP"); } elseif (strstr(strtolower(join(':', $sess_fields)), "remote_host")) { // TODO: how to check size, already done? echo "", _("FIXING"), "remote_host", " ... "; $this->dbi->genericSqlQuery("ALTER TABLE $accesslog_tbl CHANGE remote_host VARCHAR(100)"); } else { echo _("FAILED"); } echo "
\n"; } } $this->_upgrade_cached_html(); if ($this->db_version < $this->current_db_version) { $this->dbi->set_db_version($this->current_db_version); $this->db_version = $this->dbi->get_db_version(); echo "db version: upgrade to ", $this->db_version, " "; echo _("OK"), "
\n"; flush(); } return; } /** * Filter SQL missing permissions errors. * * A wrong DBADMIN user will not be able to connect * @see _is_false_error, ErrorManager * @access private */ function _dbpermission_filter($err) { if ($err->isWarning()) { global $ErrorManager; $this->error_caught = 1; $ErrorManager->_postponed_errors[] = $err; return true; } return false; } function _try_dbadmin_user($user, $passwd) { global $DBParams, $DBAuthParams; $AdminParams = $DBParams; if (DATABASE_TYPE == 'SQL') $dsn = DB::parseDSN($AdminParams['dsn']); else { $dsn = parseDSN($AdminParams['dsn']); } $AdminParams['dsn'] = sprintf("%s://%s:%s@%s/%s", $dsn['phptype'], $user, $passwd, $dsn['hostspec'], $dsn['database']); $AdminParams['_tryroot_from_upgrade'] = 1; // add error handler to warn about missing permissions for DBADMIN_USER global $ErrorManager; $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_dbpermission_filter')); $this->error_caught = 0; $this->dbi = WikiDB::open($AdminParams); if (!$this->error_caught) return true; // FAILED: redo our connection with the wikiuser $this->dbi = WikiDB::open($DBParams); $ErrorManager->flushPostponedErrors(); $ErrorManager->popErrorHandler(); return false; } function _db_init() { if (!$this->isSQL) return; /* SQLite never needs admin params */ $backend_type = $this->dbi->_backend->backendType(); if (substr($backend_type, 0, 6) == "sqlite") { return; } $dbadmin_user = 'root'; if ($dbadmin = $this->request->getArg('dbadmin')) { $dbadmin_user = $dbadmin['user']; if (isset($dbadmin['cancel'])) { return; } elseif (!empty($dbadmin_user)) { if ($this->_try_dbadmin_user($dbadmin['user'], $dbadmin['passwd'])) return; } } elseif (DBADMIN_USER) { if ($this->_try_dbadmin_user(DBADMIN_USER, DBADMIN_PASSWD)) { return; } } // Check if the privileges are enough. Need CREATE and ALTER perms. // And on Windows: SELECT FROM mysql, possibly: UPDATE mysql. $form = HTML::form(array("method" => "post", "action" => $this->request->getPostURL(), "accept-charset" => 'UTF-8'), HTML::p(_("Upgrade requires database privileges to CREATE and ALTER the phpwiki database."), HTML::br(), _("And on Windows at least the privilege to SELECT FROM mysql, and possibly UPDATE mysql")), HiddenInputs(array('action' => 'upgrade', 'overwrite' => $this->request->getArg('overwrite'))), HTML::table(array("cellspacing" => 4), HTML::tr(HTML::td(array('align' => 'right'), _("DB admin user:")), HTML::td(HTML::input(array('name' => "dbadmin[user]", 'size' => 12, 'maxlength' => 256, 'value' => $dbadmin_user)))), HTML::tr(HTML::td(array('align' => 'right'), _("DB admin password:")), HTML::td(HTML::input(array('name' => "dbadmin[passwd]", 'type' => 'password', 'size' => 12, 'maxlength' => 256)))), HTML::tr(HTML::td(array('align' => 'center', 'colspan' => 2), Button("submit:", _("Submit"), 'wikiaction'), HTML::raw(' '), Button("submit:dbadmin[cancel]", _("Cancel"), 'button'))))); $form->printXml(); echo "\n"; echo asXML(Template("bottom")); echo "\n"; $this->request->finish(); exit(); } /** * if page.cached_html does not exists: * put _cached_html from pagedata into a new separate blob, * not into the huge serialized string. * * It is only rarelely needed: for current page only, if-not-modified, * but was extracetd for every simple page iteration. */ function _upgrade_cached_html($verbose = true) { global $DBParams; if (!$this->isSQL) return 0; $count = 0; if ($this->phpwiki_version >= 1030.10) { if ($verbose) echo _("Check for extra page.cached_html column"), " ... "; $database = $this->dbi->_backend->database(); extract($this->dbi->_backend->_table_names); $fields = $this->dbi->_backend->listOfFields($database, $page_tbl); if (!$fields) { echo _("SKIP"), "
\n"; return 0; } if (!strstr(strtolower(join(':', $fields)), "cached_html")) { if ($verbose) echo "", _("ADDING"), "", " ... "; $backend_type = $this->dbi->_backend->backendType(); if (substr($backend_type, 0, 5) == 'mysql') $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html MEDIUMBLOB"); else $this->dbi->genericSqlQuery("ALTER TABLE $page_tbl ADD cached_html BLOB"); if ($verbose) echo "", _("CONVERTING"), "", " ... "; $count = _convert_cached_html(); if ($verbose) echo $count, " ", _("OK"), "
\n"; } else { if ($verbose) echo _("OK"), "
\n"; } } return $count; } /** * move _cached_html for all pages from pagedata into a new separate blob. * decoupled from action=upgrade, so that it can be used by a WikiAdminUtils button also. */ function _convert_cached_html() { global $DBParams; if (!$this->isSQL) return 0; //if (!in_array(DATABASE_TYPE, array('SQL','ADODB'))) return; $pages = $this->dbi->getAllPages(); $cache =& $this->dbi->_cache; $count = 0; extract($this->dbi->_backend->_table_names); while ($page = $pages->next()) { $pagename = $page->getName(); $data = $this->dbi->_backend->get_pagedata($pagename); if (!empty($data['_cached_html'])) { $cached_html = $data['_cached_html']; $data['_cached_html'] = ''; $cache->update_pagedata($pagename, $data); // store as blob, not serialized $this->dbi->genericSqlQuery("UPDATE $page_tbl SET cached_html=? WHERE pagename=?", array($cached_html, $pagename)); $count++; } } return $count; } /** * upgrade to 1.3.13 link structure. */ function _upgrade_relation_links($verbose = true) { if ($this->phpwiki_version >= 1030.12200610 and $this->isSQL) { echo _("Check for relation field in link table"), " ..."; $database = $this->dbi->_backend->database(); $prefix = isset($DBParams['prefix']) ? $DBParams['prefix'] : ''; $link_tbl = $prefix . 'link'; $fields = $this->dbi->_backend->listOfFields($database, $link_tbl); if (!$fields) { echo _("SKIP"); } elseif (strstr(strtolower(join(':', $fields)), "link")) { echo "", _("ADDING"), " relation", " ... "; $this->dbi->genericSqlQuery("ALTER TABLE $link_tbl ADD relation INT DEFAULT 0;"); $this->dbi->genericSqlQuery("CREATE INDEX link_relation ON $link_tbl (relation);"); } else { echo _("FAILED"); } echo "
\n"; } if ($this->phpwiki_version >= 1030.12200610) { echo _("Rebuild entire database to upgrade relation links"), " ... "; if (DATABASE_TYPE == 'dba') { echo "", _("CONVERTING"), " dba linktable", "(~2 min, max 4 min) ... "; flush(); longer_timeout(240); $this->dbi->_backend->_linkdb->rebuild(); } else { flush(); longer_timeout(180); $this->dbi->_backend->rebuild(); } echo _("OK"), "
\n"; } } function CheckPluginUpdate() { echo "

", sprintf(_("Check for necessary %s updates"), _("plugin argument")), "

\n"; $this->_configUpdates = array(); $this->_configUpdates[] = new UpgradePluginEntry ($this, array('key' => 'plugin_randompage_numpages', 'fixed_with' => 1012.0, //'header' => _("change RandomPage pages => numpages"), //'notice' =>_("found RandomPage plugin"), 'check_args' => array("plugin RandomPage pages", "/(<\?\s*plugin\s+ RandomPage\s+)pages/", "\\1numpages"))); $this->_configUpdates[] = new UpgradePluginEntry ($this, array('key' => 'plugin_createtoc_position', 'fixed_with' => 1013.0, //'header' => _("change CreateToc align => position"), //'notice' =>_("found CreateToc plugin"), 'check_args' => array("plugin CreateToc align", "/(<\?\s*plugin\s+ CreateToc[^\?]+)align/", "\\1position"))); if (empty($this->_configUpdates)) return; foreach ($this->_configUpdates as $update) { $pages = $this->dbi->fullSearch($this->check_args[0]); while ($page = $allpages->next()) { $current = $page->getCurrentRevision(); $pagetext = $current->getPackedContent(); $update->check($this->check_args[1], $this->check_args[2], $pagetext, $page, $current); } } free($allpages); unset($pagetext); unset($current); unset($page); } /** * preg_replace over local file. * Only line-orientated matches possible. */ function fixLocalFile($match, $replace, $filename) { $o_filename = $filename; if (!file_exists($filename)) $filename = FindFile($filename); if (!file_exists($filename)) return array(false, sprintf(_("File ā€œ%sā€ not found."), $o_filename)); $found = false; if (is_writable($filename)) { $in = fopen($filename, "rb"); $out = fopen($tmp = tempnam(getUploadFilePath(), "cfg"), "wb"); if (isWindows()) $tmp = str_replace("/", "\\", $tmp); // Detect the existing linesep at first line. fgets strips it even if 'rb'. // Before we simply assumed \r\n on Windows local files. $s = fread($in, 1024); rewind($in); $linesep = (substr_count($s, "\r\n") > substr_count($s, "\n")) ? "\r\n" : "\n"; //$linesep = isWindows() ? "\r\n" : "\n"; while ($s = fgets($in)) { // =>php-5.0.1 can fill count //$new = preg_replace($match, $replace, $s, -1, $count); $new = preg_replace($match, $replace, $s); if ($new != $s) { $s = $new . $linesep; $found = true; } fputs($out, $s); } fclose($in); fclose($out); if (!$found) { // todo: skip $reason = sprintf(_("%s not found in %s"), $match, $filename); unlink($out); return array($found, $reason); } else { @unlink("$file.bak"); @rename($file, "$file.bak"); if (!rename($tmp, $file)) return array(false, sprintf(_("couldn't move %s to %s"), $tmp, $filename)); return true; } } else { return array(false, sprintf(_("file %s is not writable"), $filename)); } } function CheckConfigUpdate() { echo "

", sprintf(_("Check for necessary %s updates"), "config.ini"), "

\n"; $entry = new UpgradeConfigEntry ($this, array('key' => 'cache_control_none', 'fixed_with' => 1012.0, 'header' => sprintf(_("Check for %s"), "CACHE_CONTROL = NONE"), 'applicable_args' => 'CACHE_CONTROL', 'notice' => _("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'"), 'check_args' => array("/^\s*CACHE_CONTROL\s*=\s*NONE/", "CACHE_CONTROL = NO_CACHE"))); $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty')); $this->_configUpdates[] = $entry; $entry = new UpgradeConfigEntry ($this, array('key' => 'group_method_none', 'fixed_with' => 1012.0, 'header' => sprintf(_("Check for %s"), "GROUP_METHOD = NONE"), 'applicable_args' => 'GROUP_METHOD', 'notice' => _("GROUP_METHOD is set to NONE, and must be changed to \"NONE\""), 'check_args' => array("/^\s*GROUP_METHOD\s*=\s*NONE/", "GROUP_METHOD = \"NONE\""))); $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty')); $this->_configUpdates[] = $entry; $entry = new UpgradeConfigEntry ($this, array('key' => 'blog_empty_default_prefix', 'fixed_with' => 1013.0, 'header' => sprintf(_("Check for %s"), "BLOG_EMPTY_DEFAULT_PREFIX"), 'applicable_args' => 'BLOG_EMPTY_DEFAULT_PREFIX', 'notice' => _("fix BLOG_EMPTY_DEFAULT_PREFIX into BLOG_DEFAULT_EMPTY_PREFIX"), 'check_args' => array("/BLOG_EMPTY_DEFAULT_PREFIX\s*=/", "BLOG_DEFAULT_EMPTY_PREFIX ="))); $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined')); $this->_configUpdates[] = $entry; // TODO: find extra file updates if (empty($this->_configUpdates)) return; foreach ($this->_configUpdates as $update) { $update->check(); } } } // class Upgrade class UpgradeEntry { /** * Add an upgrade item to be checked. * * @param $parent object The parent Upgrade class to inherit the version properties * @param $key string A short unique key to store success in the WikiDB * @param $fixed_with double @see phpwiki_version() number * @param $header string Optional header to be printed always even if not applicable * @param $applicable WikiCallback Optional callback boolean applicable() * @param $notice string Description of the check * @param $method WikiCallback Optional callback array method(array) * //param All other args are passed to $method */ function UpgradeEntry(&$parent, $params) { $this->parent =& $parent; // get the properties db_version foreach (array('key' => 'required', // the wikidb stores the version when we actually fixed that. 'fixed_with' => 'required', 'header' => '', // always printed 'applicable_cb' => null, // method to check if applicable 'applicable_args' => array(), // might be the config name 'notice' => '', 'check_cb' => null, // method to apply 'check_args' => array()) as $k => $v) { if (!isset($params[$k])) { // default if ($v == 'required') trigger_error("Required arg $k missing", E_USER_ERROR); else $this->{$k} = $v; } else { $this->{$k} = $params[$k]; } } if (!is_array($this->applicable_args)) // single arg convenience shortcut $this->applicable_args = array($this->applicable_args); if (!is_array($this->check_args)) // single arg convenience shortcut $this->check_args = array($this->check_args); if ($this->notice === '' and count($this->applicable_args) > 0) $this->notice = 'Check for ' . join(', ', $this->applicable_args); $this->_db_key = "_upgrade"; $this->upgrade = $this->parent->dbi->get($this->_db_key); } /* needed ? */ function setApplicableCb($object) { $this->applicable_cb =& $object; } function _check_if_already_fixed() { // not yet fixed? if (!isset($this->upgrade['name'])) return false; // override with force? if ($this->parent->request->getArg('force')) return false; // already fixed and with an ok version if ($this->upgrade['name'] >= $this->fixed_with) return $this->upgrade['name']; // already fixed but with an older version. do it again. return false; } function pass() { // store in db no to fix again $this->upgrade['name'] = $this->parent->phpwiki_version; $this->parent->dbi->set($this->_db_key, $this->upgrade); echo "", _("FIXED"), ""; if (isset($this->reason)) echo _(": "), $this->reason; echo "
\n"; flush(); return true; } function fail() { echo '' . _("FAILED") . ""; if (isset($this->reason)) echo _(": "), $this->reason; echo "
\n"; flush(); return false; } function skip() { // not applicable if (isset($this->silent_skip)) return true; echo " " . _("Skipped.") . "
\n"; flush(); return true; } function check($args = null) { if ($this->header) echo $this->header, ' ... '; if ($when = $this->_check_if_already_fixed()) { // be totally silent if no header is defined. if ($this->header) echo _("fixed with"), " ", $when, "
\n"; flush(); return true; } if (is_object($this->applicable_cb)) { if (!$this->applicable_cb->call_array($this->applicable_args)) return $this->skip(); } if ($this->notice) { if ($this->header) echo "
\n"; echo $this->notice, " "; flush(); } if (!is_null($args)) $this->check_args =& $args; if (is_object($this->check_cb)) $do = $this->method_cb->call_array($this->check_args); else $do = $this->default_method($this->check_args); if (is_array($do)) { $this->reason = $do[1]; $do = $do[0]; } return $do ? $this->pass() : $this->fail(); } } // class UpgradeEntry class UpgradeConfigEntry extends UpgradeEntry { function _applicable_defined() { return (boolean)defined($this->applicable_args[0]); } function _applicable_defined_and_empty() { $const = $this->applicable_args[0]; return (boolean)(defined($const) and !constant($const)); } function default_method($args) { $match = $args[0]; $replace = $args[1]; return $this->parent->fixLocalFile($match, $replace, "config/config.ini"); } } // class UpdateConfigEntry /* This is different */ class UpgradePluginEntry extends UpgradeEntry { /** * check all pages for a plugin match */ public $silent_skip = 1; function default_method(&$args) { $match = $args[0]; $replace = $args[1]; $pagetext =& $args[2]; $page =& $args[3]; $current =& $args[4]; if (preg_match($match, $pagetext)) { echo $page->getName(), " ", $this->notice, " ... "; if ($newtext = preg_replace($match, $replace, $pagetext)) { $meta = $current->_data; $meta['summary'] = "upgrade: " . $this->header; $page->save($newtext, $current->getVersion() + 1, $meta); $this->pass(); } else { $this->fail(); } } } } // class UpdatePluginEntry /** * fix custom themes which are not in our distribution * this should be optional */ class UpgradeThemeEntry extends UpgradeEntry { function default_method(&$args) { $match = $args[0]; $replace = $args[1]; $template = $args[2]; } function fixThemeTemplate($match, $new, $template) { // for all custom themes $ourthemes = explode(":", "blog:Crao:default:Hawaiian:MacOSX:MonoBook:Portland:shamino_com:SpaceWiki:wikilens:Wordpress"); $themedir = NormalizeLocalFileName("themes"); $dh = opendir($themedir); while ($r = readdir($dh)) { if (filetype($r) == 'dir' and $r[0] != '.' and !is_array($r, $ourthemes)) $customthemes[] = $r; } $success = true; $errors = ''; foreach ($customthemes as $customtheme) { $template = FindFile("themes/$customtheme/templates/$template"); $do = $this->parent->fixLocalFile($match, $new, template); if (!$do[0]) { $success = false; $errors .= $do[1] . " "; echo $do[1]; } } return array($success, $errors); } } /** * TODO: * * Upgrade: Base class for multipage worksteps * identify, validate, display options, next step */ /* */ // TODO: At which step are we? // validate and do it again or go on with next step. /** entry function from lib/main.php */ function DoUpgrade(&$request) { if (!$request->_user->isAdmin()) { $request->_notAuthorized(WIKIAUTH_ADMIN); $request->finish( HTML::div(array('class' => 'disabled-plugin'), fmt("Upgrade disabled: user != isAdmin"))); return; } // TODO: StartLoadDump should turn on implicit_flush. @ini_set("implicit_flush", true); StartLoadDump($request, _("Upgrading this PhpWiki")); $upgrade = new Upgrade($request); //if (!$request->getArg('noindex')) // CheckOldIndexUpdate($request); // index.php => config.ini to upgrade from < 1.3.10 if (!$request->getArg('nodb')) { $upgrade->CheckDatabaseUpdate($request); // first check cached_html and friends } if (!$request->getArg('nopgsrc')) { $upgrade->CheckPgsrcUpdate($request); $upgrade->CheckActionPageUpdate($request); } // if (!$request->getArg('noplugin')) { // $upgrade->CheckPluginUpdate($request); // } if (!$request->getArg('noconfig')) { $upgrade->CheckConfigUpdate($request); } // This is optional and should be linked. In EndLoadDump or PhpWikiAdministration? //if ($request->getArg('theme')) // $upgrade->CheckThemeUpdate($request); EndLoadDump($request); } // Local Variables: // mode: php // tab-width: 8 // c-basic-offset: 4 // c-hanging-comment-ender-p: nil // indent-tabs-mode: nil // End: