diff --git a/css/alex-2012.css b/css/alex-2012.css
index 3d6fbb8d..264edae7 100644
--- a/css/alex-2012.css
+++ b/css/alex-2012.css
@@ -149,9 +149,6 @@ div.refer {
line-height: 13pt;
}
-div.refer a:first-child:before { content: "" }
-div.refer a:before { content: "• " }
-
div.message {
background-color:#fee;
color:#000;
diff --git a/modules/fix-encoding.pl b/modules/fix-encoding.pl
index be82d00a..28f2d932 100644
--- a/modules/fix-encoding.pl
+++ b/modules/fix-encoding.pl
@@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-$ModulesDescription .= '
fix-encoding.pl
';
+$ModulesDescription .= 'fix-encoding.pl, see Fix Encoding
';
$Action{'fix-encoding'} = \&FixEncoding;
diff --git a/modules/namespaces.pl b/modules/namespaces.pl
index fbb9cfb5..2d75b0c8 100644
--- a/modules/namespaces.pl
+++ b/modules/namespaces.pl
@@ -376,7 +376,7 @@ sub NewNamespaceBrowsePage {
#REDIRECT into different namespaces
my ($id, $raw, $comment, $status) = @_;
OpenPage($id);
- my ($text, $revision) = GetTextRevision(GetParam('revision', ''));
+ my ($text, $revision) = GetTextRevision(GetParam('revision', ''), 1);
my $oldId = GetParam('oldid', '');
if (not $oldId and not $revision and (substr($text, 0, 10) eq '#REDIRECT ')
and (($WikiLinks and $text =~ /^\#REDIRECT\s+(($InterSitePattern:)?$InterLinkPattern)/)
diff --git a/modules/private-pages.pl b/modules/private-pages.pl
new file mode 100644
index 00000000..894b2c32
--- /dev/null
+++ b/modules/private-pages.pl
@@ -0,0 +1,128 @@
+# Copyright (C) 2012 Alex Schroeder
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see .
+
+=head1 Private Pages Extension
+
+This module allows you to hide the content of particular pages in Oddmuse.
+Unlike the I, this is not based on the user's role of
+editor or administrator. Instead, every page can have a different password by
+beginning it with #PASSWORD XYZZY where XYZZY is the password required to read
+it. Multiple passwords can be supplied, separated by spaces.
+
+Note that all the meta information of the private page remains public: The
+I of the page, the fact that is has been edited, the author, the
+revision, the content of past revisions that have not been protected by a
+password all remain visible to other users.
+
+Notes:
+
+=over
+
+=item * This extension might not work in a mod_perl environment because it
+ sets C<$NewText> without ever resetting it.
+
+=item * If you're protecting a comment page, people can still leave comments
+ -- they just can't read the resulting page.
+
+=back
+
+=cut
+
+$ModulesDescription .= 'private-pages.pl, see Private Pages Extension
';
+
+sub PrivatePageLocked {
+ my $text = shift;
+ my ($line) = split(/\n/, $text, 1);
+ my @token = split(/\s+/, $line);
+ my $lock = 0;
+ if (shift(@token) eq '#PASSWORD') {
+ my $pwd = GetParam('pwd', '');
+ $lock = 1;
+ foreach (@token) {
+ if ($pwd eq $_) {
+ $lock = 0;
+ break;
+ }
+ }
+ }
+ return $lock;
+}
+
+*OldPrivatePagesUserCanEdit = *UserCanEdit;
+*UserCanEdit = *NewPrivatePagesUserCanEdit;
+
+sub NewPrivatePagesUserCanEdit {
+ my ($id, $editing, @rest) = @_;
+ my $result = OldPrivatePagesUserCanEdit($id, $editing, @rest);
+ # bypass OpenPage and GetPageContent (these are redefined below)
+ if ($result > 0 and $editing and $IndexHash{$id}) {
+ my %data = ParseData(ReadFileOrDie(GetPageFile($id)));
+ if (PrivatePageLocked($data{text})) {
+ return 0;
+ }
+ }
+ return $result;
+}
+
+sub PrivatePageMessage {
+ return Ts('This page is password protected. If you know the password, you can %s. Once you have done that, return and reload this page.',
+ '[' . ScriptUrl('action=password') . ' '
+ . T('supply the password now') . ']');
+}
+
+*OldPrivatePagesOpenPage = *OpenPage;
+*OpenPage = *NewPrivatePagesOpenPage;
+
+sub NewPrivatePagesOpenPage {
+ OldPrivatePagesOpenPage(@_);
+ if (PrivatePageLocked($Page{text})) {
+ %Page = (); # reset everything
+ $NewText = PrivatePageMessage();
+ }
+ return $OpenPageName;
+}
+
+*OldPrivatePagesGetPageContent = *GetPageContent;
+*GetPageContent = *NewPrivatePagesGetPageContent;
+
+sub NewPrivatePagesGetPageContent {
+ my $text = OldPrivatePagesGetPageContent(@_);
+ if (PrivatePageLocked($text)) {
+ return PrivatePageMessage();
+ }
+ return $text;
+}
+
+push(@MyRules, \&PrivatePageRule);
+
+sub PrivatePageRule {
+ if (pos == 0 && m/\G#PASSWORD.*\n/gc) {
+ return '';
+ }
+ return undef;
+}
+
+*OldPrivatePagesGetSummary = *GetSummary;
+*GetSummary = *NewPrivatePagesGetSummary;
+
+sub NewPrivatePagesGetSummary {
+ my $text = GetParam('text', '');
+ if ($text and $text =~ /^#PASSWORD\b/
+ # no text means aftertext is set (leaving a comment)
+ or $Page{text} =~ /^#PASSWORD\b/) {
+ # if no summary was set, set something in order to avoid the default
+ return '';
+ }
+ return OldPrivatePagesGetSummary();
+}
diff --git a/t/private-pages.t b/t/private-pages.t
new file mode 100644
index 00000000..664b8aac
--- /dev/null
+++ b/t/private-pages.t
@@ -0,0 +1,93 @@
+# Copyright (C) 2012 Alex Schroeder
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see .
+
+require 't/test.pl';
+package OddMuse;
+use Test::More tests => 27;
+
+clear_pages();
+add_module('private-pages.pl');
+
+# create password protected page: can't read it without password!
+test_page(update_page('Privat', "#PASSWORD foo\nSo many secrets remain untold.\n"),
+ 'This page is password protected');
+
+# can't update password protected page
+update_page('Privat', "#PASSWORD foo\nCats have secrets.\n");
+test_page($redirect, 'Status: 403');
+
+# use password foo to update protected page: can't read it without password!
+test_page_negative(update_page('Privat', "#PASSWORD foo\nCats have secrets.\n", undef, undef, 1),
+ 'Cats have secrets');
+test_page($redirect, 'Status: 302');
+
+# read it with password
+my $page = get_page('action=browse id=Privat pwd=foo');
+test_page_negative($page, 'This page is password protected');
+test_page($page, 'Cats have secrets');
+
+# a keep file was created as well
+ok(-f GetKeepFile('Privat', 1), 'Keep file exists');
+
+# can't read old revisions without a password
+test_page_negative(get_page('action=browse id=Privat revision=1'),
+ 'Cats have secrets');
+
+# read old revisions with password
+test_page(get_page('action=browse id=Privat revision=1 pwd=foo'),
+ 'So many secrets remain untold');
+
+# can't see secrets when printing raw pages
+my $page = get_page('action=browse raw=1 id=Privat pwd=foo');
+test_page_negative($page, 'This page is password protected');
+test_page($page, 'Cats have secrets');
+
+# can't see summaries with secrets
+my $page = get_page('action=rc raw=1 all=1');
+test_page($page, 'Privat');
+test_page_negative($page, 'secret');
+
+# can't search for secrets without a password
+my $page = get_page('search=cats');
+test_page($page, '0 pages found');
+test_page_negative($page, "Privat");
+
+# search finds secrets with password
+my $page = get_page('search=cats pwd=foo');
+test_page($page, '1 pages? found',
+ 'Privat', 'Cats have secrets');
+
+# can't edit a private page without a password
+my $page = get_page('action=edit id=Privat');
+test_page($page, 'Editing not allowed');
+test_page_negative($page, 'Cats have secrets');
+
+# can edit a private page with a password
+my $page = get_page('action=edit id=Privat pwd=foo');
+test_page_negative($page, 'This page is password protected');
+test_page($page, 'Cats have secrets');
+
+# can't edit an old revision of a private page without a password
+my $page = get_page('action=edit id=Privat revision=1');
+test_page($page, 'Editing not allowed');
+test_page_negative($page, 'secret');
+
+# can't just post changes to a private page without a password
+my $page = get_page('title=Privat text=YaddaYadda revision=1');
+test_page($page, 'Editing not allowed');
+test_page_negative($page, 'secret');
+
+# can't include them
+test_page_negative(update_page('Publik', ''),
+ 'Cats have secrets');
diff --git a/wiki.pl b/wiki.pl
index aa8c4192..465b1b5f 100755
--- a/wiki.pl
+++ b/wiki.pl
@@ -2495,7 +2495,7 @@ sub PrintHtmlDiff {
if ($type == 1) {
$old = $Page{lastmajor} - 1;
($text, $new) = GetTextRevision($Page{lastmajor}, 1)
- unless $new or $Page{lastmajor} == $Page{revision};
+ unless $new or $Page{lastmajor} == $Page{revision};
} elsif ($new) {
$old = $new - 1;
} else {