Compare commits

..

56 Commits

Author SHA1 Message Date
Alex Schroeder
4b0d411564 Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse
Conflicts:
	wiki.pl
2014-11-01 01:03:16 +01:00
Alex Schroeder
6790de2d6a Yet another attempt at fixing encoding issues.
To facilitate future debugging, STDERR now also gets the UTF-8 layer.

Apparently CGI does not decode UTF-8 encoded URL parameters. Handle this
case in GetParam.

PageHtml can be called when STDOUT already has the UTF-8 layer. It needs
to be able to handle both cases. That's why we call binmode without any
layers and then we call binmode with the UTF-8 layer again. Now it will
work for RSS files as well.

Unrelated fix: In order to force a decent Etag header even if no index
file exists (and thus $LastUpdate is undef), we use $Now as an
alternative.
2014-11-01 00:52:55 +01:00
Alex Schroeder
2784628544 PageHtml no longer uses utf8 layer
binmode adding utf8 layer to STDOUT resulted in double encoded pages
included via PageHtml. On my homepage I was appending the comments to
every page using the following code:

    my $target = $CommentsPrefix . $id;
    my $page = '';
    $page = PageHtml($target) if $IndexHash{$target};
    print $q->div({-class=>'comment'},
                  $q->h2(T('Comments')),
                  $page);
2014-10-31 23:50:28 +01:00
Alex Schroeder
dd8c687b2b Caching: Fixed tests.
There is no problem generating an Etag header even if a Last Modified
header is provided.
2014-10-31 23:49:41 +01:00
Alex Jakimenko
9f4ceb2d72 module-updater.pl: No need to return when calling OrError subs 2014-10-31 18:48:48 +02:00
Alex Jakimenko
0f8a4fa1df module-updater.pl: Fixed cache problems 2014-10-31 18:48:06 +02:00
Alex Jakimenko
3b16b58880 module-bisect.pl: Solved cache problems, added 'Back' button from 'Stop' page, all strings are now translatable, some refactoring. 2014-10-31 18:41:08 +02:00
Alex Jakimenko
192a902932 Typos and language 2014-10-31 15:41:00 +02:00
Alex Schroeder
aedf77cff8 wiki.pl: Fix caching.
Previously, if calling GetHeader with 'nocache', this would get passed
on to GetHttpHeader as $ts. The code would then produce an etag header
with a value of 'nocache'. This is now fixed. A long comment now
explains how it is supposed to work to reduce confusion in the future.
2014-10-31 09:27:27 +01:00
Alex Schroeder
728547f309 Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2014-10-31 09:10:08 +01:00
Alex Schroeder
33f5484441 oddmuse-curl.el: oddmuse-search, oddmuse-match
Both may use a prefix argument to search a different wiki.
2014-10-31 09:09:52 +01:00
Alex Schroeder
a225486709 vc-oddmuse.el: vc-oddmuse-diff, vc-oddmuse-checkin
These simply did not work.
2014-10-31 09:08:16 +01:00
Alex Jakimenko
3f7f9ec1eb module-bisect.pl: Indicate progress by using > signs 2014-10-31 04:45:45 +02:00
Alex Jakimenko
174aac5570 module-bisect.pl: Do binary search on your modules by disabling/enabling them 2014-10-31 04:09:33 +02:00
Alex Jakimenko
954232f7c8 module-updater.pl: Do not update modules immediately, just show diffs and ask the user whether he wants to apply those changes. 2014-10-29 23:40:09 +02:00
Alex Jakimenko
067658fd10 comment-div-wrapper.pl: Now using $q-> functions instead of printing html manually 2014-10-26 00:54:45 +03:00
Alex Jakimenko
b2b2b0f6cc comment-div-wrapper.pl: Use 'class' instead of 'id' (otherwise it creates multiple elements with the same id in journals) 2014-10-26 00:29:35 +03:00
Alex Jakimenko
408d169729 comment-div-wrapper.pl: indentation 2014-10-26 00:28:32 +03:00
Alex Schroeder
0eddbd5806 oddmuse-2013.css: white-space and word-wrap 2014-10-24 15:50:42 +02:00
Alex Schroeder
210a28afd4 oddmuse-2013.css: small changes 2014-10-24 15:50:31 +02:00
Alex Schroeder
94a16bd463 Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2014-10-24 09:47:29 +02:00
Alex Schroeder
4005e246f7 oddmuse-curl.el: Emacs 24.4 compatibility
shell-command-on-region apparently no longer returns an error number.

Also, use quotes for oddmuse-get-command.
2014-10-24 09:46:51 +02:00
Alex Jakimenko
6b3cd0437f module-updater.pl: GetRaw() instead of wget, cgi functions instead of printing tags 2014-10-22 03:00:32 +03:00
Alex Jakimenko
c961748b49 image.pl: Commentable images (create divs with text above images) 2014-10-21 17:26:31 +03:00
Alex Jakimenko
7327eb8e0c div-foo.pl: Support for spans, paragraph problem solved 2014-10-21 16:27:14 +03:00
Alex Schroeder
1b0d595945 Oddmuse mode for Emacs: Fix diff support
C-x v = calls diff and it didn't work at all; now it works again but
something about the revisions still isn't right.
2014-10-16 23:29:37 +02:00
Alex Schroeder
b7e2a04bb4 search.t: test fix for encoding error
On my website, I noticed that searching for matching pages with a
string containing a non-ascii character worked, where as doing a
normal search resulted in an encoding error. The difference was this:

  # fix match parameter
  my $match = GetParam('match', '');
  SetParam('match', FreeToNormal($match)) if $match;

This may be necessary to remove underscores, but it should not be
necessary to fix encoding errors.
2014-10-10 18:15:54 +02:00
Alex Schroeder
848eb65ad0 use CGI qw/-utf8/
This option should allow automatic encoding and decoding of
parameters. This saves a few lines and solves an encoding error:
previously, searching for a text containing non-ASCII characters would
result in double-encoded text fields.
2014-10-10 17:15:28 +02:00
Alex Schroeder
7ae98f4ed9 $q->endform to $q->end_form
When updating to CGI 4.06, it turns out that $q->endform
is no longer defined.
2014-10-10 16:43:01 +02:00
Alex Jakimenko
dc792691d4 Fail less dramatically if h1 appears. 2014-10-02 16:29:45 +03:00
Alex Jakimenko
0a57a8e89b toc.pl: Treat h2 as the first header when Creole Extension is active. 2014-10-02 16:23:00 +03:00
Alex Jakimenko
f75d415322 Div Foo Extension 2014-09-29 03:46:02 +03:00
Gauthier Vandemoortele
1654562236 clustermap.pl: fixed regression (subrotine name was changed)
This module used a function called ‘DoRc’ that disappeared from the main oddmuse script since the 1.837 version. It was apparently replaced by ‘PrintRcHtml’.
2014-09-26 23:12:23 +03:00
Alex Schroeder
ba0535f39d Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2014-09-24 16:51:30 +02:00
Alex Schroeder
e5b069f70b upgrade.t: ensure modules directory exists
When calling clean_pages on OSX, the mac.pl module is installed via
add_module, which in turn calls mkdir. On other systems, however,
installing the old namespace module will not work since the module
directory will not exist. This change ensures that mkdir is called
before symlinking.
2014-09-24 16:49:29 +02:00
Alex Schroeder
d34b9f669b upgrade.t: ensure modules directory exists
When creating a symlink on HFS+, the directory doesn't need to exist,
apparently. On ext4, it needs to exist. Make the test more robust by
calling mkdir.
2014-09-24 16:42:46 +02:00
Alex Schroeder
ae2061fcaf referrer-tracking.t: skip if questionasker is installed 2014-09-24 16:27:21 +02:00
Alex Schroeder
65475cf2e8 git.t: make test more robust
Different git versions use different strings to report issues and the
tests don't use --porcelain. Made them a little more robust.
2014-09-24 16:17:25 +02:00
Alex Schroeder
ce2e63be6b Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2014-09-23 22:19:51 +02:00
Alex Schroeder
007ce8db86 cookie.t: no longer hardcode the wiki name
Changed the tests to no longer assume the default value for
$CookieName. This is important when running the tests on a system
where Apache starts a number of wikis for various virtual hosts and
one of them will end up as the host to use for http://localhost/. That
wiki might very well have a cookie name set.
2014-09-23 22:17:47 +02:00
IngoBelka
5997c3ea02 creole.pl: title-tag for images
This enables tooltips for images in FireFox and possibly in other browsers too
2014-09-13 14:55:38 +03:00
Alex Schroeder
6895428844 UserCanEditOrDie text preferred
It turns out that one of the tests in lock.t relied on the exact wording
of the error message. I decided to use the same wording for all
instances: "Editing not allowed: %s is read-only" and no longer using
"Editing not allowed for %s".
2014-09-12 15:30:20 +02:00
Alex Schroeder
2bc2d1f927 New convenience function UserCanEditOrDie
Closes bug #41625 "ban-quick-editors.pl can hide the error message".
DoPost now uses the same code as DoEdit and thus it will display the
error message, which solves the issue for ban-quick-editors.pl. Added a
test to demonstrate it.
2014-09-12 15:20:06 +02:00
Alex Schroeder
873ce10ced permanent-anchors.pl: report page deletion status
Make sure the status is returned if page deletion fails.
2014-09-12 11:50:51 +02:00
Alex Schroeder
3855c83a7e maintain.t: Verify that maintenance deletes pages 2014-09-12 11:45:24 +02:00
Alex Schroeder
281736a082 Merge: use [[foo bar]] and [[foo_bar]]
I find that removing " space" and "_underscore" made it easier for me to
visually compare the XPath expressions. I also wanted to keep the
[[foo bar|text]] test (and keep the XPath expression as similar as possible).
2014-09-11 22:08:50 +02:00
Alex Schroeder
63d8e24c2f creole.t: test for new free link rule
Make sure that [[foo bar]] turns to [[foo_bar?]] if foo_bar does not
exist, and [[foo bar|text]] turns to [[foo_bar?|text]].
2014-09-11 21:57:27 +02:00
Alex Jakimenko
c66f1a6f8e We have chosen a wrong way to compare $text with $id. It should be better now.
This also fixes recent test added to creole.t
2014-09-11 16:47:18 +03:00
Alex Jakimenko
6418cab98c creole.t: creole extension has its own link rules, so we have to test spaces in [[links]] 2014-09-11 16:43:52 +03:00
Alex Jakimenko
040d51bc93 default-links.t: test both underscores and spaces in [[links]] 2014-09-11 16:41:22 +03:00
Alex Schroeder
41c3245a51 default-links.t: test for new free link rule
Make sure that [[foo_bar]] turns to [[foo_bar?]] if foo_bar does not
exist.
2014-09-10 23:13:45 +02:00
Alex Schroeder
ffc2a0b12f Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2014-09-10 22:59:36 +02:00
Alex Schroeder
90c632f4ab Avoid Save button for comments after rollback
If you clicked on the Save button after rolling pack a comment page, the
content of the comment page would be deleted.
2014-09-10 22:56:54 +02:00
Alex Jakimenko
9e8def306a Fixed free links to nonexisting pages (now [[foo bar]] results in [[foo_bar?]] instead of [[foo_bar?|foo bar]]) 2014-09-03 15:25:13 +03:00
Alex Jakimenko
f4e551111a Executable flag is only useful when shebang is specified 2014-09-03 14:23:20 +03:00
Alex Jakimenko
1ead545561 Fix gravatar tests (https links from f7b99d4 commit) 2014-09-03 14:06:38 +03:00
78 changed files with 504 additions and 209 deletions

