3 * Copyright © 2004,2005,2006,2007 $ThePhpWikiProgrammingTeam
4 * Copyright © 2008 Marc-Etienne Vargenau, Alcatel-Lucent
6 * This file is part of PhpWiki.
8 * PhpWiki is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * PhpWiki is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 * SPDX-License-Identifier: GPL-2.0-or-later
27 * Upgrade existing WikiDB and config settings after installing a new PhpWiki software version.
28 * Status: almost no queries for verification.
29 * simple merge conflict resolution, or Overwrite All.
31 * Installation on an existing PhpWiki database needs some
32 * additional worksteps. Each step will require multiple pages.
35 * 1. Check for new or changed database schema and update it
36 * according to some predefined upgrade tables. (medium, complete)
37 * 2. Check for new or changed (localized) pgsrc/ pages and ask
38 * for upgrading these. Check timestamps, upgrade silently or
39 * show diffs if existing. Overwrite or merge (easy, complete)
40 * 3. Check for new or changed or deprecated index.php/config.ini settings
41 * and help in upgrading these. (for newer changes since 1.3.11, not yet)
42 * 3a. Convert old-style index.php into config/config.ini. (easy, not yet)
43 * 4. Check for changed plugin invocation arguments. (medium, done)
44 * 5. Check for changed theme variables. (hard, not yet)
45 * 6. Convert the single-request upgrade to a class-based multi-page
48 * Done: overwrite=1 link on edit conflicts at first occurence "Overwrite all".
50 * @author: Reini Urban
52 require_once 'lib/loadsave.php';
56 public $_configUpdates;
61 function __construct(&$request)
63 $this->request =& $request;
64 $this->dbi =& $request->_dbi;
67 private function doPgsrcUpdate($pagename, $path, $filename)
69 // don't ever update the HomePage
70 if ((defined(HOME_PAGE) and ($pagename == HOME_PAGE))
71 or ($pagename == _("HomePage"))
72 or ($pagename == "HomePage")
74 echo "$path/$pagename: " . _("always skip the HomePage") . " ... " . _("Skipped"), "<br />\n";
78 $page = $this->dbi->getPage($pagename);
79 if ($page->exists()) {
80 // check mtime: update automatically if pgsrc is newer
81 $rev = $page->getCurrentRevision();
82 $page_mtime = $rev->get('mtime');
83 $data = implode("", file($path . "/" . $filename));
84 if (($parts = ParseMimeifiedPages($data))) {
85 usort($parts, 'SortByPageVersion');
87 $pageinfo = $parts[0];
88 $stat = stat($path . "/" . $filename);
90 if (isset($pageinfo['versiondata']['mtime']))
91 $new_mtime = $pageinfo['versiondata']['mtime'];
92 if (!$new_mtime and isset($pageinfo['versiondata']['lastmodified']))
93 $new_mtime = $pageinfo['versiondata']['lastmodified'];
94 if (!$new_mtime and isset($pageinfo['pagedata']['date']))
95 $new_mtime = $pageinfo['pagedata']['date'];
97 $new_mtime = $stat[9];
98 if ($new_mtime > $page_mtime) {
99 echo "$path/$pagename" . _(": ") . _("newer than the existing page")
100 . " ... " . _("Replace") . "<br />\n";
101 LoadAny($this->request, $path . "/" . $filename);
104 echo "$path/$pagename" . _(": ") . _("older than the existing page")
105 . " ... " . _("Skipped"), "<br />\n";
108 echo "$path/$pagename" . _(": ") . _("unknown format") . " ... " . _("Skipped") . "<br />\n";
111 echo sprintf(_("%s does not exist"), $pagename), "<br />\n";
112 LoadAny($this->request, $path . "/" . $filename);
117 public function CheckActionPageUpdate()
119 echo "<h2>", sprintf(_("Check for necessary %s updates"), _("Action Pages")), "</h2>\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->_rename_page_helper("Help/_AuthInfoPlugin", "Help/DebugAuthInfoPlugin");
123 $this->_rename_page_helper("_GroupInfo", "DebugGroupInfo");
124 $this->_rename_page_helper("Help/_GroupInfoPlugin", "Help/DebugGroupInfoPlugin");
125 $this->_rename_page_helper("_BackendInfo", "DebugBackendInfo");
126 $this->_rename_page_helper("Help/_BackendInfoPlugin", "Help/DebugBackendInfoPlugin");
127 $this->_rename_page_helper("Help/_WikiTranslationPlugin", "Help/WikiTranslationPlugin");
128 $this->_rename_page_helper("Help/Advice Mediawiki users", "Help/Advice for Mediawiki users");
129 $this->_rename_page_helper("DebugInfo", "DebugBackendInfo");
130 $this->_rename_page_helper("_GroupInfo", "GroupAuthInfo"); // never officially existed
131 $this->_rename_page_helper("InterWikiKarte", "InterWikiListe"); // German only
132 $this->_rename_page_helper("TemplateTalk", "Template/Talk");
134 $path = findFile('pgsrc');
135 $pgsrc = new FileSet($path);
136 // most actionpages have the same name as the plugin
137 $loc_path = findLocalizedFile('pgsrc');
138 foreach ($pgsrc->getFiles() as $filename) {
139 if (substr($filename, -1, 1) == '~') continue;
140 if (substr($filename, -5, 5) == '.orig') continue;
141 $pagename = urldecode($filename);
142 if (isActionPage($pagename)) {
143 $translation = __($pagename);
144 if ($translation == $pagename)
145 $this->doPgsrcUpdate($pagename, $path, $filename);
146 elseif (findLocalizedFile('pgsrc/' . urlencode($translation), 1))
147 $this->doPgsrcUpdate($translation, $loc_path, urlencode($translation));
149 $this->doPgsrcUpdate($pagename, $path, $filename);
154 // see loadsave.php for saving new pages.
155 public function CheckPgsrcUpdate()
157 // Check some theme specific pgsrc files (blog, wikilens, fusionforge, custom).
158 // We check theme specific pgsrc first in case the page is present in both
159 // theme specific and global pgsrc
161 $path = $WikiTheme->file("pgsrc");
162 // TBD: the call to FileSet prints a warning:
163 // Notice: Unable to open directory 'themes/MonoBook/pgsrc' for reading
164 $themepgsrc = array();
165 $pgsrc = new FileSet($path);
166 if ($pgsrc->getFiles()) {
167 echo "<h2>", sprintf(_("Check for necessary theme %s updates"),
169 foreach ($pgsrc->getFiles() as $filename) {
170 if (substr($filename, -1, 1) == '~') continue;
171 if (substr($filename, -5, 5) == '.orig') continue;
172 $pagename = urldecode($filename);
173 $themepgsrc[] = $pagename;
174 $this->doPgsrcUpdate($pagename, $path, $filename);
178 echo "<h2>", sprintf(_("Check for necessary %s updates"), "pgsrc"), "</h2>\n";
179 $translation = __("HomePage");
180 if ($translation == "HomePage") {
181 $path = findFile(WIKI_PGSRC);
183 $path = findLocalizedFile(WIKI_PGSRC);
185 $pgsrc = new FileSet($path);
186 // fixme: verification, ...
187 foreach ($pgsrc->getFiles() as $filename) {
188 if (substr($filename, -1, 1) == '~') continue;
189 if (substr($filename, -5, 5) == '.orig') continue;
190 $pagename = urldecode($filename);
191 if (!isActionPage($filename)) {
192 // There're a lot of now unneeded pages around.
193 // At first rename the BlaPlugin pages to Help/<pagename> and then to the update.
194 $this->_rename_to_help_page($pagename);
195 if (in_array($pagename, $themepgsrc)) {
196 echo sprintf(_('%s already checked in theme pgsrc'), $pagename).' ... '._('Skipped').'<br />';
198 $this->doPgsrcUpdate($pagename, $path, $filename);
204 private function _rename_page_helper($oldname, $pagename)
206 if ($this->dbi->isWikiPage($oldname) and !$this->dbi->isWikiPage($pagename)) {
207 echo sprintf(_("rename %s to %s"), $oldname, $pagename), " ... ";
208 if ($this->dbi->_backend->rename_page($oldname, $pagename)) {
209 echo _("OK"), " <br />\n";
211 echo ' <span style="color: red; font-weight: bold;">' . _("FAILED") . "</span><br />\n";
216 private function _rename_to_help_page($pagename)
218 $newprefix = _("Help") . "/";
219 if (substr($pagename, 0, strlen($newprefix)) != $newprefix)
221 $oldname = substr($pagename, strlen($newprefix));
222 $this->_rename_page_helper($oldname, $pagename);
226 * preg_replace over local file.
227 * Only line-orientated matches possible.
229 public function fixLocalFile($match, $replace, $filename)
231 $o_filename = $filename;
232 if (!file_exists($filename))
233 $filename = findFile($filename);
234 if (!file_exists($filename))
235 return array(false, sprintf(_("File “%s” not found."), $o_filename));
237 if (is_writable($filename)) {
238 $in = fopen($filename, "rb");
239 $out = fopen($tmp = tempnam(getUploadFilePath(), "cfg"), "wb");
241 $tmp = str_replace("/", "\\", $tmp);
242 // Detect the existing linesep at first line. fgets strips it even if 'rb'.
243 // Before we simply assumed \r\n on Windows local files.
244 $s = fread($in, 1024);
246 $linesep = (substr_count($s, "\r\n") > substr_count($s, "\n")) ? "\r\n" : "\n";
247 //$linesep = isWindows() ? "\r\n" : "\n";
248 while ($s = fgets($in)) {
249 // =>php-5.0.1 can fill count
250 //$new = preg_replace($match, $replace, $s, -1, $count);
251 $new = preg_replace($match, $replace, $s);
253 $s = $new . $linesep;
262 $reason = sprintf(_("%s not found in %s"), $match, $filename);
264 return array(false, $reason);
266 @unlink($filename.".bak");
267 @rename($filename, $filename.".bak");
268 if (!rename($tmp, $filename))
269 return array(false, sprintf(_("couldn't move %s to %s"), $tmp, $filename));
273 return array(false, sprintf(_("file %s is not writable"), $filename));
277 public function CheckConfigUpdate()
279 echo "<h2>", sprintf(_("Check for necessary %s updates"),
280 "config.ini"), "</h2>\n";
281 $entry = new UpgradeConfigEntry($this,
282 array('key' => 'cache_control_none',
283 'header' => sprintf(_("Check for %s"), "CACHE_CONTROL = NONE"),
284 'applicable_args' => array('CACHE_CONTROL'),
285 'notice' => _("CACHE_CONTROL is set to 'NONE', and must be changed to 'NO_CACHE'"),
286 'check_args' => array("/^\s*CACHE_CONTROL\s*=\s*NONE/", "CACHE_CONTROL = NO_CACHE")));
287 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
288 $this->_configUpdates[] = $entry;
290 $entry = new UpgradeConfigEntry($this,
291 array('key' => 'group_method_none',
292 'header' => sprintf(_("Check for %s"), "GROUP_METHOD = NONE"),
293 'applicable_args' => array('GROUP_METHOD'),
294 'notice' => _("GROUP_METHOD is set to NONE, and must be changed to \"NONE\""),
295 'check_args' => array("/^\s*GROUP_METHOD\s*=\s*NONE/", "GROUP_METHOD = \"NONE\"")));
296 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined_and_empty'));
297 $this->_configUpdates[] = $entry;
299 $entry = new UpgradeConfigEntry($this,
300 array('key' => 'blog_empty_default_prefix',
301 'header' => sprintf(_("Check for %s"), "BLOG_EMPTY_DEFAULT_PREFIX"),
302 'applicable_args' => array('BLOG_EMPTY_DEFAULT_PREFIX'),
303 'notice' => _("fix BLOG_EMPTY_DEFAULT_PREFIX into BLOG_DEFAULT_EMPTY_PREFIX"),
304 'check_args' => array("/BLOG_EMPTY_DEFAULT_PREFIX\s*=/", "BLOG_DEFAULT_EMPTY_PREFIX =")));
305 $entry->setApplicableCb(new WikiMethodCb($entry, '_applicable_defined'));
306 $this->_configUpdates[] = $entry;
308 // TODO: find extra file updates
309 if (empty($this->_configUpdates))
311 foreach ($this->_configUpdates as $update) {
320 public $applicable_cb;
325 public /* array */ $applicable_args;
326 public /* object */ $parent;
327 private /* array */ $check_args;
328 private /* string */ $notice;
329 private /* string */ $_db_key;
333 * Add an upgrade item to be checked.
335 * @param object $parent The parent Upgrade class to inherit the version properties
336 * @param array $params
338 function __construct(&$parent, $params)
340 $this->parent =& $parent;
341 foreach (array('key' => 'required',
342 // the wikidb stores the version when we actually fixed that.
343 'header' => '', // always printed
344 'applicable_cb' => null, // method to check if applicable
345 'applicable_args' => array(), // might be the config name
347 'check_cb' => null, // method to apply
348 'check_args' => array())
350 if (!isset($params[$k])) { // default
351 if ($v == 'required') trigger_error("Required arg $k missing", E_USER_ERROR);
352 else $this->{$k} = $v;
354 $this->{$k} = $params[$k];
357 if ($this->notice === '' and count($this->applicable_args) > 0)
358 $this->notice = 'Check for ' . join(', ', $this->applicable_args);
359 $this->_db_key = "_upgrade";
360 $this->upgrade = $this->parent->dbi->get($this->_db_key);
364 public function setApplicableCb($object)
366 $this->applicable_cb =& $object;
369 public function pass()
371 // store in db no to fix again
372 $this->parent->dbi->set($this->_db_key, $this->upgrade);
373 echo "<b>", _("FIXED"), "</b>";
374 if (isset($this->reason))
375 echo _(": "), $this->reason;
381 public function fail()
383 echo '<span style="color: red; font-weight: bold; ">' . _("FAILED") . "</span>";
384 if (isset($this->reason))
385 echo _(": "), $this->reason;
391 private function skip()
393 if (isset($this->silent_skip))
395 echo " ... " . _("Skipped") . "<br />\n";
400 public function check($args = null)
402 if ($this->header) echo $this->header, ' ... ';
403 if (is_object($this->applicable_cb)) {
404 if (!$this->applicable_cb->call_array($this->applicable_args))
405 return $this->skip();
410 echo $this->notice, " ";
413 if (!is_null($args)) $this->check_args =& $args;
414 if (is_object($this->check_cb))
415 $do = $this->method_cb->call_array($this->check_args);
417 $do = $this->default_method($this->check_args);
419 $this->reason = $do[1];
422 return $do ? $this->pass() : $this->fail();
424 } // class UpgradeEntry
426 class UpgradeConfigEntry extends UpgradeEntry
428 public function _applicable_defined()
430 return defined($this->applicable_args[0]);
433 public function _applicable_defined_and_empty()
435 $const = $this->applicable_args[0];
436 return defined($const) and !constant($const);
439 public function default_method($args)
443 return $this->parent->fixLocalFile($match, $replace, "config/config.ini");
445 } // class UpdateConfigEntry
447 /** entry function from lib/main.php
449 function DoUpgrade(&$request)
452 if (!$request->_user->isAdmin()) {
453 $request->_notAuthorized(WIKIAUTH_ADMIN);
455 HTML::div(array('class' => 'disabled-plugin'),
456 fmt("Upgrade disabled: user != isAdmin")));
459 // TODO: StartLoadDump should turn on implicit_flush.
460 @ini_set("implicit_flush", true);
461 StartLoadDump($request, _("Upgrading this PhpWiki"));
462 $upgrade = new Upgrade($request);
463 if (!$request->getArg('nopgsrc')) {
464 $upgrade->CheckPgsrcUpdate();
465 $upgrade->CheckActionPageUpdate();
467 if (!$request->getArg('noconfig')) {
468 $upgrade->CheckConfigUpdate();
470 EndLoadDump($request);