View File

@@ -112,7 +112,7 @@ USERNAME, your optional username to provide. It defaults to
;;; Variables
(defvar oddmuse-get-command
"curl --silent %w --form action=browse --form raw=2 --form id=%t"
"curl --silent %w --form action=browse --form raw=2 --form id='%t'"
"Command to use for publishing pages.
It must print the page to stdout.
@@ -254,6 +254,9 @@ Example:
((\"Alex\" ((\"Contact\" . \"58\"))))")
(defvar oddmuse-revision nil
"A variable to bind dynamically when calling `oddmuse-format-command'.")
(defun oddmuse-revision-put (wiki page rev)
"Store REV for WIKI and PAGE in `oddmuse-revisions'."
(let ((w (assoc wiki oddmuse-revisions)))
@@ -285,10 +288,10 @@ Example:
(file-name-directory file))))
(defmacro with-oddmuse-file (file &rest body)
"Bind `oddmuse-wiki' and `oddmuse-page-name' based on FILE
and execute BODY."
`(let ((oddmuse-page-name (oddmuse-page-name ,file))
(oddmuse-wiki (oddmuse-wiki ,file)))
"Bind `wiki' and `pagename' based on FILE and execute BODY."
(declare (debug (symbolp &rest form)))
`(let ((pagename (oddmuse-page-name ,file))
(wiki (oddmuse-wiki ,file)))
,@body))
(put 'with-oddmuse-file 'lisp-indent-function 1)
@@ -407,6 +410,7 @@ It's either a [[free link]] or a WikiWord based on
%p `oddmuse-password'
%q `question' as provided by `oddmuse-wikis'
%o `oddmuse-ts'
%v `oddmuse-revision'
%r `regexp' as provided by the user"
(dolist (pair '(("%w" . url)
("%t" . pagename)
@@ -416,6 +420,7 @@ It's either a [[free link]] or a WikiWord based on
("%p" . oddmuse-password)
("%q" . question)
("%o" . oddmuse-ts)
("%v" . oddmuse-revision)
("%r" . regexp)))
(let* ((key (car pair))
(sym (cdr pair))
@@ -477,15 +482,13 @@ well."
(message "%s using %s..." mesg command)
(when (numberp expected-code)
(setq expected-code (number-to-string expected-code)))
;; If SEND-BUFFER, the resulting HTTP CODE is found in BUF, so check
;; that, too.
(let* ((errno (if send-buffer
(shell-command-on-region (point-min) (point-max) command buf)
(shell-command command buf)))
(status (with-current-buffer buf (buffer-string))))
(cond ((not (zerop errno))
(error "Error %s: non-zero return value" mesg))
((and send-buffer expected-code (not (string= expected-code status)))
(if send-buffer
(shell-command-on-region (point-min) (point-max) command buf)
(shell-command command buf))
(let ((status (with-current-buffer buf (buffer-string))))
(cond ((and send-buffer
expected-code
(not (string= expected-code status)))
(error "Error %s: HTTP Status Code %s" mesg status))
((string-match "<title>Error</title>" status)
(if (string-match "<h1>\\(.*\\)</h1>" status)
@@ -901,9 +904,10 @@ node as returned by `libxml-parse-html-region' or
(defun oddmuse-search (regexp)
"Search the wiki for REGEXP.
REGEXP must be a regular expression understood by the
wiki (ie. it must use Perl syntax)."
wiki (ie. it must use Perl syntax).
Use a prefix argument to search a different wiki."
(interactive "sSearch term: ")
(let* ((wiki (or oddmuse-wiki
(let* ((wiki (or (and (not current-prefix-arg) oddmuse-wiki)
(completing-read "Wiki: " oddmuse-wikis nil t)))
(name (concat "*" wiki ": search for '" regexp "'*")))
(if (and (get-buffer name)
@@ -921,9 +925,10 @@ wiki (ie. it must use Perl syntax)."
(defun oddmuse-match (regexp)
"Search the wiki for page names matching REGEXP.
REGEXP must be a regular expression understood by the
wiki (ie. it must use Perl syntax)."
wiki (ie. it must use Perl syntax).
Use a prefix argument to search a different wiki."
(interactive "sPages matching: ")
(let* ((wiki (or oddmuse-wiki
(let* ((wiki (or (and (not current-prefix-arg) oddmuse-wiki)
(completing-read "Wiki: " oddmuse-wikis nil t)))
(name (concat "*" wiki ": matches for '" regexp "'*")))
(if (and (get-buffer name)

View File

@@ -94,14 +94,19 @@ See `oddmuse-format-command' for the formatting options.")
(error "This is not supported."))
(defvar vc-oddmuse-get-revision-command
"curl --silent %w\"?action=browse;id=%t;revision=%o;raw=1\""
(concat "curl --silent"
" --form action=browse"
" --form id=%t"
" --form revision=%v"
" --form raw=1"
" '%w'")
"Command to use to get older revisions of a page.
It must print the page to stdout.
%? '?' character
%w URL of the wiki as provided by `oddmuse-wikis'
%t Page title as provided by `oddmuse-page-name'
%o Revision to retrieve as provided by `oddmuse-revision'")
%v Revision to retrieve as provided by `oddmuse-revision'")
(defun oddmuse-revision-filename (rev)
"Return filename for revision REV.
@@ -117,14 +122,17 @@ This uses `oddmuse-directory', `oddmuse-wiki' and
(setq buffer (or buffer (get-buffer-create "*vc-diff*")))
(dolist (file files)
(with-oddmuse-file file
(setq rev1 (or rev1 (oddmuse-get-latest-revision)))
(setq rev1 (or rev1 (oddmuse-get-latest-revision wiki pagename)))
(dolist (rev (list rev1 rev2))
(when (and rev (not (file-readable-p (oddmuse-revision-filename rev))))
(let* ((oddmuse-revision rev)
(command (oddmuse-format-command vc-oddmuse-get-revision-command))
(command (oddmuse-format-command
vc-oddmuse-get-revision-command))
(filename (oddmuse-revision-filename rev)))
(with-temp-buffer
(oddmuse-run (concat "Downloading revision " rev) command)
(oddmuse-run
(concat "Downloading revision " rev)
command wiki)
(write-file filename)))))
(diff-no-select
(if rev1 (oddmuse-revision-filename rev1) file)
@@ -156,6 +164,6 @@ used as a check-in comment."
(buf (get-buffer-create " *oddmuse-response*")))
(with-temp-buffer
(insert-file-contents file)
(oddmuse-run "Posting" command nil nil buf t 302))))))
(oddmuse-run "Posting" command wiki pagename buf t 302))))))
(provide 'vc-oddmuse)

View File

@@ -197,9 +197,17 @@ hr {
div.footer hr {
height:4px;
margin: 2em 0 1ex 0;
clear:both;
}
div.comment {
div.content > div.comment {
border-top: none;
padding-top: none;
border-left: 1ex solid #bbb;
padding-left: 1ex;
}
div.wrapper > div.comment {
border-top: 2px solid #000;
padding-top: 2em;
}
@@ -210,12 +218,13 @@ pre {
margin-right: 2em;
white-space: pre;
overflow:hidden;
white-space: pre-wrap; /* CSS 3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
tt, pre, code {
font-size: 80%;
};
div.footer hr {
clear:both;
}

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2004, 2006, 2008 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2004, 2006, 2008, 2014 Alex Schroeder <alex@gnu.org>
#
# 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
@@ -140,11 +140,11 @@ sub DoAtomSave {
my $title = $entry->title();
my $author = $entry->author();
SetParam('username', $author->name) if $author; # Used in Save()
my $id = FreeToNormal($title) if ValidIdOrDie($title);
my $id = FreeToNormal($title);
UserCanEditOrDie($id);
$oldid = $id unless $oldid;
ValidIdOrDie($oldid);
my $summary = $entry->summary();
ReportError(Ts('Editing not allowed for %s.', $id), '403 FORBIDDEN') unless UserCanEdit($id, 1);
# Lock before getting old page to prevent races
RequestLockOrError(); # fatal
OpenPage($oldid);

View File

@@ -40,8 +40,8 @@ $PrintTOCAnchor = 0;
%ClusterMap = ();
*OldDoRc = *DoRc;
*DoRc = *ClusterMapDoRc;
*OldPrintRcHtml = *PrintRcHtml;
*PrintRcHtml = *ClusterMapPrintRcHtml;
push(@MyAdminCode, \&ClusterMapAdminRule);
@@ -178,7 +178,7 @@ sub CreateClusterMap {
}
}
sub ClusterMapDoRc {
sub ClusterMapPrintRcHtml {
my ( @options ) = @_;
my $page = "";
my $cluster = GetParam(rcclusteronly);
@@ -195,7 +195,7 @@ sub ClusterMapDoRc {
print "</ul>";
}
OldDoRc(@options);
OldPrintRcHtml(@options);
}
sub PrintUnclusteredMap {

View File

@@ -25,14 +25,14 @@ sub CommentDivWrapper {
if (substr($OpenPageName, 0, length($CommentsPrefix)) eq $CommentsPrefix) {
if (pos == 0 and not $CommentDiv) {
$CommentDiv = 1;
return '<div class="userComment">';
return $q->start_div({-class=>'userComment'});
}
}
if ($OpenPageName =~ /$CommentsPattern/o) {
if ($bol and m/\G(\s*\n)*----+[ \t]*\n?/cg) {
my $html = CloseHtmlEnvironments()
. ($CommentDiv++ > 0 ? '</div>' : '<h2 id="commentsHeading">' . T('Comments:') . '</h2>') . '<div class="userComment">'
. AddHtmlEnvironment('p');
. ($CommentDiv++ > 0 ? $q->end_div() : $q->h2({-class=>'commentsHeading'}, T('Comments:'))) . $q->start_div({-class=>'userComment'})
. AddHtmlEnvironment('p');
return $html;
}
}
@@ -46,8 +46,8 @@ sub CommentDivWrapper {
sub NewCommentDivApplyRules {
my ($blocks, $flags) = OldCommentDivApplyRules(@_);
if ($CommentDiv) {
print '</div>';
$blocks .= $FS . '</div>';
print $q->end_div();
$blocks .= $FS . $q->end_div();
$flags .= $FS . 0;
$CommentDiv = 0;
}

View File

@@ -242,6 +242,7 @@ sub CreoleRule {
-class=> 'image outside'},
$q->img({-src=> UnquoteHtml($1),
-alt=> UnquoteHtml($3),
-title=> UnquoteHtml($3),
-class=> 'url outside'})));
}
# image link: [[link|{{pic}}]] and [[link|{{pic|text}}]]
@@ -252,6 +253,7 @@ sub CreoleRule {
ScriptLink(UrlEncode(FreeToNormal($2)),
$q->img({-src=> GetDownloadLink(FreeToNormal($3), 2),
-alt=> UnquoteHtml($text),
-title=> UnquoteHtml($text),
-class=> 'upload'}), 'image')), $text);
}
# image link: [[link|{{url}}]] and [[link|{{url|text}}]]
@@ -262,6 +264,7 @@ sub CreoleRule {
ScriptLink(UrlEncode(FreeToNormal($2)),
$q->img({-src=> UnquoteHtml($3),
-alt=> UnquoteHtml($text),
-title=> UnquoteHtml($text),
-class=> 'url outside'}), 'image')), $text);
}
# image link: [[url|{{pic}}]] and [[url|{{pic|text}}]]
@@ -272,6 +275,7 @@ sub CreoleRule {
$q->a({-href=> UnquoteHtml($2), -class=> 'image outside'},
$q->img({-src=> GetDownloadLink(FreeToNormal($3), 2),
-alt=> UnquoteHtml($text),
-title=> UnquoteHtml($text),
-class=> 'upload'}))), $text);
}
# image link: [[url|{{url}}]] and [[url|{{url|text}}]]
@@ -281,6 +285,7 @@ sub CreoleRule {
$q->a({-href=> UnquoteHtml($1), -class=> 'image outside'},
$q->img({-src=> UnquoteHtml($2),
-alt=> UnquoteHtml($4),
-title=> UnquoteHtml($4),
-class=> 'url outside'})));
}
# link: [[url]] and [[url|text]]

35
modules/div-foo.pl Normal file
View File

@@ -0,0 +1,35 @@
# Copyright (C) 2014 Alex-Daniel Jakimenko <alex.jakimenko@gmail.com>
#
# 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 <http://www.gnu.org/licenses/>.
package OddMuse;
AddModuleDescription('div-foo.pl', 'Div Foo Extension');
push(@MyRules, \&DivFooRule);
sub DivFooRule {
if (m/\G\&lt;([\w ]+)\&gt;\s*\n/cg) {
return CloseHtmlEnvironment('p') . AddHtmlEnvironment('div', qq{class="$1"});
}
if (m/\G\&lt;([\w ]+)\&gt;/cg) {
return AddHtmlEnvironment('span', qq{class="$1"});
}
if (m/\G\&lt;\/\/\&gt;/cg) {
return CloseHtmlEnvironment('div') . (InElement('div') ? '' : AddHtmlEnvironment('p'));
}
if (m/\G\&lt;\/\&gt;/cg) {
return CloseHtmlEnvironment('span');
}
return undef;
}

View File

@@ -28,7 +28,7 @@ push(@MyRules, \&ImageSupportRule);
sub ImageSupportRule {
my $result = undef;
if (m!\G\[\[image((/[a-z]+)*)( external)?:\s*([^]|]+?)\s*(\|[^]|]+?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*\]\]!gc) {
if (m!\G\[\[image((/[a-z]+)*)( external)?:\s*([^]|]+?)\s*(\|[^]|]+?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*\]\](\{([^}]+)\})?!gc) {
my $oldpos = pos;
my $class = 'image' . $1;
my $external = $3;
@@ -42,6 +42,7 @@ sub ImageSupportRule {
my $link = $6 ? substr($6, 1) : '';
my $caption = $7 ? substr($7, 1) : '';
my $reference = $8 ? substr($8, 1) : '';
my $comments = $10;
my $id = FreeToNormal($name);
$class =~ s!/! !g;
my $linkclass = $class;
@@ -66,6 +67,16 @@ sub ImageSupportRule {
if ($found) {
$result = $q->img({-src=>$src, -alt=>$alt, -title=>$alt, -class=>'upload'});
$result = $q->a({-href=>$link, -class=>$linkclass}, $result);
if ($comments) {
for (split '\n', $comments) {
my $valRegex = qr/(([0-9.]+[a-z]*%?)\s+)/;
if ($_ =~ /^\s*(([a-zA-Z ]+)\/)?$valRegex$valRegex$valRegex$valRegex(.*)$/) { # can't use {4} here? :(
my $commentClass = $2 ? "imagecomment $2" : 'imagecomment';
$result .= $q->div({-class=>$commentClass, -style=>"position: absolute; top: $6; left: $4; width: $8; height: $10"}, QuoteHtml($11));
}
}
$result = CloseHtmlEnvironments() . $q->div({-class=>"imageholder", -style=>"position: relative"}, $result);
}
} else {
$result = GetDownloadLink($src, 1, undef, $alt);
}

View File

@@ -366,7 +366,7 @@ sub JoinerDoRegister {
$table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
print $q->table($table);
print JoinerGetQuestion();
print $q->endform;
print $q->end_form;
print $q->end_div();
PrintFooter();
@@ -530,7 +530,7 @@ sub JoinerDoLogin {
$table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
print $q->table($table);
print JoinerGetQuestion();
print $q->endform;
print $q->end_form;
print $q->start_p();
print ScriptLink('action=joiner_forgot_password', T('Forgot your password?'));
@@ -705,7 +705,7 @@ sub JoinerDoChangePassword {
$q->td($q->password_field(-name=>'joiner_repeat_new_password', -id=>'joiner_repeat_new_password')));
$table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
print $q->table($table);
print $q->endform;
print $q->end_form;
print $q->end_div();
PrintFooter();
@@ -790,7 +790,7 @@ sub JoinerDoForgotPassword {
$table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
print $q->table($table);
print JoinerGetQuestion();
print $q->endform;
print $q->end_form;
print $q->end_div();
PrintFooter();
@@ -891,7 +891,7 @@ sub JoinerDoChangeEmail {
$q->td($q->password_field(-name=>'joiner_password', -id=>'joiner_password')));
$table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Submit', -value=>T('Submit'))));
print $q->table($table);
print $q->endform;
print $q->end_form;
print $q->end_div();
PrintFooter();
@@ -1082,7 +1082,7 @@ sub JoinerDoBan {
$q->td($q->textfield(-name=>'joiner_username', -id=>'joiner_username')));
$table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Ban', -value=>T('Ban'))));
print $q->table($table);
print $q->endform;
print $q->end_form;
print $q->start_p();
print T('Enter username of the account to unban:');
@@ -1096,7 +1096,7 @@ sub JoinerDoBan {
$q->td($q->textfield(-name=>'joiner_username', -id=>'joiner_username')));
$table .= $q->Tr($q->td(), $q->td($q->submit(-name=>'Unban', -value=>T('Unban'))));
print $q->table($table);
print $q->endform;
print $q->end_form;
print $q->end_div();
PrintFooter();

View File

@@ -247,7 +247,7 @@ sub DoMailSubscriptions {
print $q->p(ScriptLink('action=subscriptions;mail=', T('Change email address'),
'change subscriptions'));
}
print $q->endform(), $q->end_div();
print $q->end_form(), $q->end_div();
PrintFooter();
}

137
modules/module-bisect.pl Normal file
View File

@@ -0,0 +1,137 @@
# Copyright (C) 2014 Alex-Daniel Jakimenko <alex.jakimenko@gmail.com>
#
# 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 <http://www.gnu.org/licenses/>.
package OddMuse;
use File::Basename;
use File::Copy;
AddModuleDescription('module-bisect.pl', 'Module Bisect Extension');
push(@MyAdminCode, \&ModuleBisectMenu);
$Action{bisect} = \&BisectAction;
sub ModuleBisectMenu {
return unless UserIsAdmin();
my ($id, $menuref, $restref) = @_;
push(@$menuref, ScriptLink('action=bisect', T('Bisect modules'), 'modulebisect'));
}
sub BisectAction {
UserIsAdminOrError();
RequestLockOrError();
print GetHeader('', T('Module Bisect'), '', 'nocache');
if (GetParam('stop')) {
BisectEnableAll(1);
print $q->br(), $q->strong(T('All modules enabled now!'));
print GetFormStart(undef, 'get', 'bisect');
print GetHiddenValue('action', 'bisect');
print $q->submit(-name=>'noop', -value=>T('Go back'));
print $q->end_form();
} elsif (GetParam('good') or GetParam('bad')) {
BisectProcess(GetParam('good'));
} else {
BisectInitialScreen();
}
PrintFooter();
ReleaseLock();
}
sub BisectInitialScreen {
print GetFormStart(undef, 'get', 'bisect');
print GetHiddenValue('action', 'bisect');
my @disabledFiles = bsd_glob("$ModuleDir/*.p[ml].disabled");
if (@disabledFiles == 0) {
print T('Test / Always enabled / Always disabled'), $q->br();
my @files = bsd_glob("$ModuleDir/*.p[ml]");
for (my $i = 0; $i < @files; $i++) {
my $moduleName = fileparse($files[$i]);
my @disabled = ($moduleName eq 'module-bisect.pl' ? (-disabled=>'disabled') : ());
print $q->input({-type=>'radio', -name=>"m$i", -value=>'t', ($moduleName ne 'module-bisect.pl' ? (-checked=>'checked') : ()), @disabled});
print $q->input({-type=>'radio', -name=>"m$i", -value=>'on', ($moduleName eq 'module-bisect.pl' ? (-checked=>'checked') : ())});
print $q->input({-type=>'radio', -name=>"m$i", -value=>'off', @disabled});
print $moduleName, $q->br();
}
print $q->submit(-name=>'bad', -value=>T('Start'));
} else {
print T('Biscecting proccess is already active.'), $q->br();
print $q->submit(-name=>'stop', -value=>T('Stop'));
}
print $q->end_form();
}
sub BisectProcess {
my ($isGood) = @_;
my $parameterHandover = '';
BisectEnableAll();
my @files = bsd_glob("$ModuleDir/*.p[ml]");
for (my $i = @files - 1; $i >= 0; $i--) { # handle user choices
if (GetParam("m$i") eq 'on') {
$parameterHandover .= GetHiddenValue("m$i", GetParam("m$i"));
splice @files, $i, 1;
} elsif (GetParam("m$i") eq 'off') {
$parameterHandover .= GetHiddenValue("m$i", GetParam("m$i"));
move($files[$i], $files[$i] . '.disabled');
splice @files, $i, 1;
}
}
my $start = GetParam('start', 1) - 1; # $start and $end are indexes
my $end = GetParam('end', @files * 2) - 1;
if ($end - $start <= 1) {
print Ts('It seems like module %s is causing your problem.',
$q->strong((fileparse($isGood ? $files[$end] : $files[$start]))[0])), $q->br(), $q->br();
print T('Please note that this module does not handle situations when your problem is caused by a combination of specific modules (which is rare anyway).'), $q->br();
print T('Good luck fixing your problem! ;)');
print GetFormStart(undef, 'get', 'bisect');
print GetHiddenValue('action', 'bisect');
print $q->submit(-name=>'stop', -value=>T('Stop'));
print $q->end_form();
return;
}
print T('Module count (only testable modules): '), $q->strong(scalar @files), $q->br();
print $q->br(), T('Current module statuses:'), $q->br();
my $halfsize = ($end - $start + 1) / 2.0; # + 1 because it is count
$end -= int($halfsize) unless $isGood;
$start += int($halfsize + 0.51) if $isGood; # ceil
$halfsize = ($end - $start + 1) / 2.0;
for (my $i = 0; $i < @files; $i++) {
if ($i >= $start and $i <= $end - int($halfsize)) {
print $q->strong('> + '), (fileparse($files[$i]))[0], $q->br();
} elsif ($i >= $start and $i <= $end) {
print $q->strong('> - '), (fileparse($files[$i]))[0], $q->br();
move($files[$i], $files[$i] . '.disabled');
} else {
print $q->strong('- '), (fileparse($files[$i]))[0], $q->br();
move($files[$i], $files[$i] . '.disabled');
}
}
print GetFormStart(undef, 'get', 'bisect');
print GetHiddenValue('action', 'bisect');
print GetHiddenValue('start', $start + 1);
print GetHiddenValue('end', $end + 1);
print $parameterHandover;
print $q->submit(-name=>'good', -value=>T('Good')), ' ';
print $q->submit(-name=>'bad', -value=>T('Bad')), ' ';
print $q->submit(-name=>'stop', -value=>T('Stop'));
print $q->end_form();
}
sub BisectEnableAll {
for (bsd_glob("$ModuleDir/*.p[ml].disabled")) { # reenable all modules
my $oldName = $_;
s/\.disabled$//;
print Ts('Enabling %s', (fileparse($_))[0]), '...', $q->br() if $_[0];
move($oldName, $_);
}
}

View File

@@ -31,54 +31,78 @@ sub ModuleUpdaterMenu {
}
sub ModuleUpdaterAction {
return unless UserIsAdminOrError();
UserIsAdminOrError();
RequestLockOrError();
print GetHeader('', T('Module Updater'), '');
for (bsd_glob("$ModuleDir/*.p[ml]")) {
my $curModule = fileparse($_);
ProcessModule($curModule);
print GetHeader('', T('Module Updater'), '', 'nocache');
if (GetParam('ok')) {
ModuleUpdaterApply();
} else {
unlink bsd_glob("$TempDir/*.p[ml]"); # XXX is it correct to use $TempDir for such stuff? What if something else puts .pm files there?
for (bsd_glob("$ModuleDir/*.p[ml]")) {
my $curModule = fileparse($_);
ProcessModule($curModule);
}
print $q->br();
print GetFormStart(undef, 'get');
print GetHiddenValue('action', 'updatemodules');
print $q->submit(-name=>'ok', -value=>T('Looks good. Update modules now!'));
print $q->end_form();
}
print '<strong>Done!</strong>';
PrintFooter();
ReleaseLock();
}
sub ModuleUpdaterApply {
for (bsd_glob("$TempDir/*.p[ml]")) {
my $moduleName = fileparse($_);
if (move($_, "$ModuleDir/$moduleName")) {
print $q->strong("Module $moduleName updated successfully!"), $q->br();
} else {
print $q->strong("Unable to replace module $moduleName: $!"), $q->br();
}
}
unlink bsd_glob("$TempDir/*.p[ml]"); # XXX same as above
print $q->br(), $q->strong('Done!');
}
sub ProcessModule() {
my $module = shift;
CreateDir($TempDir);
print "<hr/>";
print "<strong>Updating $module ...</strong><br/>";
if (system('wget', '-O', "$TempDir/newmodule", '--', "$OddmuseModulesUrl/$module") != 0) {
if ($? >> 8 == 8) { # wget usually returns 8 if server response is NOT FOUND
# TODO maybe there is any better way to do this?
print '<strong>There is no such module in git repository. If this is your own module, please contribute it to Oddmuse! If it is not, then it was probably removed.</strong><br/>';
return;
}
print 'There was an error downloading this module.<br/>';
print $q->hr();
print $q->strong("Diffing $module ..."), $q->br();
my $moduleData = GetRaw("$OddmuseModulesUrl/$module");
if (not $moduleData) {
print $q->strong('There was an error downloading this module.'
. ' If this is your own module, please contribute it to Oddmuse!'), $q->br();
return;
}
my $diff = DoModuleDiff("$ModuleDir/$module", "$TempDir/newmodule");
open my $fh, ">", "$TempDir/$module" or die("Could not open file. $!");
print $fh $moduleData;
close $fh;
my $diff = DoModuleDiff("$ModuleDir/$module", "$TempDir/$module");
if (not $diff) {
print '<strong>This module is up to date, there is no need to update it.</strong><br/>';
print $q->strong('This module is up to date, there is no need to update it.'), $q->br();
unlink "$TempDir/$module";
return;
}
print '<strong>There is a newer version of this module. Here is a diff:</strong><br/>';
print $q->strong('There is a newer version of this module. Here is a diff:'), $q->br();
$diff = QuoteHtml($diff);
$diff =~ tr/\r//d; # TODO is this required? # probably not
$diff =~ tr/\r//d; # TODO is this required? # probably not # but maybe it is there to fix problems with dos newlines?
for (split /\n/, $diff) {
my ($type) = /(.)/;
if ($type eq '+') {
print '<span class="updaternew">';
} elsif ($type eq '-') {
print '<span class="updaterold">';
if ($type =~ /[+-]/) {
my $class = $type eq '+' ? 'updaternew' : 'updaterold';
print $q->span({-class => $class}, $q->code($_));
} else {
print $q->span($q->code($_));
}
print '<code>' . $_ . '</code>';
print '</span>' if $type =~ /[+-]/;
print '<br/>';
print $q->br();
}
move("$TempDir/newmodule", "$ModuleDir/$module") or print "<strong>Unable to replace module: $! </strong><br/>";
print '<strong>Module updated successfully!</strong><br/>';
}
sub DoModuleDiff {

View File

@@ -167,7 +167,8 @@ anchors.
*DeletePage = *NewPermanentAnchorsDeletePage;
sub NewPermanentAnchorsDeletePage {
OldPermanentAnchorsDeletePage(@_);
my $status = OldPermanentAnchorsDeletePage(@_);
return $status if $status; # this would be the error message
DeletePermanentAnchors(@_); # the only parameter is $id
}

View File

@@ -546,7 +546,7 @@ sub NewSearchFreePrintFooter {
$q->p(GetHiddenValue('id', $id), GetHiddenValue('action', 'retag'),
T('Tags:'), $q->br(), GetTextArea('tags', join(' ', @tags), 2),
$q->br(), $q->submit(-name=>'Save', -value=>T('Save'))),
$q->endform());
$q->end_form());
} elsif ($id and @tags) {
print $q->div({-class=>'tags'},
$q->p(T('Tags:'), map { $_ = "\[\[tag:$_\]\]";

View File

@@ -368,7 +368,7 @@ sub StaticGetCommentForm {
-override=>1, -size=>40, -maxlength=>100)),
$q->p($q->submit(-name=>'Save', -accesskey=>T('s'), -value=>T('Save')), ' ',
$q->submit(-name=>'Preview', -value=>T('Preview'))),
$q->endform());
$q->end_form());
}
return '';
}

View File

@@ -111,7 +111,7 @@ sub ThreadAdd {
. '</td></tr></table>'
. '<p>'
. $q->p($q->submit(-name=>'Save', -value=>T('Save')))
. $q->endform());
. $q->end_form());
print $q->end_html;
} else {
my ($page, $thread) = ThreadExtract($id);

View File

@@ -75,6 +75,6 @@ sub DoTZ {
-values=>\@names,
-default=>GetParam('time', $defaultTZ)),
$q->submit('dotz', T('Set')));
print $q->endform . $q->end_div();
print $q->end_form . $q->end_div();
PrintFooter();
}

View File

@@ -270,7 +270,7 @@ sub GetTocHtml {
# By Usemod convention, all headers begin with depth 2. This algorithm,
# however, expects headers to begin with depth 1. Thus, to "streamline"
# things, we transform it appropriately. ;-)
if (defined &UsemodRule) { $header_depth--; }
$header_depth-- if defined &UsemodRule or defined &CreoleRule;
# If this is the first header and if this header's depth is deeper than 1,
# we manually clamp this header's depth to 1 so as to ensure the first list
@@ -280,7 +280,7 @@ sub GetTocHtml {
# Close ordered lists and list items for prior headings deeper than this
# heading's depth.
while ($list_depth > $header_depth) {
while ($list_depth > $header_depth and $list_depth != 1) {
$list_depth--;
$toc_html .= '</li></ol>';
}

View File

@@ -208,7 +208,7 @@ sub DoTranslationLink {
$q->input({-type=>'hidden', -name=>'missing',
-value=>GetParam('missing', '')}),
$q->submit('dotranslate', T('Go!')));
print $q->endform, $q->end_div();
print $q->end_form, $q->end_div();
PrintFooter();
}
}

0
t/aggregate.t Executable file → Normal file
View File

0
t/all.t Executable file → Normal file
View File

0
t/anchors.t Executable file → Normal file
View File

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2013 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2013-2014 Alex Schroeder <alex@gnu.org>
#
# 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
@@ -14,7 +14,7 @@
require 't/test.pl';
package OddMuse;
use Test::More tests => 3;
use Test::More tests => 4;
clear_pages();
@@ -28,7 +28,8 @@ add_module('ban-quick-editors.pl');
get_page('Test');
test_page(update_page('Test', 'cannot edit'),
'This page is empty');
test_page($redirect, 'Editing not allowed');
test_page($redirect, 'Editing not allowed',
'fast editing spam bot');
sleep 5;
test_page(update_page('Test', 'edit succeeded'),
'edit succeeded');

0
t/ban.t Executable file → Normal file
View File

46
t/cache.t Executable file → Normal file
View File

@@ -1,24 +1,20 @@
# Copyright (C) 2006, 2007 Alex Schroeder <alex@emacswiki.org>
# Copyright (C) 2006, 2007 Alex Schroeder <alex@gnu.org>
#
# 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 2 of the License, or
# (at your option) any later version.
# 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.
# 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, write to the
# Free Software Foundation, Inc.
# 59 Temple Place, Suite 330
# Boston, MA 02111-1307 USA
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
require 't/test.pl';
package OddMuse;
use Test::More tests => 7;
use Test::More tests => 8;
clear_pages();
@@ -27,23 +23,39 @@ sub get_etag {
return $1 if $str =~ /Etag: (.*)\r\n/;
}
sub get_last_modified {
my $str = shift;
return $1 if $str =~ /Last-Modified: (.*)\r\n/i;
}
# Get the ts from the page db and compare it to the Etag
update_page('CacheTest', 'something');
OpenPage('CacheTest');
my $ts1 = $Page{ts};
my $ts2 = get_etag(get_page('CacheTest'));
ok(abs($ts1 - $ts2) <= 1, "Latest edit of this page: $ts1 and $ts2 are close");
# When updating another page, that page's ts is the new Etag for all of them
update_page('OtherPage', 'something');
OpenPage('OtherPage');
$ts1 = $Page{ts};
$ts2 = get_etag(get_page('OtherPage'));
ok(abs($ts1 - $ts2) <= 1, "Latest edit of other page: $ts1 and $ts2 are close");
# Getting it raw should use the original timestamp
OpenPage('CacheTest');
$ts1 = $Page{ts};
$ts2 = get_etag(get_page('/raw/CacheTest?'));
ok(abs($ts1 - $ts2) <= 1, "Latest edit of raw page: $ts1 and $ts2 are close");
$page = get_page('/raw/CacheTest?');
$ts2 = get_etag($page);
ok(abs($ts1 - $ts2) <= 1, "Latest edit of raw page: $ts1 and $ts2 based on etag are close");
SKIP: {
eval { require Date::Parse };
skip ("Date::Parse not installed", 1) if $@;
$ts2 = Date::Parse::str2time(get_last_modified($page));
ok(abs($ts1 - $ts2) <= 1, "Latest edit of raw page: $ts1 and $ts2 based on last-modified timestamp are close");
}
$str = 'This is a WikiLink.';

0
t/calendar.t Executable file → Normal file
View File

0
t/clusters.t Executable file → Normal file
View File

0
t/comments.t Executable file → Normal file
View File

0
t/config-page.t Executable file → Normal file
View File

0
t/conflict.t Executable file → Normal file
View File

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2009, 2012 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20092014 Alex Schroeder <alex@gnu.org>
#
# 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
@@ -46,7 +46,6 @@ AppendStringToFile($ConfigFile, "\$WikiLinks = 0;\n");
test_page(get_page('action=browse id=HomePage username=Alex'),
'username=Alex');
SKIP: {
eval { require LWP::UserAgent; };
@@ -68,21 +67,23 @@ SKIP: {
# Set the cookie
$response = $ua->get("$wiki?action=debug;pwd=foo");
ok($response->is_success, 'request the page');
test_page($ua->cookie_jar->as_string, 'Set-Cookie.*: Wiki=pwd%251efoo');
$ua->cookie_jar->as_string =~ /Set-Cookie.*: ([^=]+)=pwd%251efoo/;
my $cookie = $1;
ok($cookie, 'pwd was set in the cookie');
test_page_negative($response->content, 'pwd');
# Change the cookie
$response = $ua->get("$wiki?action=debug;pwd=test");
test_page($ua->cookie_jar->as_string, 'Set-Cookie.*: Wiki=pwd%251etest');
test_page($ua->cookie_jar->as_string, qq{Set-Cookie.*: $cookie=pwd%251etest});
# Delete the cookie
$response = $ua->get("$wiki?action=debug;pwd=");
test_page($ua->cookie_jar->as_string, 'Set-Cookie.*: Wiki=""');
test_page($ua->cookie_jar->as_string, qq{Set-Cookie.*: $cookie=""});
# Encoding issues
$response = $ua->get("$wiki?action=rc;username=Alex\%20Schr\%C3\%B6der");
test_page($ua->cookie_jar->as_string,
'Set-Cookie.*: Wiki=username%251eAlex%20Schr%C3%B6der');
qq{Set-Cookie.*: $cookie=username%251eAlex%20Schr%C3%B6der});
test_page($response->decoded_content,
'Cookie: Wiki, username=Alex Schröder');
qq{Cookie: $cookie, username=Alex Schröder});
};

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env perl
# Copyright (C) 2006-2013 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2006-2014 Alex Schroeder <alex@gnu.org>
#
# 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
@@ -16,7 +16,7 @@
require 't/test.pl';
package OddMuse;
use Test::More tests => 108;
use Test::More tests => 111;
clear_pages();
add_module('creole.pl');
@@ -217,6 +217,12 @@ http://www.wikicreole.org/.
//a[@class="url http outside"][@href="http://www.wikicreole.org/"][em[text()="Visit the WikiCreole website"]]
[[http://www.wikicreole.org/ | Visit the WikiCreole website]]
//a[@class="url http outside"][@href="http://www.wikicreole.org/"][text()="Visit the WikiCreole website"]
[[foo bar]]
//div[text()[.="[[foo_bar"]/following-sibling::a[@class="edit"][@title="Click to edit this page"][@href="http://localhost/test.pl?action=edit;id=foo_bar"][text()="?"]/following-sibling::text()[.="]]"]]
[[foo_bar]]
//div[text()[.="[[foo_bar"]/following-sibling::a[@class="edit"][@title="Click to edit this page"][@href="http://localhost/test.pl?action=edit;id=foo_bar"][text()="?"]/following-sibling::text()[.="]]"]]
[[foo bar|text]]
//div[text()[.="[[foo_bar"]/following-sibling::a[@class="edit"][@title="Click to edit this page"][@href="http://localhost/test.pl?action=edit;id=foo_bar"][text()="?"]/following-sibling::text()[.="|text]]"]]
[[link]]
//a[text()="link"]
[[link|Go to my page]]

0
t/creoleaddition.t Normal file → Executable file
View File

1
t/crossbar.t Normal file → Executable file
View File

@@ -1,4 +1,3 @@
#!/usr/bin/env perl
# ====================[ crossbar.t ]====================

0
t/crumbs.t Executable file → Normal file
View File

6
t/default-links.t Executable file → Normal file
View File

@@ -15,7 +15,7 @@
require 't/test.pl';
package OddMuse;
use Test::More tests => 61;
use Test::More tests => 63;
clear_pages();
@@ -34,6 +34,10 @@ xpath_run_tests(split('\n',<<'EOT'));
//div[text()="[[0]]"]
[[0a]]
//a[@class="edit"][@title="Click to edit this page"][@href="http://localhost/test.pl?action=edit;id=0a"][text()="?"]
[[foo bar]]
//div[text()[.="[[foo_bar"]/following-sibling::a[@class="edit"][@title="Click to edit this page"][@href="http://localhost/test.pl?action=edit;id=foo_bar"][text()="?"]/following-sibling::text()[.="]]"]]
[[foo_bar]]
//div[text()[.="[[foo_bar"]/following-sibling::a[@class="edit"][@title="Click to edit this page"][@href="http://localhost/test.pl?action=edit;id=foo_bar"][text()="?"]/following-sibling::text()[.="]]"]]
file://home/foo/tutorial.pdf
//a[@class="url file"][@href="file://home/foo/tutorial.pdf"][text()="file://home/foo/tutorial.pdf"]
file:///home/foo/tutorial.pdf

0
t/diff.t Executable file → Normal file
View File

0
t/download.t Executable file → Normal file
View File

View File

@@ -15,7 +15,7 @@
require 't/test.pl';
package OddMuse;
use Test::More tests => 15;
use Test::More tests => 16;
use utf8; # test data is UTF-8 and it matters
SKIP: {
@@ -27,7 +27,7 @@ SKIP: {
add_module('git.pl');
if (qx($GitBinary --version) !~ /git version/) {
skip "$GitBinary not found", 15;
skip "$GitBinary not found", 16;
}
GitInitVariables();
@@ -48,8 +48,7 @@ SKIP: {
$GitResult = '';
GitRun(qw(status));
test_page($GitResult,
'nothing to commit, working directory clean');
test_page($GitResult, 'nothing to commit', 'working directory clean');
GitRun(qw(log -- Test));
test_page($GitResult,

View File

@@ -35,7 +35,7 @@ xpath_test($page,
'//span[@class="portrait gravatar"]',
'//p[contains(text(),"This is my comment")]',
'//a[@href="http://oddmuse.org/"][text()="Alex Schroeder"]',
'//img[@src="http://www.gravatar.com/avatar/' . $gravatar . '"]');
'//img[@src="https://secure.gravatar.com/avatar/' . $gravatar . '"]');
# without homepage
@@ -51,7 +51,7 @@ xpath_test($page,
'//span[@class="portrait gravatar"]',
'//p[contains(text(),"This is my comment")]',
'//a[@href="http://localhost/wiki.pl/Alex_Schroeder"][text()="Alex Schroeder"]',
'//img[@src="http://www.gravatar.com/avatar/' . $gravatar . '"]');
'//img[@src="https://secure.gravatar.com/avatar/' . $gravatar . '"]');
# with homepage an no email
@@ -68,4 +68,4 @@ xpath_test($page,
'//a[@href="http://oddmuse.org/"][text()="Alex Schroeder"]');
negative_xpath_test($page,
'//span[@class="portrait gravatar"]',
'//img[@src="http://www.gravatar.com/avatar/' . $gravatar . '"]');
'//img[@src="https://secure.gravatar.com/avatar/' . $gravatar . '"]');

0
t/history.t Executable file → Normal file
View File

0
t/image.t Executable file → Normal file
View File

0
t/include.t Executable file → Normal file
View File

0
t/indexed-search.t Executable file → Normal file
View File

0
t/irc.t Executable file → Normal file
View File

0
t/journal.t Executable file → Normal file
View File

0
t/link-all.t Executable file → Normal file
View File

0
t/localnames.t Executable file → Normal file
View File

View File

@@ -25,7 +25,7 @@ test_page(get_page('action=editlock'), 'operation is restricted');
test_page(get_page('action=editlock pwd=foo'), 'Edit lock created');
xpath_test(update_page('TestLock', 'mu!'),
'//a[@href="http://localhost/wiki.pl?action=password"][@class="password"][text()="This page is read-only"]');
test_page($redirect, '403 FORBIDDEN', 'Editing not allowed for TestLock');
test_page($redirect, '403 FORBIDDEN', 'Editing not allowed: TestLock is read-only');
test_page(get_page('action=editlock set=0'), 'operation is restricted');
test_page(get_page('action=editlock set=0 pwd=foo'), 'Edit lock removed');
RequestLockDir('main');

0
t/long-tables.t Executable file → Normal file
View File

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2009 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2009, 2014 Alex Schroeder <alex@gnu.org>
#
# 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
@@ -15,9 +15,10 @@
require 't/test.pl';
package OddMuse;
use Test::More tests => 6;
use Test::More tests => 9;
clear_pages();
# old log entry to be moved
my $log = join($FS, '1235079422', 'Ganz_und_Gar', '',
'Ladenbeschreibung und Preisliste', '62.12.165.34',
'Alex', '1', '', '');
@@ -33,3 +34,16 @@ test_page($log,
"${FS}test${FS}",
"${FS}this is a test${FS}");
test_page_negative($log, "^\n");
# old page to be deleted
OpenPage('test');
$Page{ts} = 1;
$Page{revision} = 1;
$Page{text} = $DeletedPage;
SavePage();
ok(-f GetPageFile($OpenPageName), GetPageFile($OpenPageName)
. " exists");
xpath_test(get_page('action=maintain pwd=foo'),
'//a[text()="test"]/following-sibling::text()[.=" deleted"]');
ok(! -e GetPageFile($OpenPageName), GetPageFile($OpenPageName)
. " was deleted");

0
t/major.t Executable file → Normal file
View File

0
t/markdown-rule.t Normal file → Executable file
View File

0
t/markup.t Executable file → Normal file
View File

0
t/moin.t Executable file → Normal file
View File

0
t/oddmuse-2.2.6.pl Normal file → Executable file
View File

0
t/pagenames.t Executable file → Normal file
View File

0
t/portrait.t Normal file → Executable file
View File

0
t/questionasker.t Executable file → Normal file
View File

0
t/redirection.t Executable file → Normal file
View File

View File

@@ -32,8 +32,13 @@ SKIP: {
my $response = $ua->get("$wiki?action=version");
skip("No wiki running at $wiki", 12)
unless $response->is_success;
skip("Wiki running at $wiki doesn't have the referrer-tracking extension installed", 12)
# check that the wiki is capable of running these tests
skip("Wiki running at $wiki doesn't have the Referrer-Tracking Extension installed", 12)
unless $response->content =~ /referrer-tracking\.pl/;
# if we're running in some random environment where localhost is not
# a wiki for us to interact with
skip("Wiki running at $wiki has the Question Asker Extension installed", 12)
if $response->content =~ /questionasker\.pl/;
my $id = 'Random' . time;
# make sure we're not being fooled by 404 errors

0
t/revisions.t Executable file → Normal file
View File

40
t/rollback.t Executable file → Normal file
View File

@@ -1,21 +1,20 @@
# Copyright (C) 2006, 2007, 2008 Alex Schroeder <alex@emacswiki.org>
# Copyright (C) 20062014 Alex Schroeder <alex@gnu.org>
#
# 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 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.
# 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 <http://www.gnu.org/licenses/>.
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
require 't/test.pl';
package OddMuse;
use Test::More tests => 63;
use Test::More tests => 69;
use utf8; # tests contain UTF-8 characters and it matters
clear_pages();
@@ -195,3 +194,20 @@ $ts = $Now - $KeepDays * 86400 + 100;
get_page("action=rollback to=$ts username=Alex pwd=foo");
AppendStringToFile($ConfigFile, "\$KeepDays = 7;\n");
test_page_negative(get_page("action=rc raw=1"), '[[rollback]]');
# Avoid Save button for comments
AppendStringToFile($ConfigFile, "\$CommentsPrefix = 'Comments on ';\n");
update_page('Comments_on_Test', 'no spam');
ok(get_page('action=browse id=Test raw=2')
=~ /(\d+) # Do not delete this line/,
'raw=2 returns timestamp');
$to = $1;
ok($to, 'timestamp stored');
sleep(1);
get_page('title=Comments_on_Test aftertext=http://spam/amoxil/');
test_page(get_page('Comments_on_Test'), 'spam');
# rollback without password
$page = get_page("action=rollback id=Comments_on_Test to=$to username=Alex");
test_page($page, 'Rolling back changes');
test_page_negative($page, 'Add your comment here', 'Save');

0
t/rss.t Executable file → Normal file
View File

7
t/search.t Executable file → Normal file
View File

@@ -1,4 +1,4 @@
# Copyright (C) 2006, 2007, 2009 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20062014 Alex Schroeder <alex@gnu.org>
#
# 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
@@ -15,7 +15,7 @@
require 't/test.pl';
package OddMuse;
use Test::More tests => 38;
use Test::More tests => 39;
use utf8; # tests contain UTF-8 characters and it matters
clear_pages();
@@ -102,6 +102,9 @@ xpath_test(update_page('IncludeSearch',
'//div[@class="search"]/p/span[@class="result"]/a[@class="local"][@href="http://localhost/wiki.pl/NegativeSearchTestTwo"][text()="NegativeSearchTestTwo"]',
'//p[text()=" last line"]'); # note the NL -> SPC
xpath_test(get_page('search=Schröder'),
'//input[@name="search"][@value="Schröder"]');
# Search for zero
update_page("Zero", "This is about 0 and the empty string ''.");

0
t/setext.t Executable file → Normal file
View File

0
t/sidebar.t Normal file → Executable file
View File

0
t/subscribe.t Executable file → Normal file
View File

0
t/summary.t Executable file → Normal file
View File

0
t/tags.t Executable file → Normal file
View File

0
t/test.pl Executable file → Normal file
View File

0
t/toc.t Normal file → Executable file
View File

View File

@@ -87,6 +87,7 @@ clear_pages();
my $dir = `/bin/pwd`;
chop($dir);
my $mod = 'namespaces-2.2.6.pl';
mkdir($ModuleDir);
symlink("$dir/t/$mod", "$ModuleDir/$mod");
ok(-e "$ModuleDir/$mod", "old namespaces.pl installed");

0
t/upload.t Executable file → Normal file
View File

0
t/usemod-1.0.4.pl Executable file → Normal file
View File

0
t/usemod-options.t Executable file → Normal file
View File

133
wiki.pl
View File

@@ -29,11 +29,9 @@
package OddMuse;
use strict;
use warnings;
#use diagnostics;
use CGI;
use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
use utf8; # in case anybody ever addes UTF8 characters to the source
use CGI qw/-utf8/;
use CGI::Carp qw(fatalsToBrowser);
use File::Glob ':glob';
local $| = 1; # Do not buffer output (localized for mod_perl)
@@ -217,7 +215,8 @@ sub ReportError { # fatal!
}
sub Init {
binmode(STDOUT, ':utf8');
binmode(STDOUT, ':utf8'); # this is where the HTML gets printed
binmode(STDERR, ':utf8'); # just in case somebody prints debug info to stderr
InitDirConfig();
$FS = "\x1e"; # The FS character is the RECORD SEPARATOR control char in ASCII
$Message = ''; # Warnings and non-fatal errors.
@@ -305,11 +304,10 @@ sub InitVariables { # Init global session variables for mod_perl!
CreateDir($DataDir); # Create directory if it doesn't exist
$Now = time; # Reset in case script is persistent
my $ts = (stat($IndexFile))[9]; # always stat for multiple server processes
ReInit() if not $ts or not $LastUpdate or $LastUpdate != $ts; # reinit if another process changed files (requires $DataDir)
ReInit() if not $ts or $LastUpdate != $ts; # reinit if another process changed files (requires $DataDir)
$LastUpdate = $ts;
unshift(@MyRules, \&MyRules) if defined(&MyRules) && (not @MyRules or $MyRules[0] != \&MyRules);
$RuleOrder{$_} //= 0 for @MyRules; # default is 0
@MyRules = sort {$RuleOrder{$a} <=> $RuleOrder{$b}} @MyRules;
@MyRules = sort {$RuleOrder{$a} <=> $RuleOrder{$b}} @MyRules; # default is 0
ReportError(Ts('Cannot create %s', $DataDir) . ": $!", '500 INTERNAL SERVER ERROR') unless -d $DataDir;
@IndexOptions = (['pages', T('Include normal pages'), 1, \&AllPagesList]);
foreach my $sub (@MyInitVariables) {
@@ -328,9 +326,8 @@ sub ReInit { # init everything we need if we want to link to stuff
sub InitCookie {
undef $q->{'.cookies'}; # Clear cache if it exists (for SpeedyCGI)
my $cookie = $q->cookie($CookieName);
utf8::decode($cookie); # make sure it's decoded as UTF-8
%OldCookie = split(/$FS/o, UrlDecode($cookie));
my %provided = map { utf8::decode($_); $_ => 1 } $q->param;
my %provided = map { $_ => 1 } $q->param;
for my $key (keys %OldCookie) {
SetParam($key, $OldCookie{$key}) unless $provided{$key};
}
@@ -366,10 +363,9 @@ sub CookieRollbackFix {
sub GetParam {
my ($name, $default) = @_;
utf8::encode($name); # may fail
utf8::encode($name); # turn to byte string
my $result = $q->param($name);
$result //= $default;
utf8::decode($result) if defined $result; # may fail, avoid turning undef to ''
return QuoteHtml($result); # you need to unquote anything that can have <tags>
}
@@ -457,7 +453,7 @@ sub ApplyRules {
} else {
$Includes{$OpenPageName} = 1;
local $OpenPageName = FreeToNormal($uri);
if ($type and $type eq 'text') {
if ($type eq 'text') {
print $q->pre({class=>"include $OpenPageName"}, QuoteHtml(GetPageContent($OpenPageName)));
} elsif (not $Includes{$OpenPageName}) { # with a starting tag, watch out for recursion
print $q->start_div({class=>"include $OpenPageName"});
@@ -527,7 +523,7 @@ sub ApplyRules {
$bol = (substr($_, pos() - 1, 1) eq "\n");
}
}
pos = length $_ if $_; # notify module functions we've completed rule handling
pos = length $_; # notify module functions we've completed rule handling
Clean(CloseHtmlEnvironments()); # last block -- close it, cache it
if ($Fragment ne '') {
$Fragment =~ s|<p>\s*</p>||g; # clean up extra paragraphs (see end Dirty())
@@ -677,7 +673,7 @@ sub OpenHtmlEnvironment { # close the previous $html_tag and open a new one
}
sub CloseHtmlEnvironments { # close all -- remember to use AddHtmlEnvironment('p') if required!
return CloseHtmlEnvironmentUntil() if $_ and pos($_) == length($_); # close all HTML environments if we're are at the end of this page
return CloseHtmlEnvironmentUntil() if pos($_) == length($_); # close all HTML environments if we're are at the end of this page
my $html = '';
while (@HtmlStack) {
defined $HtmlEnvironmentContainers{$HtmlStack[0]} and # avoid closing block level elements
@@ -773,7 +769,6 @@ sub DoClearCache {
sub QuoteHtml {
my $html = shift;
return '' if not $html;
$html =~ s/&/&amp;/g;
$html =~ s/</&lt;/g;
$html =~ s/>/&gt;/g;
@@ -844,9 +839,9 @@ sub PrintJournal {
$offset ||= 0;
# FIXME: Should pass filtered list of pages to SearchTitleAndBody to save time?
my @pages = sort JournalSort (grep(/$regexp/, $search ? SearchTitleAndBody($search) : AllPagesList()));
@pages = reverse @pages if $mode and ($mode eq 'reverse' or $mode eq 'future');
@pages = reverse @pages if $mode eq 'reverse' or $mode eq 'future';
$b = $Today // CalcDay($Now);
if ($mode and ($mode eq 'future' or $mode eq 'past')) {
if ($mode eq 'future' || $mode eq 'past') {
my $compare = $mode eq 'future' ? -1 : 1;
for (my $i = 0; $i < @pages; $i++) {
$a = $pages[$i];
@@ -1156,7 +1151,7 @@ sub GetPageOrEditLink { # use GetPageLink and GetEditLink if you know the result
} else { # reproduce markup if $UseQuestionmark
return GetEditLink($id, UnquoteHtml($bracket ? "[$link]" : $link)) unless $UseQuestionmark;
$link = QuoteHtml($id) . GetEditLink($id, '?');
$link .= ($free ? '|' : ' ') . $text if $text and $text ne $id;
$link .= ($free ? '|' : ' ') . $text if $text and FreeToNormal($text) ne $id;
$link = "[[$link]]" if $free;
$link = "[$link]" if $bracket or not $free and $text;
return $link;
@@ -1168,7 +1163,7 @@ sub GetPageLink { # use if you want to force a link to local pages, whether it e
$id = FreeToNormal($id);
$name ||= $id;
$class .= ' ' if $class;
return ScriptLink(UrlEncode($id), NormalToFree($name), ($class || '') . 'local',
return ScriptLink(UrlEncode($id), NormalToFree($name), $class . 'local',
undef, undef, $accesskey);
}
@@ -1268,11 +1263,13 @@ sub PageHtml {
local *STDOUT;
OpenPage($id);
open(STDOUT, '>', \$diff) or die "Can't open memory file: $!";
binmode(STDOUT); # works whether STDOUT already has the UTF8 layer or not
binmode(STDOUT, ":utf8");
PrintPageDiff();
utf8::decode($diff);
return $error if $limit and length($diff) > $limit;
open(STDOUT, '>', \$page) or die "Can't open memory file: $!";
binmode(STDOUT); # works whether STDOUT already has the UTF8 layer or not
binmode(STDOUT, ":utf8");
PrintPageHtml();
utf8::decode($page);
@@ -1461,7 +1458,7 @@ sub PageFresh { # pages can depend on other pages (ie. last update), admin statu
sub PageEtag {
my ($changed, $visible, %params) = CookieData();
return UrlEncode(join($FS, $LastUpdate, sort(values %params))); # no CTL in field values
return UrlEncode(join($FS, $LastUpdate||$Now, sort(values %params))); # no CTL in field values
}
sub FileFresh { # old files are never stale, current files are stale when the page was modified
@@ -1582,8 +1579,8 @@ sub GetRcLinesFor {
next if $idOnly and $idOnly ne $id;
next if $filterOnly and not $match{$id};
next if ($userOnly and $userOnly ne $username);
next if $minor and $minor == 1 and not $showminoredit; # skip minor edits (if [[rollback]] this is bogus)
next if not $minor and $showminoredit and $showminoredit == 2; # skip major edits
next if $minor == 1 and not $showminoredit; # skip minor edits (if [[rollback]] this is bogus)
next if not $minor and $showminoredit == 2; # skip major edits
next if $match and $id !~ /$match/i;
next if $hostOnly and $host !~ /$hostOnly/i;
my @languages = split(/,/, $languages);
@@ -1721,7 +1718,7 @@ sub GetFilterForm {
-default=>GetParam('lang', ''))));
}
return GetFormStart(undef, 'get', 'filter') . $q->p($form) . $q->table($table)
. $q->p($q->submit('dofilter', T('Go!'))) . $q->endform;
. $q->p($q->submit('dofilter', T('Go!'))) . $q->end_form;
}
sub RcHtml {
@@ -1796,7 +1793,7 @@ sub RcHtml {
$more .= ";$_=$val" if $val;
}
$html .= $q->p({-class=>'more'}, ScriptLink($more, T('More...'), 'more'));
return GetFormStart(undef, 'get', 'rc') . $html . $q->endform;
return GetFormStart(undef, 'get', 'rc') . $html . $q->end_form;
}
sub PrintRcHtml { # to append RC to existing page, or action=rc directly
@@ -2075,7 +2072,7 @@ sub DoRollback {
if ($Page{text} eq $text) {
print T("The two revisions are the same."), $q->br() if $page; # no message when doing mass revert
} elsif (not UserCanEdit($id, 1)) {
print Ts('Editing not allowed for %s.', $id), $q->br();
print Ts('Editing not allowed: %s is read-only.', $id), $q->br();
} elsif (not UserIsEditor() and my $rule = BannedContent($text)) {
print Ts('Rollback of %s would restore banned content.', $id), $rule, $q->br();
} else {
@@ -2086,7 +2083,7 @@ sub DoRollback {
WriteRcLog('[[rollback]]', $page, $to); # leave marker
print $q->end_p() . $q->end_div();
ReleaseLock();
PrintFooter($page);
PrintFooter($page, 'edit');
}
sub DoAdminPage {
@@ -2243,11 +2240,17 @@ sub GetHeaderTitle {
sub GetHttpHeader {
return if $PrintedHeader;
$PrintedHeader = 1;
my ($type, $ts, $status, $encoding) = @_; # $ts is undef, a ts, or 'nocache'
my ($type, $ts, $status, $encoding) = @_;
$q->charset($type =~ m!^(text/|application/xml)! ? 'utf-8' : ''); # text/plain, text/html, application/xml: UTF-8
my %headers = (-cache_control=>($UseCache < 0 ? 'no-cache' : 'max-age=10'));
$headers{-etag} = $ts || PageEtag() if GetParam('cache', $UseCache) >= 2;
$headers{'-last-modified'} = TimeToRFC822($ts) if $ts and $ts ne 'nocache'; # RFC 2616 section 13.3.4
# Set $ts when serving raw content that cannot be modified by cookie parameters; or 'nocache'; or undef. If you
# provide a $ts, the last-modiefied header generated will by used by HTTP/1.0 clients. If you provide no $ts, the etag
# header generated will be used by HTTP/1.1 clients. In this situation, cookie parameters can influence the look of
# the page and we cannot rely on $LastUpdate. HTTP/1.0 clients will ignore etags. See RFC 2616 section 13.3.4.
if (GetParam('cache', $UseCache) >= 2 and $ts ne 'nocache') {
$headers{'-last-modified'} = TimeToRFC822($ts) if $ts;
$headers{-etag} = PageEtag();
}
$headers{-type} = GetParam('mime-type', $type);
$headers{-status} = $status if $status;
$headers{-Content_Encoding} = $encoding if $encoding;
@@ -2281,7 +2284,6 @@ sub Cookie {
my ($changed, $visible, %params) = CookieData(); # params are URL encoded
if ($changed) {
my $cookie = join(UrlEncode($FS), %params); # no CTL in field values
utf8::encode($cookie); # prevent casting to Latin 1
my $result = $q->cookie(-name=>$CookieName, -value=>$cookie, -expires=>'+2y');
if ($visible) {
$Message .= $q->p(T('Cookie: ') . $CookieName . ', '
@@ -2384,7 +2386,6 @@ sub PrintFooter {
sub GetFooterTimestamp {
my ($id, $rev) = @_;
$rev //= '';
if ($id and $rev ne 'history' and $rev ne 'edit' and $Page{revision}) {
my @elements = ($q->br(), ($rev eq '' ? T('Last edited') : T('Edited')), TimeToText($Page{ts}),
Ts('by %s', GetAuthorLink($Page{host}, $Page{username})));
@@ -2396,7 +2397,6 @@ sub GetFooterTimestamp {
sub GetFooterLinks {
my ($id, $rev) = @_;
$rev //= '';
my @elements;
if ($id and $rev ne 'history' and $rev ne 'edit') {
if ($CommentsPattern) {
@@ -2418,7 +2418,7 @@ sub GetFooterLinks {
}
push(@elements, GetHistoryLink($id, T('View other revisions'))) if $Action{history} and $id and $rev ne 'history';
push(@elements, GetPageLink($id, T('View current revision')),
GetRCLink($id, T('View all changes'))) if $Action{history} and $rev;
GetRCLink($id, T('View all changes'))) if $Action{history} and $rev ne '';
if ($Action{contrib} and $id and $rev eq 'history') {
push(@elements, ScriptLink("action=contrib;id=" . UrlEncode($id), T('View contributors'), 'contrib'));
}
@@ -2432,7 +2432,6 @@ sub GetFooterLinks {
sub GetCommentForm {
my ($id, $rev, $comment) = @_;
$rev //= '';
if ($CommentsPattern ne '' and $id and $rev ne 'history' and $rev ne 'edit'
and $id =~ /$CommentsPattern/o and UserCanEdit($id, 0, 1)) {
return $q->div({-class=>'comment'}, GetFormStart(undef, undef, 'comment'), # protected by questionasker
@@ -2450,7 +2449,7 @@ sub GetCommentForm {
-override=>1, -size=>40, -maxlength=>100))),
$q->p($q->submit(-name=>'Save', -accesskey=>T('s'), -value=>T('Save')), ' ',
$q->submit(-name=>'Preview', -accesskey=>T('p'), -value=>T('Preview'))),
$q->endform());
$q->end_form());
}
return '';
}
@@ -2478,7 +2477,7 @@ sub GetSearchForm {
-default=>GetParam('lang', '')) . ' ';
}
return GetFormStart(undef, 'get', 'search')
. $q->p($form . $q->submit('dosearch', T('Go!'))) . $q->endform;
. $q->p($form . $q->submit('dosearch', T('Go!'))) . $q->end_form;
}
sub GetValidatorLink {
@@ -2986,21 +2985,9 @@ sub UnWiki {
sub DoEdit {
my ($id, $newText, $preview) = @_;
ValidIdOrDie($id);
UserCanEditOrDie($id);
my $upload = GetParam('upload', undef);
if (not UserCanEdit($id, 1)) {
my $rule = UserIsBanned();
if ($rule) {
ReportError(T('Edit Denied'), '403 FORBIDDEN', undef,
$q->p(T('Editing not allowed: user, ip, or network is blocked.')),
$q->p(T('Contact the wiki administrator for more information.')),
$q->p(Ts('The rule %s matched for you.', $rule) . ' '
. Ts('See %s for more information.', GetPageLink($BannedHosts))));
} else {
ReportError(T('Edit Denied'), '403 FORBIDDEN', undef,
$q->p(Ts('Editing not allowed: %s is read-only.', NormalToFree($id))));
}
} elsif ($upload and not $UploadAllowed and not UserIsAdmin()) {
if ($upload and not $UploadAllowed and not UserIsAdmin()) {
ReportError(T('Only administrators can upload files.'), '403 FORBIDDEN');
}
OpenPage($id);
@@ -3061,7 +3048,7 @@ sub GetEditForm {
} elsif ($UploadAllowed or UserIsAdmin()) {
$html .= $q->p(ScriptLink('action=edit;upload=1;id=' . UrlEncode($page_name), T('Replace this text with a file'), 'upload'));
}
$html .= $q->endform();
$html .= $q->end_form();
return $html;
}
@@ -3079,19 +3066,18 @@ sub DoDownload {
OpenPage($id) if ValidIdOrDie($id);
print $q->header(-status=>'304 NOT MODIFIED') and return if FileFresh(); # FileFresh needs an OpenPage!
my ($text, $revision) = GetTextRevision(GetParam('revision', '')); # maybe revision reset!
my $ts = $Page{ts};
if (my ($type, $encoding) = TextIsFile($text)) {
my ($data) = $text =~ /^[^\n]*\n(.*)/s;
my %allowed = map {$_ => 1} @UploadTypes;
if (@UploadTypes and not $allowed{$type}) {
ReportError(Ts('Files of type %s are not allowed.', $type), '415 UNSUPPORTED MEDIA TYPE');
}
print GetHttpHeader($type, $ts, undef, $encoding);
print GetHttpHeader($type, $Page{ts}, undef, $encoding);
require MIME::Base64;
binmode(STDOUT, ":pop:raw"); # need to pop utf8 for Windows users!?
print MIME::Base64::decode($data);
} else {
print GetHttpHeader('text/plain', $ts);
print GetHttpHeader('text/plain', $Page{ts});
print $text;
}
}
@@ -3113,7 +3099,7 @@ sub DoPassword {
print GetFormStart(undef, undef, 'password'),
$q->p(GetHiddenValue('action', 'password'), T('Password:'), ' ',
$q->password_field(-name=>'pwd', -size=>20, -maxlength=>50),
$q->submit(-name=>'Save', -accesskey=>T('s'), -value=>T('Save'))), $q->endform;
$q->submit(-name=>'Save', -accesskey=>T('s'), -value=>T('Save'))), $q->end_form;
} else {
print $q->p(T('This site does not use admin or editor passwords.'));
}
@@ -3133,6 +3119,24 @@ sub UserIsAdminOrError {
return 1;
}
sub UserCanEditOrDie {
my $id = shift;
ValidIdOrDie($id);
if (not UserCanEdit($id, 1)) {
my $rule = UserIsBanned();
if ($rule) {
ReportError(T('Edit Denied'), '403 FORBIDDEN', undef,
$q->p(T('Editing not allowed: user, ip, or network is blocked.')),
$q->p(T('Contact the wiki administrator for more information.')),
$q->p(Ts('The rule %s matched for you.', $rule) . ' '
. Ts('See %s for more information.', GetPageLink($BannedHosts))));
} else {
ReportError(T('Edit Denied'), '403 FORBIDDEN', undef,
$q->p(Ts('Editing not allowed: %s is read-only.', NormalToFree($id))));
}
}
}
sub UserCanEdit {
my ($id, $editing, $comment) = @_;
return 0 if $id eq 'SampleUndefinedPage' or $id eq T('SampleUndefinedPage')
@@ -3498,8 +3502,7 @@ sub Replace {
sub DoPost {
my $id = FreeToNormal(shift);
ValidIdOrDie($id);
ReportError(Ts('Editing not allowed for %s.', $id), '403 FORBIDDEN') unless UserCanEdit($id, 1);
UserCanEditOrDie($id);
# Lock before getting old page to prevent races
RequestLockOrError(); # fatal
OpenPage($id);
@@ -3903,7 +3906,7 @@ sub DelayRequired {
my $name = shift;
my @entries = @{$RecentVisitors{$name}};
my $ts = $entries[$SurgeProtectionViews];
return $Now - ($ts || 0) < $SurgeProtectionTime;
return ($Now - $ts) < $SurgeProtectionTime;
}
sub AddRecentVisitor {
@@ -3931,20 +3934,17 @@ sub WriteRecentVisitors {
foreach my $name (keys %RecentVisitors) {
my @entries = @{$RecentVisitors{$name}};
if ($entries[0] >= $limit) { # if the most recent one is too old, do not keep
$data .= join($FS, $name, @entries[0 .. $SurgeProtectionViews - 1]) . "\n";
$data .= join($FS, $name, @entries[0 .. $SurgeProtectionViews - 1]) . "\n";
}
}
WriteStringToFile($VisitorFile, $data);
}
sub TextIsFile {
return '' unless $_[0];
$_[0] =~ /^#FILE (\S+) ?(\S+)?\n/
}
sub TextIsFile { $_[0] =~ /^#FILE (\S+) ?(\S+)?\n/ }
sub AddModuleDescription { # cannot use $q here because this is module init time
my ($filename, $page, $dir, $tag) = @_;
my $src = "http://git.savannah.gnu.org/cgit/oddmuse.git/tree/modules/" . ($dir || '') . UrlEncode($filename) . ($tag ? '?' . $tag : '');
my $src = "http://git.savannah.gnu.org/cgit/oddmuse.git/tree/modules/$dir" . UrlEncode($filename) . ($tag ? '?' . $tag : '');
my $doc = 'http://www.oddmuse.org/cgi-bin/oddmuse/' . UrlEncode(FreeToNormal($page));
$ModulesDescription .= "<p><a href=\"$src\">" . QuoteHtml($filename) . "</a>" . ($tag ? " ($tag)" : '');
$ModulesDescription .= T(', see ') . "<a href=\"$doc\">" . QuoteHtml($page) . "</a>" if $page;
@@ -3952,5 +3952,4 @@ sub AddModuleDescription { # cannot use $q here because this is module init time
}
DoWikiRequest() if $RunCGI and not exists $ENV{MOD_PERL}; # Do everything.
warningsToBrowser(1);
1; # In case we are loaded from elsewhere