Compare commits

...

70 Commits
2.2.5 ... 2.3.0

Author SHA1 Message Date
Alex Schroeder
25989f78a5 Merge branch 'as/no-more-page-subdirectories' 2014-06-21 21:30:27 +02:00
Aki Goto
afdb7a9dcb form_timeout.pl: an anti-spam module for Oddmuse
This is an anti-spam module for Oddmuse using form timeout. Edit
permission times out after a specified duration (the default is 30
minutes). When edit content is posted directly by a spam bot without
viewing the edit form, edit will be denied.
2014-06-21 12:18:26 +02:00
Alex Schroeder
2e79a843c8 oddmuse-curl.el: Fix URL 2014-06-21 12:01:08 +02:00
Alex Schroeder
54370da235 oddmuse-curl.el: whitespace 2014-06-21 12:00:26 +02:00
Alex Schroeder
43839ac1aa upgrade.t: Testing for the changes to upgrade.pl 2014-06-21 10:45:21 +02:00
Alex Schroeder
3c2f96250b upgrade.pl: Use variables; print utf8.
Instead of fiddling with @MyInitVariables, set the options naming
pages to the empty string.
2014-06-21 04:44:33 -04:00
Alex Schroeder
9def2d2eb2 upgrade.pl: Disable some module initialisations
Some modules read a page file when initializing. As these cannot be
found before the upgrade, we need to disable them.
2014-06-21 04:18:16 -04:00
Alex Schroeder
3ad40b84fb .gitignore: .DS_Store 2014-06-20 22:39:37 +02:00
Alex Schroeder
ecda4c3d98 oddmuse-curl.el: Preview shows just the preview
Don't show the header, footer and textarea; show just the preview div.
2014-06-20 22:36:09 +02:00
Alex Schroeder
74a0576c5d oddmuse-curl.el: Font lock restructuring
All the font-locking functions are now modifying font-lock-defaults
instead of calling font-lock-add-keywords because that's what it says
in the elisp manual.
2014-06-20 21:23:30 +02:00
Alex Schroeder
a6e07a9886 oddmuse-curl.el: Preview command
The preview command uses shr, a built in HTML renderer.
2014-06-20 18:31:33 +02:00
Alex Schroeder
b76b61dc86 .gitignore: .DS_Store 2014-06-19 23:11:04 +02:00
Alex Schroeder
c3cb434973 upgrade.pl: handle name spaces 2014-06-19 23:00:18 +02:00
Alex Schroeder
62f82c2af2 upgrade.pl: New interface.
Don't rely on a separate upgrade action. Any request except for login
and unlock will trigger an upgrade, if you're an admin. Once the upgrade
is complete, the module will rename itself such that it will no longer
load.
2014-06-17 13:14:13 +02:00
Alex Schroeder
d454973294 oddmuse-curl.el: fix order of font-locking
oddmuse-basic-markup must come last (which prepends its rules to the
front) such that links get precedence over other markup.
2014-06-17 12:26:50 +02:00
Alex Schroeder
cba29c8981 oddmuse-curl.el: added oddmuse-new
A function to make it easy to blog by offering the current ISO date as
page name.
2014-06-17 10:39:05 +02:00
Alex Schroeder
4a812931c8 oddmuse-curl.el: do not break links
Added a fill-nobreak-predicate to prevent links from breaking. This uses
the face property and checks for the link face.
2014-06-17 10:37:57 +02:00
Alex-Daniel Jakimenko
093a6da63d askpage.pl: Don't keep old value of $NewComment
Resetting the value was dead code. Instead of fixing this, the old value
is now discarded. There is no point in keeping it around.
2014-06-16 15:40:13 +02:00
Aki Goto
0ab5261bc6 load-lang.pl: add $LoadLanguageDir
The new option $LoadLanguageDir specifies a directory for the language
files.
2014-06-16 15:33:53 +02:00
Alex Schroeder
1d4f3e4a28 Ask Page extension
This is a simple extension to create a page for asking questions. The
process of writing a question is similar to writing a comment, for the
only exception that after pressing the ‘Save’ button the user will be
redirected to the newly created page containing his question.
2014-06-16 11:25:13 +02:00
Alex Jakimenko
6babcffd00 Consistency in file permissions.
Some modules are executable where as others are not. This patch fixes
this.
2014-06-16 11:19:24 +02:00
Alex Jakimenko
977cbba251 Fix two issues with $NewComment.
Display $NewComment above comment textarea.

'c' is now an access key to focus comment textarea.
2014-06-16 11:06:20 +02:00
Alex Schroeder
2fc4f4b054 Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse into as/no-more-page-subdirectories 2014-06-16 10:32:31 +02:00
Alex Schroeder
53566c8434 Use a function instead of $ENV{REMOTE_ADDR}
When the webserver is behind a reverse proxy, $ENV{REMOTE_ADDR} is not
the actual remote client's address but the reverse proxy's address. The
actual remote client's address is available from an environment
variable. The name of this variable depends on the proxy, e.g. pound
uses $ENV{HTTP_X_FORWARDED_FOR}.

As suggested by tyatsumi on the wiki, all access $ENV{REMOTE_ADDR} now
happens via a new function which allows users to override it in their
config file.
2014-06-16 09:50:05 +02:00
Alex Schroeder
563e5cd9c6 upgrade.pl: delete empty directories after upgrade 2014-06-07 17:08:18 +02:00
Alex Schroeder
365d33b602 Get rid of one letter sub-directories.
Recent GNU/Linux systems use ext3 or ext4 file systems. These use HTree
to index files. Wikipedia says: "HTree indexing improved the scalability
of Linux ext2 based filesystems from a practical limit of a few thousand
files, into the range of tens of millions of files per directory. [...]
HTree indexes are available in ext3 when the dir_index feature is
enabled. [...] HTree indexes are turned on by default in ext4."

Thus, instead of working on balanced-page-directories.pl, we decided to
get rid of these sub-directories altogether.

Unfortunately, this is backwards incompatible. Users wanting to upgrade
will need to install the upgrade.pl extension in order to upgrade the
file database.
2014-06-06 17:32:44 +02:00
Alex Schroeder
eef56e435d questionasker.t: fixed number of tests 2014-06-05 17:07:15 +02:00
Alex Schroeder
2044564981 Merge branch 'master' of ssh://git.sv.gnu.org/srv/git/oddmuse 2014-06-05 05:34:08 -04:00
Aki Goto
50c9b79858 balanced-page-directories: reset GetPageDirectory
The test for migration sets GetPageDirectory; when the test fails and
we're not migrating, we need to reset GetPageDirectory to its old value.
2014-06-04 16:14:26 +02:00
Alex Schroeder
d99f62ea7e balanced-page-directories: fix page creation
Previously, no new pages could be created because of a deadlock. The
code mistakenly attempted to start migrating when a new page was
created, and since DoPost already held the main lock, migration failed.
The check was now fixed such that migration is not started when a new
page is being created.
2014-06-04 09:58:37 +02:00
Alex Schroeder
c11188fd3e fixed documentation typo 2014-06-03 16:17:07 +02:00
Alex Schroeder
dd22a852eb balanced-page-directories: new module 2014-06-03 16:14:48 +02:00
Alex Schroeder
62b2e22da8 Questionasker: encoding issue with hidden fields
We need to get rid of $q->hidden when using Unicode, as suggested by
tyatsumi on the wiki.
2014-06-03 11:19:50 +02:00
Alex Schroeder
5483bbf386 joiner: fix URL 2014-06-02 09:37:55 +02:00
Alex Schroeder
8608464863 Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2014-06-02 09:22:38 +02:00
Aki Goto
b0d983c817 joiner: user registration module 2014-06-02 09:21:15 +02:00
Aki Goto
5f58256543 Updated Japanese translation. 2014-05-27 14:45:50 +02:00
Alex Jakimenko
c5c088deb1 Do not rehash the password for every entry. 2014-04-23 17:41:44 +02:00
Alex Schroeder
a5b5af9c07 Add $PassHashFunction and $PassSalt
This allows users to have the passwords encrypted in their config
file, as suggested by Alex Jakimenko.
2014-04-23 10:32:36 +02:00
Alex Jakimenko
0dcf49e2cf Add $CommentsPattern to supplement $CommentsPrefix
$CommentsPrefix is used in the code in two contexts:

- as a string added to the original page name to create links (used as
  a plain string)

- and as a way to detect whether the page has comments or not (used as
  a regular expression)

It feels natural to split this functionality into two separate
variables. $CommentsPattern is now the regular expression. No more
CommentsPrefix='.*' hacks!

For example, now you can do some complex stuff like this:

    $CommentsPrefix = 'Comments_on_';
    $CommentsPattern = '^(?|Comments_on_(.*)|Rant_About_(.*)|\d\d\d\d-\d\d-\d\d.*|FAQ)$';

Comments_on_ , Rant_About_ and journal pages will work correctly as
comments pages, but you can also specify some other pages as well, like
FAQ. Basically it can get as complex as one wants. $1 will be used to
create a link to the original page, which means that in this particular
example both Comments_on_Test and Rant_About_Test will have a link to
Test page, while FAQ will have no link to the original page at all.

Of course, by default there will only be "$CommentsPrefix . $id" link in
the footer, so you have to provide links to Rant_About_ pages elsewhere
yourself.

If you don't want to provide a regular expression pattern, you can leave
it undefined. It will be created automatically, keeping functionality
backwards compatible. If you were using $CommentsPrefix='.*' you should
now change it to $CommentsPattern='.*'.
2014-04-21 18:43:40 +02:00
Alex Jakimenko
f3885aa213 OpenPage: Remove useless test. 2014-04-20 00:26:29 +02:00
Alex Schroeder
6136b399a6 Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2014-04-15 07:45:01 +02:00
Alex Schroeder
5cc7d55152 License, dependencies. 2014-04-15 07:39:01 +02:00
Alex Schroeder
4112d2acc4 New URL, trimmed wiki list. 2014-04-15 07:38:01 +02:00
Alex Jakimenko
f270a3ced4 Two small fixes
User input needs to be HTML quoted when printed.
The regular expression matching needs the ignore case flag.
2014-04-15 07:31:50 +02:00
Alex Schroeder
7f74d3c211 Use smaller headers 2014-04-09 08:34:14 -04:00
Alex Jakimenko
375c844e37 Specifying the number of entries for More...
This patch addes a new parameter to PrintJournal
such that a journal can have a certain number of
entries but when the user clicks on the More...
link, pagination happens with a different number.
<Journal 1,5>
2014-04-08 10:43:28 +02:00
Alex Jakimenko
efce35e250 Simplify FreeToNormal and remove if statement 2014-04-06 15:14:59 +02:00
Alex Schroeder
cff4f1fd28 Enabling diff usince C-x v = 2014-04-04 18:12:04 +02:00
Alex Schroeder
6f9ded7e41 vc-oddmuse.el added with support for C-x v l 2014-04-04 15:15:00 +02:00
Alex Jakimenko
40c01683fd Split pageidx file using space
Previously, we split it using whitespace (/\s+/). This caused a problem
if somebody managed to create a page containing non-breaking whitespace.
2014-03-29 09:42:00 +01:00
Alex Schroeder
08a4861dc3 Get rid of cloud icon. 2014-03-21 07:43:24 +01:00
Alex Schroeder
d7c40d4dbe Fix dead comment links
If the comment page does not exist, the static copy contains an anchor
element with no href attribute. Better avoid this situation.
2014-03-18 13:01:27 +01:00
Alex Schroeder
f8360bebad Produce static.css as well when exporting 2014-03-18 12:47:16 +01:00
Alex Schroeder
45a0558fcc Fixing the UTF-8 encoding issues for static export 2014-03-18 11:49:28 +01:00
Alex Schroeder
f4ff56e69f Fix HTML output for static export 2014-03-18 11:21:08 +01:00
Alex Schroeder
0d7236c047 More tests regarding local links in static exports 2014-03-18 10:32:22 +01:00
Alex Schroeder
686f24251b Fix static export of HTML pages
Apparently the html=1 parameter got lost so that you could not force an
export of all the pages.
2014-03-18 09:26:04 +01:00
Alex Schroeder
0841c834b9 Escape URL of More... links
When computing the More... link at the end of a journal, we need to URL
escape all user input. In this case, that's the regexp and search
parameter.
2014-03-17 09:52:30 +01:00
Alex Schroeder
5225bded01 Revert "Homepage URL fixing works recognizes https."
I must have been hallucinating. Instead of committing the change I
wanted to commit, I added a file that wasn't ready.

This reverts commit 670b69c118.
2014-03-17 09:51:11 +01:00
Alex Schroeder
e0d18c31e2 Homepage URL fixing works recognizes https.
When leaving a comment, users are given the option of providing a
homepage to link their name to. A common error is to just provide a
domain like "oddmuse.org" instead of a real URL. The resulting markup
used to be [oddmuse.org YourName] which doesn't do what the user
expected. That's why a piece of code used to check whether the homepage
starts with "http://" and if it doesn't, it prefixes it, resulting in
"[http://oddmuse.org YourName]". If the homepage started with
"https://", however, the code did the wrong thing. That's why we're now
checking whether the homepage starts with any known URL-protocol and a
colon.
2014-03-17 09:50:32 +01:00
Alex Schroeder
670b69c118 Homepage URL fixing works recognizes https.
When leaving a comment, users are given the option of providing a
homepage to link their name to. A common error is to just provide a
domain like "oddmuse.org" instead of a real URL. The resulting markup
used to be [oddmuse.org YourName] which doesn't do what the user
expected. That's why a piece of code used to check whether the homepage
starts with "http://" and if it doesn't, it prefixes it, resulting in
"[http://oddmuse.org YourName]". If the homepage started with
"https://", however, the code did the wrong thing. That's why we're now
checking whether the homepage starts with any known URL-protocol and a
colon.
2014-03-16 08:48:36 +01:00
Alex Schroeder
f4d0f300e6 Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse
Conflicts:
	modules/translations/chinese_cn-utf8.pl
2014-03-07 09:02:41 +01:00
Andy Stewart
53a7a9a80c chinese_cn-utf8.pl: Updated translation
Simplified Chinese, fixed.
2014-03-07 08:38:15 +01:00
Alex Schroeder
4f675de687 oddmuse-curl.el: Fix following page links. 2014-03-07 08:31:08 +01:00
Alex Schroeder
dffe5e3053 Updated with new texts. 2014-03-06 17:15:35 +01:00
Alex Schroeder
201970ba0b Merge branch 'master' of git.sv.gnu.org:/srv/git/oddmuse 2014-03-06 16:28:27 +01:00
Alex Schroeder
7e9137c6f8 Changing 回復 to 回滾 after feedback from Andy Stewart. 2014-03-06 16:23:04 +01:00
Alex Schroeder
9d81a1e3d2 DoDuckDuckGoSearch uses UrlEncode on the argument.
Drew Adams reported that clicking on the title of a page with a + in
its name had the + replaced by a space. This commit fixes this issue.
2014-03-03 08:30:21 +01:00
Alex Schroeder
2f58de9aa4 $NewText and $NewComment can be translated.
As suggested by Dexasys on the wiki.
2014-02-01 23:49:48 +01:00
47 changed files with 7094 additions and 1307 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@
/Mac/pkg/
*.dmg
*.pkg
.DS_Store

View File

@@ -1,12 +1,12 @@
;;; oddmuse-curl.el -- edit pages on an Oddmuse wiki using curl
;;
;; Copyright (C) 20062013 Alex Schroeder <alex@gnu.org>
;; Copyright (C) 20062014 Alex Schroeder <alex@gnu.org>
;; (C) 2007 rubikitch <rubikitch@ruby-lang.org>
;;
;; Latest version:
;; http://git.savannah.gnu.org/cgit/oddmuse.git/plain/contrib/oddmuse-curl.el
;; Discussion, feedback:
;; http://www.emacswiki.org/cgi-bin/wiki/OddmuseMode
;; http://www.emacswiki.org/wiki/OddmuseCurl
;;
;; 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
@@ -42,25 +42,21 @@
(require 'sgml-mode)
(require 'skeleton))
(require 'goto-addr)
(require 'info)
(require 'goto-addr); URL regexp
(require 'info); link face
(require 'shr); preview
(require 'xml); preview munging
(defcustom oddmuse-directory "~/emacs/oddmuse"
(defcustom oddmuse-directory "~/.emacs.d/oddmuse"
"Directory to store oddmuse pages."
:type '(string)
:group 'oddmuse)
(defcustom oddmuse-wikis
'(("TestWiki" "http://www.emacswiki.org/cgi-bin/test"
utf-8 "question" nil)
("EmacsWiki" "http://www.emacswiki.org/cgi-bin/emacs"
'(("EmacsWiki" "http://www.emacswiki.org/cgi-bin/emacs"
utf-8 "uihnscuskc" nil)
("CommunityWiki" "http://www.communitywiki.org/cw"
utf-8 "question" nil)
("OddmuseWiki" "http://www.oddmuse.org/cgi-bin/oddmuse"
utf-8 "question" nil)
("CampaignWiki" "http://www.campaignwiki.org/wiki/NameOfYourWiki"
utf-8 "ts" nil))
utf-8 "question" nil))
"Alist mapping wiki names to URLs.
The elements in this list are:
@@ -160,17 +156,18 @@ It must print the RSS 3.0 text format to stdout.
(defvar oddmuse-post-command
(concat "curl --silent --write-out '%{http_code}'"
" --form title=%t"
" --form summary=%s"
" --form username=%u"
" --form password=%p"
" --form title='%t'"
" --form summary='%s'"
" --form username='%u'"
" --form password='%p'"
" --form %q=1"
" --form recent_edit=%m"
" --form oldtime=%o"
" --form text=\"<-\""
" %w")
" --form text='<-'"
" '%w'")
"Command to use for publishing pages.
It must accept the page on stdin.
It must accept the page on stdin and print the HTTP status code
on stdout.
%? '?' character
%t pagename
@@ -182,6 +179,29 @@ It must accept the page on stdin.
%o oldtime, a timestamp provided by Oddmuse
%w URL of the wiki as provided by `oddmuse-wikis'")
(defvar oddmuse-preview-command
(concat "curl --silent"
" --form title='%t'"
" --form username='%u'"
" --form password='%p'"
" --form %q=1"
" --form recent_edit=%m"
" --form oldtime=%o"
" --form Preview=Preview"; the only difference
" --form text='<-'"
" '%w'")
"Command to use for previewing pages.
It must accept the page on stdin and print the HTML on stdout.
%? '?' character
%t pagename
%u username
%p password
%q question-asker cookie
%m minor edit
%o oldtime, a timestamp provided by Oddmuse
%w URL of the wiki as provided by `oddmuse-wikis'")
(defvar oddmuse-link-pattern
"\\<[[:upper:]]+[[:lower:]]+\\([[:upper:]]+[[:lower:]]*\\)+\\>"
"The pattern used for finding WikiName.")
@@ -218,61 +238,102 @@ This is used by Oddmuse to merge changes.")
(defun oddmuse-creole-markup ()
"Implement markup rules for the Creole markup extension."
(font-lock-add-keywords
nil
'(("^=[^=\n]+" 0 '(face info-title-1 help-echo "Creole H1")); = h1
("^==[^=\n]+" 0 '(face info-title-2 help-echo "Creole H2")); == h2
("^===[^=\n]+" 0 '(face info-title-3 help-echo "Creole H3")); === h3
("^====+[^=\n]+" 0 '(face info-title-4 help-echo "Creole H4")); ====h4
("\\_<//\\(.*\n\\)*?.*?//" 0 '(face italic help-echo "Creole italic")); //italic//
("\\*\\*\\(.*\n\\)*?.*?\\*\\*" 0 '(face bold help-echo "Creole bold")); **bold**
("__\\(.*\n\\)*?.*?__" 0 '(face underline help-echo "Creole underline")); __underline__
("|+=?" 0 '(face font-lock-string-face help-echo "Creole table cell"))
("\\\\\\\\[ \t]+" 0 '(face font-lock-warning-face help-echo "Creole line break"))
("^#+ " 0 '(face font-lock-constant-face help-echo "Creole ordered list"))
("^- " 0 '(face font-lock-constant-face help-echo "Creole ordered list")))))
(setcar font-lock-defaults
(append
'(("^=[^=\n]+"
0 '(face info-title-1
help-echo "Creole H1")); = h1
("^==[^=\n]+"
0 '(face info-title-2
help-echo "Creole H2")); == h2
("^===[^=\n]+"
0 '(face info-title-3
help-echo "Creole H3")); === h3
("^====+[^=\n]+"
0 '(face info-title-4
help-echo "Creole H4")); ====h4
("\\_<//\\(.*\n\\)*?.*?//"
0 '(face italic
help-echo "Creole italic")); //italic//
("\\*\\*\\(.*\n\\)*?.*?\\*\\*"
0 '(face bold
help-echo "Creole bold")); **bold**
("__\\(.*\n\\)*?.*?__"
0 '(face underline
help-echo "Creole underline")); __underline__
("|+=?"
0 '(face font-lock-string-face
help-echo "Creole table cell"))
("\\\\\\\\[ \t]+"
0 '(face font-lock-warning-face
help-echo "Creole line break"))
("^#+ "
0 '(face font-lock-constant-face
help-echo "Creole ordered list"))
("^- "
0 '(face font-lock-constant-face
help-echo "Creole ordered list")))
(car font-lock-defaults))))
(defun oddmuse-bbcode-markup ()
"Implement markup rules for the bbcode markup extension."
(font-lock-add-keywords
nil
`(("\\[b\\]\\(.*\n\\)*?.*?\\[/b\\]"
0 '(face bold help-echo "BB code bold"))
("\\[i\\]\\(.*\n\\)*?.*?\\[/i\\]"
0 '(face italic help-echo "BB code italic"))
("\\[u\\]\\(.*\n\\)*?.*?\\[/u\\]"
0 '(face underline help-echo "BB code underline"))
(,(concat "\\[url=" goto-address-url-regexp "\\]")
0 '(face font-lock-builtin-face help-echo "BB code url"))
("\\[/?\\(img\\|url\\)\\]"
0 '(face font-lock-builtin-face help-echo "BB code url or img"))
("\\[s\\(trike\\)?\\]\\(.*\n\\)*?.*?\\[/s\\(trike\\)?\\]"
0 '(face strike help-echo "BB code strike"))
("\\[/?\\(left\\|right\\|center\\)\\]"
0 '(face font-lock-constant-face help-echo "BB code alignment")))))
(setcar font-lock-defaults
(append
`(("\\[b\\]\\(.*\n\\)*?.*?\\[/b\\]"
0 '(face bold
help-echo "BB code bold"))
("\\[i\\]\\(.*\n\\)*?.*?\\[/i\\]"
0 '(face italic
help-echo "BB code italic"))
("\\[u\\]\\(.*\n\\)*?.*?\\[/u\\]"
0 '(face underline
help-echo "BB code underline"))
(,(concat "\\[url=" goto-address-url-regexp "\\]")
0 '(face font-lock-builtin-face
help-echo "BB code url"))
("\\[/?\\(img\\|url\\)\\]"
0 '(face font-lock-builtin-face
help-echo "BB code url or img"))
("\\[s\\(trike\\)?\\]\\(.*\n\\)*?.*?\\[/s\\(trike\\)?\\]"
0 '(face strike
help-echo "BB code strike"))
("\\[/?\\(left\\|right\\|center\\)\\]"
0 '(face font-lock-constant-face
help-echo "BB code alignment")))
(car font-lock-defaults))))
(defun oddmuse-usemod-markup ()
"Implement markup rules for the Usemod markup extension."
(font-lock-add-keywords
nil
'(("^=[^=\n]+=$"
0 '(face info-title-1 help-echo "Usemod H1"))
("^==[^=\n]+==$"
0 '(face info-title-2 help-echo "Usemod H2"))
("^===[^=\n]+===$"
0 '(face info-title-3 help-echo "Usemod H3"))
("^====+[^=\n]+====$"
0 '(face info-title-4 help-echo "Usemod H4"))
("^ .+?$"
0 '(face font-lock-comment-face help-echo "Usemod block"))
("^[#]+ "
0 '(face font-lock-constant-face help-echo "Usemod ordered list")))))
(setcar font-lock-defaults
(append
'(("^=[^=\n]+=$"
0 '(face info-title-1
help-echo "Usemod H1"))
("^==[^=\n]+==$"
0 '(face info-title-2
help-echo "Usemod H2"))
("^===[^=\n]+===$"
0 '(face info-title-3
help-echo "Usemod H3"))
("^====+[^=\n]+====$"
0 '(face info-title-4
help-echo "Usemod H4"))
("^ .+?$"
0 '(face font-lock-comment-face
help-echo "Usemod block"))
("^[#]+ "
0 '(face font-lock-constant-face
help-echo "Usemod ordered list")))
(car font-lock-defaults))))
(defun oddmuse-usemod-html-markup ()
"Implement markup rules for the HTML option in the Usemod markup extension."
(font-lock-add-keywords
nil
'(("<\\(/?[a-z]+\\)" 1 '(face font-lock-function-name-face help-echo "Usemod HTML"))))
(setcar font-lock-defaults
(append
'(("<\\(/?[a-z]+\\)"
1 '(face font-lock-function-name-face
help-echo "Usemod HTML")))
(car font-lock-defaults)))
(set (make-local-variable 'sgml-tag-alist)
`(("b") ("code") ("em") ("i") ("strong") ("nowiki")
("pre" \n) ("tt") ("u")))
@@ -280,42 +341,70 @@ This is used by Oddmuse to merge changes.")
(defun oddmuse-extended-markup ()
"Implement markup rules for the Markup extension."
(font-lock-add-keywords
nil
'(("\\*\\w+[[:word:]-%.,:;\'\"!? ]*\\*"
0 '(face bold help-echo "Markup bold"))
("\\_</\\w+[[:word:]-%.,:;\'\"!? ]*/"
0 '(face italic help-echo "Markup italic"))
("_\\w+[[:word:]-%.,:;\'\"!? ]*_"
0 '(face underline help-echo "Markup underline")))))
(setcar font-lock-defaults
(append
'(("\\*\\w+[[:word:]-%.,:;\'\"!? ]*\\*"
0 '(face bold
help-echo "Markup bold"))
("\\_</\\w+[[:word:]-%.,:;\'\"!? ]*/"
0 '(face italic
help-echo "Markup italic"))
("_\\w+[[:word:]-%.,:;\'\"!? ]*_"
0 '(face underline
help-echo "Markup underline")))
(car font-lock-defaults))))
(defun oddmuse-basic-markup ()
"Implement markup rules for the basic Oddmuse setup without extensions.
This function should come come last in `oddmuse-markup-functions'
because of such basic patterns as [.*] which are very generic."
(font-lock-add-keywords
nil
`((,oddmuse-link-pattern
0 '(face link help-echo "Basic wiki name"))
("\\[\\[.*?\\]\\]"
0 '(face link help-echo "Basic free link"))
(,(concat "\\[" goto-address-url-regexp "\\( .+?\\)?\\]")
0 '(face link help-echo "Basic external free link"))
("^\\([*]+\\)"
0 '(face font-lock-constant-face help-echo "Basic bullet list"))))
(goto-address))
(setcar font-lock-defaults
(append
`((,oddmuse-link-pattern
0 '(face link
help-echo "Basic wiki name"))
("\\[\\[.*?\\]\\]"
0 '(face link
help-echo "Basic free link"))
(,(concat "\\[" goto-address-url-regexp "\\( .+?\\)?\\]")
0 '(face link
help-echo "Basic external free link"))
("^\\([*]+\\)"
0 '(face font-lock-constant-face
help-echo "Basic bullet list")))
(car font-lock-defaults))))
;; Should determine this automatically based on the version? And cache it per wiki?
;; http://emacswiki.org/wiki?action=version
;; Should determine this automatically based on the version? And cache
;; it per wiki? http://emacswiki.org/wiki?action=version
(defvar oddmuse-markup-functions
'(oddmuse-basic-markup
oddmuse-extended-markup
'(oddmuse-creole-markup
oddmuse-usemod-markup
oddmuse-creole-markup
oddmuse-bbcode-markup)
oddmuse-bbcode-markup
oddmuse-extended-markup
oddmuse-basic-markup
goto-address)
"The list of functions to call when `oddmuse-mode' runs.
Later functions take precedence because they call `font-lock-add-keywords'
which adds the expressions to the front of the existing list.")
If these functions add font-locking, they should modify
`font-lock-defaults'. See `font-lock-keywords' for documentation.
If these functions all prepend their keywords, you should list
the most important function last.
Here's a template for your code:
\(setcar font-lock-defaults
(append
'((REGEXP
0 '(face FACE
help-echo DOCSTRING)))
(car font-lock-defaults)))")
(defun oddmuse-nobreak-p ()
"Prevent line break of links.
This depends on the `link' face."
(let ((face (get-text-property (point) 'face)))
(if (listp face)
(memq 'link face)
(eq 'link face))))
(define-derived-mode oddmuse-mode text-mode "Odd"
"Simple mode to edit wiki pages.
@@ -335,6 +424,7 @@ Customize `oddmuse-wikis' to add more wikis to the list.
Font-locking is controlled by `oddmuse-markup-functions'.
\\{oddmuse-mode-map}"
(setq font-lock-defaults '(nil))
(mapc 'funcall oddmuse-markup-functions)
(font-lock-mode 1)
(when buffer-file-name
@@ -353,14 +443,18 @@ Font-locking is controlled by `oddmuse-markup-functions'.
(prog1 (match-string 1)
(replace-match "")
(set-buffer-modified-p nil)))))
(set (make-local-variable 'fill-nobreak-predicate)
'(oddmuse-nobreak-p))
(setq indent-tabs-mode nil))
(autoload 'sgml-tag "sgml-mode" t)
(define-key oddmuse-mode-map (kbd "C-c C-t") 'sgml-tag)
(define-key oddmuse-mode-map (kbd "C-c C-o") 'oddmuse-follow)
(define-key oddmuse-mode-map (kbd "C-c C-n") 'oddmuse-new)
(define-key oddmuse-mode-map (kbd "C-c C-m") 'oddmuse-toggle-minor)
(define-key oddmuse-mode-map (kbd "C-c C-c") 'oddmuse-post)
(define-key oddmuse-mode-map (kbd "C-c C-p") 'oddmuse-preview)
(define-key oddmuse-mode-map (kbd "C-x C-v") 'oddmuse-revert)
(define-key oddmuse-mode-map (kbd "C-c C-f") 'oddmuse-edit)
(define-key oddmuse-mode-map (kbd "C-c C-i") 'oddmuse-insert-pagename)
@@ -397,11 +491,11 @@ Font-locking is controlled by `oddmuse-markup-functions'.
("%p" . oddmuse-password)
("%q" . question)
("%o" . oddmuse-revision)
("%r" . regexp)
("%\\?" . hatena)))
(when (and (boundp (cdr pair)) (stringp (symbol-value (cdr pair))))
(setq command (replace-regexp-in-string (car pair)
(shell-quote-argument
(symbol-value (cdr pair)))
(symbol-value (cdr pair))
command t t))))
command))
@@ -450,23 +544,37 @@ Use a prefix argument to force a reload of the page."
(pop-to-buffer (get-buffer name))
(let* ((wiki-data (assoc wiki oddmuse-wikis))
(url (nth 1 wiki-data))
(oddmuse-page-name pagename)
(oddmuse-page-name pagename)
(command (oddmuse-format-command oddmuse-get-command))
(coding (nth 2 wiki-data))
(buf (find-file-noselect (concat oddmuse-directory "/" wiki "/"
pagename)))
(coding-system-for-read coding)
(coding-system-for-write coding))
;; don't use let for dynamically bound variable
(set-buffer buf)
(unless (equal name (buffer-name)) (rename-buffer name))
(erase-buffer)
(let ((max-mini-window-height 1))
(oddmuse-run "Loading" command buf nil))
(oddmuse-run "Loading" command buf))
(pop-to-buffer buf)
(oddmuse-mode)))))
(defalias 'oddmuse-go 'oddmuse-edit)
;;;###autoload
(defun oddmuse-new (wiki pagename)
"Create a new page on a wiki.
WIKI is the name of the wiki as defined in `oddmuse-wikis'.
The pagename begins with the current date."
(interactive
(list (completing-read "Wiki: " oddmuse-wikis nil t oddmuse-wiki)
(replace-regexp-in-string
" +" "_"
(read-from-minibuffer "Pagename: "
(format-time-string "%Y-%m-%d ")))))
(oddmuse-edit wiki pagename))
(autoload 'word-at-point "thingatpt")
;;;###autoload
@@ -474,8 +582,9 @@ Use a prefix argument to force a reload of the page."
"Figure out what page we need to visit
and call `oddmuse-edit' on it."
(interactive "P")
(let ((pagename (if arg (oddmuse-read-pagename oddmuse-wiki)
(oddmuse-pagename-at-point))))
(let ((pagename (or (and arg (oddmuse-read-pagename oddmuse-wiki))
(oddmuse-pagename-at-point)
(oddmuse-read-pagename oddmuse-wiki))))
(oddmuse-edit (or oddmuse-wiki
(read-from-minibuffer "URL: "))
pagename)))
@@ -494,46 +603,57 @@ and call `oddmuse-edit' on it."
(defun oddmuse-pagename-at-point ()
"Page name at point."
(let ((pagename (word-at-point)))
(cond ((oddmuse-current-free-link-contents))
((oddmuse-wikiname-p pagename)
pagename)
(t
(error "No link found at point")))))
(or (oddmuse-current-free-link-contents)
(oddmuse-wikiname-p pagename))))
(defun oddmuse-wikiname-p (pagename)
"Whether PAGENAME is WikiName or not."
(let (case-fold-search)
(string-match (concat "^" oddmuse-link-pattern "$") pagename)))
(when pagename
(let (case-fold-search)
(when (string-match (concat "^" oddmuse-link-pattern "$") pagename)
pagename))))
;; (oddmuse-wikiname-p nil)
;; (oddmuse-wikiname-p "WikiName")
;; (oddmuse-wikiname-p "not-wikiname")
;; (oddmuse-wikiname-p "notWikiName")
(defun oddmuse-run (mesg command buf on-region)
(defun oddmuse-run (mesg command buf &optional on-region expected-code)
"Print MESG and run COMMAND on the current buffer.
MESG should be appropriate for the following uses:
\"MESG...\"
\"MESG...done\"
\"MESG failed: REASON\"
Save outpout in BUF and report an appropriate error.
ON-REGION indicates whether the commands runs on the region
such as when posting, or whether it just runs by itself such
as when loading a page."
(message "%s..." mesg)
as when loading a page.
If ON-REGION is not nil, the command output is compared to
EXPECTED-CODE. The command is supposed to print the HTTP status
code on stdout, so usually we want to provide either 302 or 200
as EXPECTED-CODE."
(message "%s using %s..." mesg command)
(when (numberp expected-code)
(setq expected-code (number-to-string expected-code)))
;; If ON-REGION, the resulting HTTP CODE is found in BUF, so check
;; that, too.
(if (and (= 0 (if on-region
(shell-command-on-region (point-min) (point-max) command buf)
(shell-command-on-region (point-min) (point-max)
command buf)
(shell-command command buf)))
(or (not on-region)
(string= "302" (with-current-buffer buf
(buffer-string)))))
(not expected-code)
(string= expected-code
(with-current-buffer buf
(buffer-string)))))
(message "%s...done" mesg)
(let ((err "Unknown error"))
(with-current-buffer buf
(when (re-search-forward "<h1>\\(.*?\\)\\.?</h1>" nil t)
(setq err (match-string 1))))
(error "%s...%s" mesg err))))
(error "Error %s: %s" mesg err))))
;;;###autoload
(defun oddmuse-post (summary)
@@ -560,7 +680,60 @@ The current wiki is taken from `oddmuse-wiki'."
(buf (get-buffer-create " *oddmuse-response*"))
(text (buffer-string)))
(and buffer-file-name (basic-save-buffer))
(oddmuse-run "Posting" command buf t)))
(oddmuse-run "Posting" command buf t 302)))
;;;###autoload
(defun oddmuse-preview ()
"Preview the current buffer for the current wiki.
The current wiki is taken from `oddmuse-wiki'."
(interactive)
;; when using prefix or on a buffer that is not in oddmuse-mode
(when (or (not oddmuse-wiki) current-prefix-arg)
(set (make-local-variable 'oddmuse-wiki)
(completing-read "Wiki: " oddmuse-wikis nil t)))
(when (not oddmuse-page-name)
(set (make-local-variable 'oddmuse-page-name)
(read-from-minibuffer "Pagename: " (buffer-name))))
(let* ((list (assoc oddmuse-wiki oddmuse-wikis))
(url (nth 1 list))
(oddmuse-minor (if oddmuse-minor "on" "off"))
(coding (nth 2 list))
(coding-system-for-read coding)
(coding-system-for-write coding)
(question (nth 3 list))
(oddmuse-username (or (nth 4 list)
oddmuse-username))
(command (oddmuse-format-command oddmuse-preview-command))
(buf (get-buffer-create " *oddmuse-response*"))
(text (buffer-string)))
(and buffer-file-name (basic-save-buffer))
(oddmuse-run "Previewing" command buf t); no status code on stdout
(message "Rendering...")
(pop-to-buffer "*Preview*")
(erase-buffer)
(shr-insert-document
(with-current-buffer (get-buffer " *oddmuse-response*")
(let ((html (libxml-parse-html-region (point-min) (point-max))))
(oddmuse-find-node
(lambda (node)
(and (eq (xml-node-name node) 'div)
(string= (xml-get-attribute node 'class) "preview")))
html))))
(goto-char (point-min))
(message "Rendering...done")))
(defun oddmuse-find-node (test node)
"Return the child of NODE that satisfies TEST.
TEST is a function that takes a node as an argument. NODE is a
node as returned by `libxml-parse-html-region' or
`xml-parse-region'. The function recurses through the node tree."
(if (funcall test node)
node
(dolist (child (xml-node-children node))
(when (listp child)
(let ((result (oddmuse-find-node test child)))
(when result
(return result)))))))
(defun oddmuse-make-completion-table (wiki)
"Create pagename completion table for WIKI.
@@ -615,7 +788,7 @@ With universal argument, reload."
(unless (equal name (buffer-name)) (rename-buffer name))
(erase-buffer)
(let ((max-mini-window-height 1))
(oddmuse-run "Load recent changes" command buf nil))
(oddmuse-run "Load recent changes" command buf))
(oddmuse-rc-buffer)
(set (make-local-variable 'oddmuse-wiki) wiki)))))

187
contrib/vc-oddmuse.el Normal file
View File

@@ -0,0 +1,187 @@
;;; vc-oddmuse.el -- add VC support to oddmuse-curl
;;
;; Copyright (C) 2014 Alex Schroeder <alex@gnu.org>
;;
;; Latest version:
;; http://git.savannah.gnu.org/cgit/oddmuse.git/plain/contrib/vc-oddmuse.el
;; Discussion, feedback:
;; http://www.emacswiki.org/cgi-bin/wiki/OddmuseCurl
;;
;; 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; Add the following to your init file:
;;
;; (add-to-list 'vc-handled-backends 'oddmuse)
(add-to-list 'vc-handled-backends 'oddmuse)
(require 'oddmuse)
(require 'diff)
(defun vc-oddmuse-revision-granularity () 'file)
(defun vc-oddmuse-registered (file)
"Handle files in `oddmuse-directory'."
(string-match (concat "^" (expand-file-name oddmuse-directory))
(file-name-directory file)))
(defun vc-oddmuse-state (file)
"No idea."
'up-to-date)
(defun vc-oddmuse-working-revision (file)
"No idea")
(defun vc-oddmuse-checkout-model (files)
"No locking."
'implicit)
(defun vc-oddmuse-create-repo (file)
(error "You cannot create Oddmuse wikis using Emacs."))
(defun vc-oddmuse-register (files &optional rev comment)
"This always works.")
(defun vc-oddmuse-revert (file &optional contents-done)
"No idea"
nil)
(defvar vc-oddmuse-log-command
"curl --silent %w\"?action=rc;showedit=1;all=1;from=1;raw=1;match=%r\""
"Command to use for publishing index pages.
It must print the page to stdout.
%? '?' character
%w URL of the wiki as provided by `oddmuse-wikis'
%r Regular expression, URL encoded, of the pages to limit ourselves to.
This uses the free variable `regexp'.")
(defun vc-oddmuse-print-log (files buffer &optional shortlog
start-revision limit)
"Load complete recent changes for the files."
(let* ((wiki (or oddmuse-wiki
(completing-read "Wiki: " oddmuse-wikis nil t)))
(wiki-data (assoc wiki oddmuse-wikis))
(url (nth 1 wiki-data))
(regexp (concat
"^(" ;; Perl regular expression!
(mapconcat 'file-name-nondirectory files "|")
")$"))
(command (oddmuse-format-command vc-oddmuse-log-command))
(coding (nth 2 wiki-data))
(coding-system-for-read coding)
(coding-system-for-write coding)
(max-mini-window-height 1))
(oddmuse-run "Getting recent changes" command buffer nil))
;; Parse current buffer as RSS 3.0 and display it correctly.
(save-excursion
(with-current-buffer buffer
(let (result)
(dolist (item (cdr (split-string (buffer-string) "\n\n")));; skip first item
(let ((data (mapcar (lambda (line)
(when (string-match "^\\(.*?\\): \\(.*\\)" line)
(cons (match-string 1 line)
(match-string 2 line))))
(split-string item "\n"))))
(setq result (cons data result))))
(dolist (item (nreverse result))
(insert "title: " (cdr (assoc "title" item)) "\n"
"version: " (cdr (assoc "revision" item)) "\n"
"generator: " (cdr (assoc "generator" item)) "\n"
"timestamp: " (cdr (assoc "last-modified" item)) "\n\n"
" " (or (cdr (assoc "description" item)) ""))
(fill-paragraph)
(insert "\n\n"))
(goto-char (point-min))))))
(defun vc-oddmuse-log-outgoing ()
(error "This is not supported."))
(defun vc-oddmuse-log-incoming ()
(error "This is not supported."))
(defvar vc-oddmuse-get-revision-command
"curl --silent %w\"?action=browse;id=%t;revision=%o;raw=1\""
"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'")
(defvar vc-oddmuse-get-history-command
"curl --silent %w\"?action=history;id=%t;raw=1\""
"Command to use to get the history 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'")
(defun vc-oddmuse-diff (files &optional rev1 rev2 buffer)
"Report the differences for FILES."
(setq buffer (or buffer (get-buffer-create "*vc-diff*")))
(dolist (file files)
(setq oddmuse-page-name (file-name-nondirectory file)
oddmuse-wiki (or oddmuse-wiki
(file-name-nondirectory
(directory-file-name
(file-name-directory file)))))
(let* ((wiki-data (or (assoc oddmuse-wiki oddmuse-wikis)
(error "Cannot find data for wiki %s" oddmuse-wiki)))
(url (nth 1 wiki-data)))
(unless rev1
;; Since we don't know the most recent revision we have to fetch
;; it from the server every time.
(with-temp-buffer
(let ((max-mini-window-height 1))
(oddmuse-run "Determining latest revision"
(oddmuse-format-command vc-oddmuse-get-history-command)
(current-buffer) nil))
(if (re-search-forward "^revision: \\([0-9]+\\)$" nil t)
(setq rev1 (match-string 1))
(error "Cannot determine the latest revision from the page history"))))
(dolist (rev (list rev1 rev2))
(when (and rev
(not (file-readable-p (concat oddmuse-directory
"/" oddmuse-wiki "/"
oddmuse-page-name
".~" rev "~"))))
(let* ((oddmuse-revision rev)
(command (oddmuse-format-command vc-oddmuse-get-revision-command))
(coding (nth 2 wiki-data))
(filename (concat oddmuse-directory "/" oddmuse-wiki "/"
oddmuse-page-name ".~" rev "~"))
(coding-system-for-read coding)
(coding-system-for-write coding))
(with-temp-buffer
(let ((max-mini-window-height 1))
(oddmuse-run (concat "Downloading revision " rev)
command (current-buffer) nil))
(write-file filename)))))
(diff-no-select
(if rev1
(concat oddmuse-directory "/" oddmuse-wiki "/" oddmuse-page-name ".~" rev1 "~")
file)
(if rev2
(concat oddmuse-directory "/" oddmuse-wiki "/" oddmuse-page-name ".~" rev2 "~")
file)
nil
(vc-switches 'oddmuse 'diff)
buffer))))
(provide 'vc-oddmuse)

View File

@@ -237,9 +237,6 @@ a.near:link {
a.near:visited {
color:#550;
}
a.tag:before {
content:"\2601\ ";
}
ol, ul, dl {
padding-top:0.5em;
}

View File

@@ -28,7 +28,7 @@ body {
padding:2% 5%;
margin:0;
font-family: "Gentium Basic", "Bookman Old Style", "Times New Roman", serif;
font-size: 18pt;
font-size: 16pt;
}
div.header h1 {
@@ -129,9 +129,15 @@ dd {
margin-bottom:1ex;
}
textarea { width:100%; height:80%; }
textarea {
width:100%;
height:80%;
font-size: 12pt;
}
textarea#summary { height: 3em; }
input {
font-size: 12pt;
}
div.image span.caption {
margin: 0 1em;
}

0
modules/aawrapperdiv.pl Executable file → Normal file
View File

0
modules/antispam.pl Executable file → Normal file
View File

64
modules/askpage.pl Normal file
View File

@@ -0,0 +1,64 @@
# 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/>.
$ModulesDescription .= '<p><a href="http://git.savannah.gnu.org/cgit/oddmuse.git/tree/modules/askpage.pl">askpage.pl</a>, see <a href="http://www.oddmuse.org/cgi-bin/oddmuse/Ask_Page_Extension">Ask Page Extension</a></p>';
use Fcntl qw(:DEFAULT :flock);
use vars qw($AskPage $QuestionPage $NewQuestion);
# Don't forget to set your $CommentsPattern to include both $AskPage and $QuestionPage
$AskPage = 'Ask';
$QuestionPage = 'Question_';
$NewQuestion = 'Write your question here:';
sub IncrementInFile {
my $filename = shift;
sysopen my $fh, $filename, O_RDWR|O_CREAT or die "can't open $filename: $!";
flock $fh, LOCK_EX or die "can't flock $filename: $!";
my $num = <$fh> || 1;
seek $fh, 0, 0 or die "can't rewind $filename: $!";
truncate $fh, 0 or die "can't truncate $filename: $!";
(print $fh $num+1, "\n") or die "can't write $filename: $!";
close $fh or die "can't close $filename: $!";
return $num;
}
*OldAskPageDoPost=*DoPost;
*DoPost=*NewAskPageDoPost;
sub NewAskPageDoPost {
my $id = FreeToNormal(shift);
if ($id eq $AskPage and not GetParam('text', undef)) {
my $currentId = IncrementInFile("$DataDir/curquestion");
$currentQuestion =~ s/[\s\n]//g;
return OldAskPageDoPost($QuestionPage . $currentQuestion, @_);
} else {
return OldAskPageDoPost($id, @_);
}
}
*OldAskPageGetCommentForm=*GetCommentForm;
*GetCommentForm=*NewAskPageGetCommentForm;
sub NewAskPageGetCommentForm {
my ($id, $rev, $comment) = @_;
$NewComment = $NewQuestion if $id eq $AskPage;
return OldAskPageGetCommentForm(@_);
}
*OldAskPageJournalSort=*JournalSort;
*JournalSort=NewAskPageJournalSort;
sub NewAskPageJournalSort {
return OldAskPageJournalSort() unless $a =~ m/^$QuestionPage\d+$/ and $b =~ m/^$QuestionPage\d+$/;
($b =~ m/$QuestionPage(\d+)/)[0] <=> ($a =~ m/$QuestionPage(\d+)/)[0];
}

0
modules/backlinks.pl Executable file → Normal file
View File

View File

@@ -0,0 +1,175 @@
# Copyright (C) 2014 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2014 Aki Goto <tyatsumi@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/>.
=head1 Balanced Page Directories
B<WARNING: This module is deprecated.> Oddmuse no longer disperses
page data files into 27 directories based on the first character of
the page name. The directories used to be "A" to "Z", and "other". If
you uses your wiki as a blog, all the pages starting with a date ended
up in "other". If your page names started with letters other than "A"
to "Z", all the pages ended up in "other". If you were using comment
pages, all your comment pages ended in "C". This module was intended
to create more subdirectories and spread them more evenly. This is no
longer necessary, as the typical filesystem's performance no longer
degrades with tens of thousands of files in a directory. I'm assuming
most Oddmuse hosts to use some form of GNU/Linux with ext3 or ext4
with dir_index option.
The remaining info for this module is all deprecated.
=over
The ext2 inode specification allows for over 100 trillion files to
reside in a single directory, however because of the current
linked-list directory implementation, only about 10-15 thousand files
can realistically be stored in a single directory. L<haversian-ga on
09 Dec 2002 22:56
PST|http://answers.google.com/answers/threadview?id=122241>
=back
CAUTION: When this extension is installed, your data structure I<must>
change. Make sure you have a backup of your data directory somewhere.
=head2 Finding the right directory
On the command line, finding the right subdirectory can be a problem.
Here's how to use md5sum. Note that the -n option to echo prevents the
trailing newline. Its inclusion would change the checksum.
echo -n HomePage | md5sum | cut -c 1-2
c1
echo -n ホームページ | md5sum | cut -c 1-2
10
=head2 $BalancedPageDirectoriesSize
If you have more than 2560000 pages (w00t!) you might want to set
$BalancedPageDirectoriesSize to 3. This will give you 16× more
directories, which should let you have 40960000 pages. Also, please
let us know about your wiki. :)
=head2 Migration
Once you install the code, reload any page. This should trigger
migration. No output is produced during migration. Migration is
triggered whenever a page file isn't found but a page is found at the
default old location. If, for example, $PageDir/c1/HomePage.pg doesn't
exist but $PageDir/h/HomePage.pg does, and the wiki can be locked, the
wiki is locked and migration is started.
=cut
$ModulesDescription .= '<p><a href="http://git.savannah.gnu.org/cgit/oddmuse.git/tree/modules/balanced-page-directories.pl">balanced-page-directories.pl</a>, see <a href="http://www.oddmuse.org/cgi-bin/oddmuse/Balanced_Page_Directories_Extension">Balanced Page Directories Extension</a>';
use Digest::MD5 qw(md5_hex);
use File::Find qw(finddepth);
use vars qw($BalancedPageDirectoriesSize);
$BalancedPageDirectoriesSize = 2;
*OldBalancedPageDirectoriesGetPageDirectory = *GetPageDirectory;
*GetPageDirectory = *NewBalancedPageDirectoriesGetPageDirectory;
sub NewBalancedPageDirectoriesGetPageDirectory {
my $id = shift;
utf8::encode($id);
return substr(md5_hex($id), 0, $BalancedPageDirectoriesSize);
}
*OldBalancedPageDirectoriesOpenPage = *OpenPage;
*OpenPage = *NewBalancedPageDirectoriesOpenPage;
sub NewBalancedPageDirectoriesOpenPage {
my $id = shift;
if (! -f GetPageFile($id)) {
BalancedPageDirectoriesMigrate($id);
}
return OldBalancedPageDirectoriesOpenPage($id, @_);
}
sub BalancedPageDirectoriesMigrate {
my $id = shift;
# This code is called if the page file does not exist. Perhaps we
# need to migrate? Check if the old page file exists. If it does
# not, there is no point in migration.
*GetPageDirectory = *OldBalancedPageDirectoriesGetPageDirectory;
if (not -f GetPageFile($id)) {
*GetPageDirectory = *NewBalancedPageDirectoriesGetPageDirectory;
return;
}
# Make sure we can change the data structure now.
RequestLockOrError();
# Now we know that we need to migrate. The list of pages is scanned
# using globbing.
SetParam('refresh', 1);
for $id (AllPagesList()) {
*GetPageDirectory = *OldBalancedPageDirectoriesGetPageDirectory;
my $page_from = GetPageFile($id);
my $keep_from = GetKeepDir($id);
my $lock_from = GetLockedPageFile($id);
my $joiner_from = $JoinerDir . '/' . GetPageDirectory($username) if $JoinerDir;
my $joiner_email_from = $JoinerEmailDir . '/' . GetPageDirectory($username) if $JoinerEmailDir;
my $referrer_from = $RefererDir . '/' . GetPageDirectory($id) if $RefererDir;
*GetPageDirectory = *NewBalancedPageDirectoriesGetPageDirectory;
my $page_to = GetPageFile($id);
my $keep_to = GetKeepDir($id);
my $lock_to = GetLockedPageFile($id);
my $joiner_to = $JoinerDir . '/' . GetPageDirectory($username) if $JoinerDir;
my $joiner_email_to = $JoinerEmailDir . '/' . GetPageDirectory($username) if $JoinerEmailDir;
my $referrer_to = $RefererDir . '/' . GetPageDirectory($id) if $RefererDir;
# no clobbering
if (! -f $page_to) {
CreatePageDir($PageDir, $id);
rename $page_from, $page_to || ReportError("Cannot rename $page_from");
}
if (-f $lock_from and ! -f $lock_to) {
rename $lock_from, $lock_to || ReportError("Cannot rename $lock_from");
}
if (-d $keep_from and ! -d $keep_to) {
CreateKeepDir($KeepDir, $id);
rename $keep_from, $keep_to || ReportError("Cannot rename $keep_from");
}
if ($joiner_from and -d $joiner_from and ! -d $joiner_to) {
CreatePageDir($JoinerDir, $id);
rename $joiner_from, $joiner_to || ReportError("Cannot rename $joiner_from");
}
if ($joiner_email_from and -d $joiner_email_from and ! -d $joiner_email_to) {
CreatePageDir($JoinerEmailDir, $id);
rename $joiner_email_from, $joiner_email_to || ReportError("Cannot rename $joiner_email_from");
}
if ($referrer_from and -d $referrer_from and ! -d $referrer_to) {
CreateRefererDir($RefererDir, $id);
rename $referrer_from, $referrer_to || ReportError("Cannot rename $referrer_from");
}
}
# Delete empty subdirectories. Actually, attempt to delete all the
# directories, depth first. It will simply fail for the non-empty
# directories. http://www.perlmonks.org/?node_id=520791
for my $parent ($PageDir, $KeepDir, $JoinerDir, $JoinerEmailDir, $RefererDir) {
next unless $parent;
finddepth(sub { rmdir $_ if -d }, $parent);
}
ReleaseLock();
}

View File

@@ -25,7 +25,7 @@ sub BanQuickNewUserIsBanned {
if (not $rule
and $SurgeProtection # need surge protection
and GetParam('title')) {
my $name = GetParam('username', $ENV{'REMOTE_ADDR'});
my $name = GetParam('username', GetRemoteAddress());
my @entries = @{$RecentVisitors{$name}};
# $entry[0] is $Now after AddRecentVisitor
my $ts = $entries[1];

0
modules/blockquote.pl Executable file → Normal file
View File

0
modules/clustermap.pl Executable file → Normal file
View File

0
modules/crossbar.pl Executable file → Normal file
View File

View File

@@ -91,14 +91,14 @@ sub DespamBannedContent {
foreach my $url (@urls) {
if ($url =~ /($regexp)/i) {
return Tss('Rule "%1" matched "%2" on this page.',
QuoteHtml($regexp), $url);
QuoteHtml($regexp), QuoteHtml($url));
}
}
}
# depends on strange-spam.pl!
foreach (@DespamStrangeRules) {
my $regexp = $_;
if ($str =~ /($regexp)/) {
if ($str =~ /($regexp)/i) {
my $match = $1;
$match =~ s/\n/ /g;
return Tss('Rule "%1" matched "%2" on this page.',

View File

@@ -44,6 +44,6 @@ sub DuckDuckGoSearchInit {
}
sub DoDuckDuckGoSearch {
my $search = GetParam('search', undef);
my $search = UrlEncode(GetParam('search', undef));
print $q->redirect({-uri=>"https://www.duckduckgo.com/?q=$search+site%3A$DuckDuckGoSearchDomain"});
}

106
modules/form_timeout.pl Normal file
View File

@@ -0,0 +1,106 @@
# form_timeout.pl - a form timeout based anti-spam module for Oddmuse
#
# Copyright (C) 2014 Aki Goto <tyatsumi@gmail.com>
#
# Original code is in PHP from http://textcaptcha.com/really
# by Rob Tuley <hello@rob.cx>. Used with permission.
#
# 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/>.
$ModulesDescription .= '<p>form_timeout_token.pl</p>';
=head1 DESCRIPTION
This is an anti-spam module for Oddmuse using form timeout method.
Edit permission is timed out in specified duration (default is 30 minutes)
after viewing the edit form. When edit content is posted directly by a spam bot
without viewing the edit form, edit will be denied.
=head1 CONFIGURATION
$FormTimeoutSalt
Mandatory. Token hash salt. Specify arbitrary string.
Default = undef.
$FormTimeoutTimeout
The form timeout in seconds.
Default = 60 * 30 (30 minutes).
=cut
use vars qw($FormTimeoutSalt $FormTimeoutTimeout);
use Digest::MD5 qw(md5_hex);
$FormTimeoutSalt = undef;
$FormTimeoutTimeout = 60 * 30; # 30 minutes
push(@MyInitVariables, \&FormTimeoutInitVariables);
sub FormTimeoutInitVariables {
if (!defined($FormTimeoutSalt)) {
ReportError(T('Set $FormTimeoutSalt.'), '500 INTERNAL SERVER ERROR');
}
}
sub FormTimeoutGetHash {
my ($when) = @_;
return md5_hex($FormTimeoutSalt . $when);
}
sub FormTimeoutGetToken {
return $Now . '#' . FormTimeoutGetHash($Now);
}
sub FormTimeoutGetTime {
my ($token) = @_;
my ($when, $hash) = split /#/, $token;
my $valid_hash = FormTimeoutGetHash($when);
if ($hash ne $valid_hash) {
return '';
}
return $when;
}
sub FormTimeoutCheck {
my $token = GetParam('form_timeout_token', '');
my $when = FormTimeoutGetTime($token);
if ($when eq '' || $when < $Now - $FormTimeoutTimeout) {
return 0;
}
return 1;
}
*OldFormTimeoutGetFormStart = *GetFormStart;
*GetFormStart = *NewFormTimeoutGetFormStart;
sub NewFormTimeoutGetFormStart {
my ($ignore, $method, $class) = @_;
my $form = OldFormTimeoutGetFormStart($ignore, $method, $class);
my $token = FormTimeoutGetToken();
$form .= $q->input({-type=>'hidden', -name=>'form_timeout_token',
-value=>$token});
return $form;
}
*OldFormTimeoutDoEdit = *DoEdit;
*DoEdit = *NewFormTimeoutDoEdit;
sub NewFormTimeoutDoEdit {
my ($id, $newText, $preview) = @_;
if (!FormTimeoutCheck()) {
ReportError(T('Form Timeout'), '403 FORBIDDEN', undef,
$q->p(Ts('Editing not allowed: %s is read-only.', NormalToFree($id))));
}
OldFormTimeoutDoEdit($id, $newText, $preview);
}

0
modules/hibernal.pl Executable file → Normal file
View File

0
modules/htmllinks.pl Executable file → Normal file
View File

View File

@@ -24,7 +24,7 @@ sub DoPrintableIndex {
print GetHeader('', T('Index'), '');
my @pages = PrintableIndexPages();
my %hash;
map { push(@{$hash{GetPageDirectory($_)}}, $_); } @pages;
map { push(@{$hash{substr($_,0,1)}}, $_); } @pages;
print '<div class="content printable index">';
print $q->p($q->a({-name=>"top"}),
map { $q->a({-href=>"#$_"}, $_); } sort keys %hash);

1131
modules/joiner.pl Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@ $ModulesDescription .= '<p><a href="http://git.savannah.gnu.org/cgit/oddmuse.git
$CookieParameters{interface} = '';
use vars qw($CurrentLanguage);
use vars qw($CurrentLanguage $LoadLanguageDir);
my %library= ('bg' => 'bulgarian-utf8.pl',
'de' => 'german-utf8.pl',
@@ -60,6 +60,7 @@ sub LoadLanguage {
foreach $_ (@prefs) {
last if $Lang{$_} eq 'en'; # the default
my $file = $library{$Lang{$_}};
$file = "$LoadLanguageDir/$file" if defined $LoadLanguageDir;
if (-r $file) {
do $file;
do "$ConfigFile-$Lang{$_}" if -r "$ConfigFile-$Lang{$_}";

2
modules/logbannedcontent.pl Executable file → Normal file
View File

@@ -45,6 +45,6 @@ sub LogWrite {
my $rule = shift;
my $id = $OpenPageName || GetId();
AppendStringToFile($BannedFile,
join("\t", TimeToW3($Now), $ENV{'REMOTE_ADDR'}, $id, $rule)
join("\t", TimeToW3($Now), GetRemoteAddress(), $id, $rule)
. "\n");
}

View File

@@ -33,10 +33,10 @@ $Action{$SelfBan} = \&DoSelfBan;
sub DoSelfBan {
my $date = &TimeToText($Now);
my $str = '^' . quotemeta($ENV{REMOTE_ADDR});
my $str = '^' . quotemeta(GetRemoteAddress());
OpenPage($BannedHosts);
Save ($BannedHosts, $Page{text} . "\n\nself-ban on $date\n $str",
Ts("Self-ban by %s", $ENV{REMOTE_ADDR}), 1); # minor edit
Ts("Self-ban by %s", GetRemoteAddress()), 1); # minor edit
ReportError(T("You have banned your own IP."));
}
@@ -52,7 +52,7 @@ sub OpenProxyNewDoEdit {
sub BanOpenProxy {
my ($force) = @_;
my $ip = $ENV{REMOTE_ADDR};
my $ip = GetRemoteAddress();
my $limit = 60*60*24*30; # rescan after 30 days
# Only check each IP address once a month
my %proxy = split(/\s+/, ReadFile($OpenProxies));

0
modules/poetry.pl Executable file → Normal file
View File

View File

@@ -78,7 +78,8 @@ sub NewQuestionaskerDoPost {
print GetHeader('', T('Edit Denied'), undef, undef, '403 FORBIDDEN');
print $q->p(T('You did not answer correctly.'));
print GetFormStart(), QuestionaskerGetQuestion(1),
(map { $q->hidden($_, '') }
(map { $q->input({-type=>'hidden', -name=>$_,
-value=>UnquoteHtml(GetParam($_))}) }
qw(title text oldtime summary recent_edit aftertext)), $q->end_form;
PrintFooter();
# logging to the error log file of the server

View File

@@ -252,7 +252,7 @@ sub ReCaptchaCheckAnswer {
eval "use Captcha::reCAPTCHA";
my $result = Captcha::reCAPTCHA->new()->check_answer(
$ReCaptchaPrivateKey,
$ENV{'REMOTE_ADDR'},
GetRemoteAddress(),
GetParam('recaptcha_challenge_field'),
GetParam('recaptcha_response_field')
);

View File

@@ -80,7 +80,7 @@ sub RefererNewDeletePage {
sub GetRefererFile {
my $id = shift;
return $RefererDir . '/' . GetPageDirectory($id) . "/$id.rf";
return "$RefererDir/$id.rf";
}
sub ReadReferers {

0
modules/sabifoo.pl Executable file → Normal file
View File

0
modules/slideshow.pl Executable file → Normal file
View File

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2004-2013 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2004-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
@@ -35,6 +35,7 @@ sub DoStatic {
}
CreateDir($StaticDir);
%StaticFiles = ();
print '<p>' unless $raw;
StaticWriteFiles();
print '</p>' unless $raw;
PrintFooter() unless $raw;
@@ -56,15 +57,21 @@ sub StaticMimeTypes {
sub StaticWriteFiles {
my $raw = GetParam('raw', 0);
my $html = GetParam('html', 0);
local *ScriptLink = *StaticScriptLink;
local *GetDownloadLink = *StaticGetDownloadLink;
# get rid of subscribe link in the footer by mail.pl
local *GetCommentForm = *MailOldGetCommentForm if defined &MailNewGetCommentForm;
foreach my $id (AllPagesList()) {
if ($StaticAlways > 1
or GetParam('html', 0)
or $html
or PageIsUploadedFile($id)) {
StaticWriteFile($id);
StaticWriteFile($id, $html);
}
}
if ($StaticAlways > 1 or $html) {
StaticWriteCss();
}
}
sub StaticScriptLink {
@@ -120,15 +127,19 @@ sub StaticFileName {
}
sub StaticWriteFile {
my $id = shift;
my ($id, $html) = @_;
my $raw = GetParam('raw', 0);
my $filename = StaticFileName($id);
OpenPage($id);
my ($mimetype, $encoding, $data) = $Page{text} =~ /^\#FILE ([^ \n]+) ?([^ \n]*)\n(.*)/s;
open(F,"> $StaticDir/$filename") or ReportError(Ts('Cannot write %s', $filename));
my ($mimetype, $encoding, $data) =
$Page{text} =~ /^\#FILE ([^ \n]+) ?([^ \n]*)\n(.*)/s;
open(F,"> $StaticDir/$filename")
or ReportError(Ts('Cannot write %s', $filename));
if ($data) {
binmode(F);
StaticFile($id, $mimetype, $data);
} elsif ($html) {
binmode(F, ':utf8');
StaticHtml($id);
} else {
print "no data for ";
@@ -141,7 +152,6 @@ sub StaticWriteFile {
sub StaticFile {
my ($id, $type, $data) = @_;
require MIME::Base64;
binmode(F);
print F MIME::Base64::decode($data);
}
@@ -200,7 +210,8 @@ EOT
print F $q->div({-class=>'content'}, PageHtml($id)); # this reopens the page currently open
# footer
my $links = '';
if ($OpenPageName !~ /^$CommentsPrefix/) { # fails if $CommentsPrefix is empty!
if ($OpenPageName !~ /^$CommentsPrefix/ # fails if $CommentsPrefix is empty!
and $IndexHash{$CommentsPrefix . $OpenPageName}) {
$links .= ScriptLink(UrlEncode($CommentsPrefix . $OpenPageName),
T('Comments on this page'));
}
@@ -216,6 +227,21 @@ EOT
print F '</body></html>';
}
sub StaticWriteCss {
my $css;
if ($StyleSheet) {
$css = GetRaw($StyleSheet);
}
if (not $css and $IndexHash{$StyleSheetPage}) {
$css = GetPageContent($StyleSheetPage);
}
if (not $css) {
$css = GetRaw('http://www.oddmuse.org/default.css');
}
WriteStringToFile("$StaticDir/static.css", $css) if $css;
chmod 0644,"$StaticDir/static.css";
}
*StaticFilesOldSave = *Save;
*Save = *StaticFilesNewSave;

View File

@@ -422,7 +422,7 @@ sub StaticNewDoRollback {
} elsif (!UserCanEdit($id, 1)) {
print Ts('Editing not allowed for %s.', $id), $q->br();
} else {
Save($id, $text, Ts('Rollback to %s', TimeToText($to)), $minor, ($Page{ip} ne $ENV{REMOTE_ADDR}));
Save($id, $text, Ts('Rollback to %s', TimeToText($to)), $minor, ($Page{ip} ne GetRemoteAddress()));
StaticDeleteFile($id);
print Ts('%s rolled back', GetPageLink($id)), ($ts ? ' ' . Ts('to %s', TimeToText($to)) : ''), $q->br();
}

View File

@@ -142,7 +142,7 @@ Go!
(minor)
(次要的)
rollback
new
新增
All changes for %s
@@ -168,13 +168,13 @@ Revision %s
Contributors to %s
編寫 %s 的作者
Missing target for rollback.
找不到要回的目標
找不到要回的目標
Target for rollback is too far back.
要回的目標已太久以前了
要回的目標已太久以前了
A username is required for ordinary users.
需使用普通用戶名稱
Rolling back changes
修改
修改
The two revisions are the same.
二個版本相同
Editing not allowed for %s.
@@ -182,9 +182,9 @@ Editing not allowed for %s.
Rollback of %s would restore banned content.
Rollback to %s
%s
%s
%s rolled back
%s 已回
%s 已回
to %s
%s
Index of all pages
@@ -644,7 +644,7 @@ SPAM 廣告頁面
Cannot find revision %s.
無法取得版本 %s
Revert to revision %1: %2
至版本 %1: %2
至版本 %1: %2
Marked as %s.
標記為 %s
Cannot find unspammed revision.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

103
modules/upgrade.pl Normal file
View File

@@ -0,0 +1,103 @@
# Copyright (C) 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
# 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/>.
use utf8;
# We are now running in InitModules. InitVariables will be called later.
# We want to prevent any calls to GetPageContent and the like.
*UpgradeOldInitVariables = *InitVariables;
*InitVariables = *UpgradeNewInitVariables;
sub UpgradeNewInitVariables {
$InterMap = undef;
$LocalNamesPage = undef;
$SidebarName = undef;
$NearMap = undef;
UpgradeOldInitVariables(@_);
}
*DoBrowseRequest = *DoUpgrade;
sub DoUpgrade {
# The only thing allowed besides upgrading is login and unlock
my $action = lc(GetParam('action', ''));
if (($action eq 'password' or $action eq 'unlock')
and $Action{$action}) {
return &{$Action{$action}}($id);
}
# Only admins may upgrade
ReportError(T('Upgrading Database'),
'403 FORBIDDEN', 0,
$q->p(T('This operation is restricted to administrators only...'))
. $q->p(ScriptLink('action=password', T('Login'), 'password')))
unless UserIsAdmin();
ReportError(T('Upgrading Database'),
'403 FORBIDDEN', 0,
$q->p(T('Did the previous upgrade end with an error? A lock was left behind.'))
. $q->p(ScriptLink('action=unlock', T('Unlock wiki'), 'unlock')))
unless RequestLockDir('main');
print GetHeader('', T('Upgrading Database')),
$q->start_div({-class=>'content upgrade'});
if (-e $IndexFile) {
unlink $IndexFile;
}
print "<p>Renaming files...";
for my $ns ('', keys %InterSite) {
next unless -d "$DataDir/$ns";
print "<br />\n<strong>$ns</strong>" if $ns;
for my $dir ($PageDir, $KeepDir, $RefererDir, $JoinerDir, $JoinerEmailDir) {
next unless $dir;
$dir =~ s/^$DataDir/$DataDir\/$ns/ if $ns;
for my $old (bsd_glob("$dir/*/*", bsd_glob("$dir/*/.*"))) {
next if $old eq '.' or $old eq '..';
my $oldname = $old;
utf8::decode($oldname);
print "<br />\n$oldname";
my $new = $old;
$new =~ s!/([A-Z]|other)/!/!;
if ($old eq $new) {
print " does not fit the pattern!";
} elsif (not rename $old, $new) {
my $newname = $new;
utf8::decode($newname);
print " → $newname failed!";
}
}
for my $subdir (grep(/\/([A-Z]|other)$/, bsd_glob("$dir/*"))) {
rmdir $subdir; # ignore errors
}
}
}
print $q->end_p();
if (rename "$ModuleDir/upgrade.pl", "$ModuleDir/upgrade.done") {
print $q->p(T("Upgrade complete."))
} else {
print $q->p(T("Upgrade complete. Please remove $ModuleDir/upgade.pl, now."))
}
ReleaseLock();
print $q->end_p(), $q->end_div();
PrintFooter();
}

View File

@@ -0,0 +1,44 @@
# Copyright (C) 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
# 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/>.
require 't/test.pl';
package OddMuse;
use Test::More tests => 10;
use utf8; # tests contain UTF-8 characters and it matters
clear_pages();
update_page('HomePage', 'Das ist ein Ei.');
ok(-f GetPageFile('HomePage'), 'page file');
update_page('HomePage', 'This is an egg.');
ok(-f GetKeepFile('HomePage', 1), 'keep file');
update_page('ホームページ', 'これが卵です。');
ok(-f GetPageFile('ホームページ'), 'Japanese page file');
update_page($StyleSheetPage, '/* nothing to see */', '', 0, 1);
ok(-f GetPageFile($StyleSheetPage), 'locked page file');
ok(-f GetLockedPageFile($StyleSheetPage), 'page lock');
add_module('balanced-page-directories.pl');
test_page(get_page('HomePage'), 'This is an egg.');
ok(-f GetKeepFile('HomePage', 1), 'keep file');
test_page(get_page('ホームページ'), 'これが卵です。');
ok(-f GetLockedPageFile($StyleSheetPage), 'page lock');
# create a new page
test_page(update_page('サイトマップ', '日本語ユーザーに向けて'),
'日本語ユーザーに向けて');

View File

@@ -1,24 +1,20 @@
# Copyright (C) 2006 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 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 => 35;
use Test::More tests => 36;
clear_pages();
AppendStringToFile($ConfigFile, "\$CommentsPrefix = 'Comments on ';\n");
@@ -89,11 +85,17 @@ test_page(get_page('Comments_on_Yadda'), 'This is my comment\.', '-- Alex');
test_page(get_page('action=rc raw=1'), 'title: Comments on Yadda',
'description: This is my comment.', 'generator: Alex');
# homepage
get_page('title=Comments_on_Yadda', 'aftertext=This%20is%20another%20comment.',
'username=Alex', 'homepage=http%3a%2f%2fwww%2eoddmuse%2eorg%2f');
xpath_test(get_page('Comments_on_Yadda'),
'//p[contains(text(),"This is my comment.")]',
'//a[@class="url http outside"][@href="http://www.oddmuse.org/"][text()="Alex"]');
# variant without protocol
get_page('title=Comments_on_Yadda', 'aftertext=This%20is%20another%20comment.',
'username=Berta', 'homepage=alexschroeder%2ech');
xpath_test(get_page('Comments_on_Yadda'),
'//a[@class="url http outside"][@href="http://alexschroeder.ch"][text()="Berta"]');
my $textarea = '//textarea[@name="aftertext"][@id="aftertext"]';
xpath_test(get_page('Comments_on_Yadda'), $textarea);

View File

@@ -39,7 +39,7 @@ update_page('ExpiredPage', "Still more spam from http://example.com.");
update_page('BannedContent', " example\\.com\n", 'required', 0, 1);
unlink("$DataDir/keep/E/ExpiredPage/1.kp")
unlink("$DataDir/keep/ExpiredPage/1.kp")
or die "Cannot delete kept revision: $!";
my $page = get_page('action=spam');

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2011 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20112014 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 => 34;
use Test::More tests => 35;
clear_pages();
@@ -38,7 +38,7 @@ test_page($page, '2011-12-17', '2011-12-16', '2011-12-15',
test_page_negative($page, '2011-12-12', '2011-12-11', '2011-12-10',
'2011-12-09', '2011-12-08');
xpath_test($page, '//a[@href="http://localhost/wiki.pl?action=more;num=5;regexp=^\d\d\d\d-\d\d-\d\d;search=;mode=;offset=5"][text()="More..."]');
xpath_test($page, '//a[@href="http://localhost/wiki.pl?action=more;num=5;regexp=%5e%5cd%5cd%5cd%5cd-%5cd%5cd-%5cd%5cd;search=;mode=;offset=5"][text()="More..."]');
# check that the link for more actually works
@@ -60,7 +60,12 @@ test_page($page, '2011-12-13', '2011-12-12', '2011-12-11',
'2011-12-10', '2011-12-09');
xpath_test($page, '//a[text()="More..."]');
# one las check
# one last check
xpath_test_negative(get_page("action=more num=5 offset=6 "),
'//a[text()="More..."]');
# check for unescaped URL
$page = update_page('Plus', "Using a plus:\n\n<journal 5 \"^.+\">");
xpath_test($page, '//a[text()="More..."][@href="http://localhost/wiki.pl?action=more;num=5;regexp=%5e.%2b;search=;mode=;offset=5"]');

4042
t/oddmuse-2.2.6.pl Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2008 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20082014 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 => 14;
use Test::More tests => 15;
clear_pages();
add_module('questionasker.pl');
@@ -55,3 +55,7 @@ test_page(update_page('test', 'answer new question', undef, undef, undef,
test_page(get_page('Comments_on_test'),
'label for="username"',
'say hi');
# test for corruption of Unicode text
update_page('Umlaute', '<Schröder>');
test_page($redirect, '&lt;Schröder&gt;')

View File

@@ -1,21 +1,22 @@
# Copyright (C) 2007, 2008, 2009 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20072014 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 => 29;
use utf8;
use Test::More tests => 36;
clear_pages();
add_module('static-copy.pl');
@@ -114,16 +115,37 @@ xpath_test(update_page('HomePage', "Static: [[image:Trogs]]"),
# delete the static pages and regenerate it
ok(unlink("$DataDir/static/Trogs.svgz"), "Deleted $DataDir/static/Trogs.svgz");
ok(unlink("$DataDir/static/Logo.png"), "Deleted $DataDir/static/Logo.png");
test_page(get_page('action=static raw=1 pwd=foo'), "Trogs", "Logo");
ok(-f "$DataDir/static/Trogs.svgz", "$DataDir/static/Trogs.svgz exists");
ok(-f "$DataDir/static/Logo.png", "$DataDir/static/Logo.png exists");
ok(! -f "$DataDir/static/HomePage.html", "$DataDir/static/HomePage.html does not exist");
test_page(get_page('action=static raw=1 pwd=foo html=1'), "Trogs", "Logo", "HomePage");
ok(-f "$DataDir/static/Trogs.svgz", "$DataDir/static/Trogs.svgz exists");
ok(-f "$DataDir/static/Logo.png", "$DataDir/static/Logo.png exists");
ok(-f "$DataDir/static/HomePage.html", "$DataDir/static/HomePage.html exists");
# Make sure spaces are translated to underscores (fixed in image.pl)
# StaticWriteFiles must write uploaded files only (since $StaticAlways = 1)
$page = get_page('action=static raw=1 pwd=foo');
test_page($page, "Trogs", "Logo"); # Remember, a rollback has restored Logo.png
test_page_negative($page, "HomePage"); # since it an ordinary page
ok(-s "$DataDir/static/Trogs.svgz", "$DataDir/static/Trogs.svgz has nonzero size");
ok(-s "$DataDir/static/Logo.png", "$DataDir/static/Logo.png has nonzero size");
ok(! -e "$DataDir/static/HomePage.html", "$DataDir/static/HomePage.html does not exist");
# force generation of HomePage using html=1
$page = get_page('action=static raw=1 pwd=foo html=1');
test_page($page, "Trogs", "Logo", "HomePage");
test_page_negative($page, "no data"); # must not skip HomePage!
ok(-s "$DataDir/static/Trogs.svgz", "$DataDir/static/Trogs.svgz has nonzero size");
ok(-s "$DataDir/static/Logo.png", "$DataDir/static/Logo.png has nonzero size");
ok(-s "$DataDir/static/HomePage.html", "$DataDir/static/HomePage.html has nonzero size");
# check that links between pages work as expected
xpath_test(update_page("Test", "Link to HomePage. Testing Ümlaute."),
'//a[text()="HomePage"][@href="http://localhost/wiki.pl/HomePage"]');
test_page(get_page('action=static raw=1 pwd=foo html=1'), 'Test');
xpath_test_file("$DataDir/static/Test.html",
'//a[text()="HomePage"][@href="HomePage.html"]');
test_file("$DataDir/static/Test.html",
"Ümlaute");
test_file("$DataDir/static/static.css",
"body { background-color:#FFF; color:#000; margin:1em 2em; }");
# make sure spaces are translated to underscores (fixed in image.pl)
add_module('image.pl');
# Now, create real pages. First, we'll use the ordinary image link to

View File

@@ -108,7 +108,7 @@ sub get_page {
sub name {
$_ = shift;
s/\n/\\n/g;
$_ = '...' . substr($_, -60) if length > 63;
$_ = '...' . substr($_, -67) if length > 70;
return $_;
}
@@ -158,6 +158,18 @@ sub test_page {
}
}
# one file, many tests
sub test_file {
my ($file, @tests) = @_;
if (open(F, '< :utf8', $file)) {
local $/ = undef;
test_page(<F>, @tests);
close(F);
} else {
warn "cannot open $file\n";
}
}
# one string, many negative tests
sub test_page_negative {
my $page = shift;
@@ -208,6 +220,17 @@ sub xpath_test {
xpath_do(sub { shift > 0; }, "No Matches\n", @_);
}
sub xpath_test_file {
my ($file, @tests) = @_;
if (open(F, '< :utf8', $file)) {
local $/ = undef;
xpath_test(<F>, @tests);
close(F);
} else {
warn "cannot open $file\n";
}
}
sub negative_xpath_test {
xpath_do(sub { shift == 0; }, "Unexpected Matches\n", @_);
}

119
t/upgrade.t Normal file
View File

@@ -0,0 +1,119 @@
# Copyright (C) 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 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/>.
require 't/test.pl';
package OddMuse;
use Test::More tests => 37;
clear_pages();
# Create a 2.2.6 wiki first.
$page = qx(perl t/oddmuse-2.2.6.pl title=Test text=Hello);
test_page($page, "Status: 302 Found");
$page = qx(perl t/oddmuse-2.2.6.pl title=Test text=Hallo);
test_page($page, "Status: 302 Found");
$page = qx(perl t/oddmuse-2.2.6.pl action=pagelock id=Test set=1 pwd=foo);
test_page($page, "created");
ok(-d "$PageDir/T", "T page directory exists");
ok(-d "$KeepDir/T", "T keep directory exists");
add_module('upgrade.pl');
ok(-f "$ModuleDir/upgrade.pl", "upgrade.pl was installed");
test_page(get_page('Test'), 'Upgrading Database', 'action=password');
test_page(get_page('action=password'), 'You are a normal user');
$page = get_page('action=upgrade pwd=foo');
test_page($page,
'page/T/Test.pg',
'page/T/Test.lck',
'keep/T/Test',
'Upgrade complete');
test_page_negative($page, 'failed',
'does not fit the pattern',
'Please remove');
ok(! -d "$PageDir/T", "T directory has disappeared");
ok(! -d "$KeepDir/T", "T keep directory has disappeared");
ok(! -d $LockDir . 'main', "Lock was released");
ok(! -f "$ModuleDir/upgrade.pl", "upgrade.pl was renamed");
test_page(get_page('action=browse id=Test revision=1'), 'Hello');
test_page(get_page('Test'), 'Hallo');
# you cannot run it again after a successful run
test_page(get_page('action=upgrade pwd=foo'),
'Invalid action parameter');
# reinstall it and run it again
add_module('upgrade.pl');
test_page(get_page('action=upgrade pwd=foo'),
'Upgrade complete');
# set up a wiki with namespaces
clear_pages();
add_module('namespaces.pl');
test_page(qx(perl t/oddmuse-2.2.6.pl title=Test text=Main%20Hello),
"Status: 302 Found");
test_page(qx(perl t/oddmuse-2.2.6.pl title=Test text=Space%20Hello ns=Space),
"Status: 302 Found");
add_module('upgrade.pl');
$page = get_page('action=upgrade pwd=foo');
test_page($page,
'<strong>Space</strong>',
'Upgrade complete');
test_page_negative($page, 'failed');
test_page(get_page('Test'), 'Main Hello');
test_page(get_page("'/Space/Test?'"), 'Space Hello');
# Install modules which use GetPageContent in their init routine.
clear_pages();
test_page(qx(perl t/oddmuse-2.2.6.pl title=$InterMap text=$InterMap),
$InterMap);
add_module('localnames.pl');
test_page(qx(perl t/oddmuse-2.2.6.pl title=$LocalNamesPage text=$LocalNamesPage),
$LocalNamesPage);
add_module('sidebar.pl');
test_page(qx(perl t/oddmuse-2.2.6.pl title=$SidebarName text=$SidebarName),
$SidebarName);
add_module('near-links.pl');
test_page(qx(perl t/oddmuse-2.2.6.pl title=$NearMap text=$NearMap),
$NearMap);
add_module('upgrade.pl');
test_page_negative(get_page('HomePage'), 'Cannot open');
test_page(get_page('action=upgrade pwd=foo'),
'Upgrade complete');

View File

@@ -54,6 +54,7 @@ sub rewrite {
my %page = split(/$FS1/, read_file($file), -1);
%section = split(/$FS2/, $page{text_default}, -1);
%text = split(/$FS3/, $section{data}, -1);
$file =~ s!/([A-Z]|other)/!/!;
$file =~ s/\.db$/.pg/ or die "Invalid page name\n";
print "Writing $file...\n";
write_page_file($file);
@@ -65,6 +66,7 @@ sub rewrite {
print "Reading refer $file...\n";
my $data = read_file($file);
$data =~ s/$FS1/$NewFS/g;
$file =~ s!/([A-Z]|other)/!/!;
$file =~ s/\.rb$/.rf/ or die "Invalid page name\n";
print "Writing $file...\n";
write_file($file, $data);
@@ -77,6 +79,7 @@ sub rewrite {
my $data = read_file($file);
my @list = split(/$FS1/, $data);
my $out = $file;
$out =~ s!/([A-Z]|other)/!/!;
$out =~ s/\.kp$// or die "Invalid keep name\n";
# We introduce a new variable $dir, here, instead of using $out,
# because $out will be part of the filename later on, and the

150
wiki.pl
View File

@@ -39,7 +39,8 @@ use vars qw($RssLicense $RssCacheHours @RcDays $TempDir $LockDir $DataDir
$KeepDir $PageDir $RcOldFile $IndexFile $BannedContent $NoEditFile $BannedHosts
$ConfigFile $FullUrl $SiteName $HomePage $LogoUrl $RcDefault $RssDir
$IndentLimit $RecentTop $RecentLink $EditAllowed $UseDiff $KeepDays $KeepMajor
$EmbedWiki $BracketText $UseConfig $UseLookup $AdminPass $EditPass $NetworkFile
$EmbedWiki $BracketText $UseConfig $UseLookup $AdminPass $EditPass
$PassHashFunction $PassSalt $NetworkFile
$BracketWiki $FreeLinks $WikiLinks $SummaryHours $FreeLinkPattern $RCName
$RunCGI $ShowEdits $LinkPattern $RssExclude $InterLinkPattern $MaxPost $UseGrep
$UrlPattern $UrlProtocols $ImageExtensions $InterSitePattern $FS $CookieName
@@ -49,7 +50,7 @@ $RssImageUrl $ReadMe $RssRights $BannedCanRead $SurgeProtection $TopLinkBar
$LanguageLimit $SurgeProtectionTime $SurgeProtectionViews $DeletedPage
%Languages $InterMap $ValidatorLink %LockOnCreation $RssStyleSheet
%CookieParameters @UserGotoBarPages $NewComment $HtmlHeaders $StyleSheetPage
$ConfigPage $ScriptName $CommentsPrefix @UploadTypes $AllNetworkFiles
$ConfigPage $ScriptName $CommentsPrefix $CommentsPattern @UploadTypes $AllNetworkFiles
$UsePathInfo $UploadAllowed $LastUpdate $PageCluster %PlainTextPages
$RssInterwikiTranslate $UseCache $Counter $ModuleDir $FullUrlPattern
$SummaryDefaultLength $FreeInterLinkPattern %InvisibleCookieParameters
@@ -95,12 +96,14 @@ $StyleSheetPage = 'css'; # Page for CSS sheet
$LogoUrl = ''; # URL for site logo ('' for no logo)
$NotFoundPg = ''; # Page for not-found links ('' for blank pg)
$NewText = "This page is empty.\n"; # New page text
$NewComment = "Add your comment here.\n"; # New comment text
$NewText = T('This page is empty.') . "\n"; # New page text
$NewComment = T('Add your comment here:') . "\n"; # New comment text
$EditAllowed = 1; # 0 = no, 1 = yes, 2 = comments pages only, 3 = comments only
$AdminPass = '' unless defined $AdminPass; # Whitespace separated passwords.
$EditPass = '' unless defined $EditPass; # Whitespace separated passwords.
$PassHashFunction = '' unless defined $PassHashFunction; # Name of the function to create hashes
$PassSalt = '' unless defined $PassSalt; # Salt will be added to any password before hashing
$BannedHosts = 'BannedHosts'; # Page for banned hosts
$BannedCanRead = 1; # 1 = banned cannot edit, 0 = banned cannot read
@@ -151,6 +154,7 @@ $TopLinkBar = 1; # 1 = add a goto bar at the top of the page
$UserGotoBar = ''; # HTML added to end of goto bar
$ValidatorLink = 0; # 1 = Link to the W3C HTML validator service
$CommentsPrefix = ''; # prefix for comment pages, eg. 'Comments_on_' to enable
$CommentsPattern = undef; # regex used to match comment pages
$HtmlHeaders = ''; # Additional stuff to put in the HTML <head> section
$IndentLimit = 20; # Maximum depth of nested lists
$LanguageLimit = 3; # Number of matches req. for each language
@@ -289,6 +293,8 @@ sub InitVariables { # Init global session variables for mod_perl!
(\$HomePage, \$RCName, \$BannedHosts, \$InterMap, \$StyleSheetPage, \$CommentsPrefix,
\$ConfigPage, \$NotFoundPg, \$RssInterwikiTranslate, \$BannedContent, \$RssExclude, );
$CommentsPrefix .= '_' if $add_space;
$CommentsPattern = "^$CommentsPrefix(.*)"
unless defined $CommentsPattern or not $CommentsPrefix;
@UserGotoBarPages = ($HomePage, $RCName) unless @UserGotoBarPages;
my @pages = sort($BannedHosts, $StyleSheetPage, $ConfigPage, $InterMap,
$RssInterwikiTranslate, $BannedContent);
@@ -465,12 +471,12 @@ sub ApplyRules {
}
Clean(AddHtmlEnvironment('p')); # if dirty block is looked at later, this will disappear
($_, pos) = ($old_, $oldpos); # restore \G (assignment order matters!)
} elsif ($bol && m/\G(\&lt;journal(\s+(\d*))?(\s+"(.*?)")?(\s+(reverse|past|future))?(\s+search\s+(.*))?\&gt;[ \t]*\n?)/cgi) {
} elsif ($bol && m/\G(\&lt;journal(\s+(\d*)(,(\d*))?)?(\s+"(.*?)")?(\s+(reverse|past|future))?(\s+search\s+(.*))?\&gt;[ \t]*\n?)/cgi) {
# <journal 10 "regexp"> includes 10 pages matching regexp
Clean(CloseHtmlEnvironments());
Dirty($1);
my ($oldpos, $old_) = (pos, $_); # remember these because of the call to PrintJournal()
PrintJournal($3, $5, $7, 0, $9); # no offset
PrintJournal($3, $5, $7, $9, 0, $11); # no offset
Clean(AddHtmlEnvironment('p')); # if dirty block is looked at later, this will disappear
($_, pos) = ($old_, $oldpos); # restore \G (assignment order matters!)
} elsif ($bol && m/\G(\&lt;rss(\s+(\d*))?\s+(.*?)\&gt;[ \t]*\n?)/cgis) {
@@ -819,7 +825,7 @@ sub GetRaw {
sub DoJournal {
print GetHeader(undef, T('Journal'));
print $q->start_div({-class=>'content'});
PrintJournal(map { GetParam($_, ''); } qw(num regexp mode offset search));
PrintJournal(map { GetParam($_, ''); } qw(num num regexp mode offset search));
print $q->end_div();
PrintFooter();
}
@@ -829,9 +835,10 @@ sub JournalSort { $b cmp $a }
sub PrintJournal {
return if $CollectingJournal; # avoid infinite loops
local $CollectingJournal = 1;
my ($num, $regexp, $mode, $offset, $search) = @_;
my ($num, $numMore, $regexp, $mode, $offset, $search) = @_;
$regexp = '^\d\d\d\d-\d\d-\d\d' unless $regexp;
$num = 10 unless $num;
$numMore = $num unless $numMore;
$offset = 0 unless $offset;
# FIXME: Should pass filtered list of pages to SearchTitleAndBody to save time?
my @pages = sort JournalSort (grep(/$regexp/, $search ? SearchTitleAndBody($search) : AllPagesList()));
@@ -860,7 +867,9 @@ sub PrintJournal {
print $q->start_div({-class=>'journal'});
my $next = $offset + PrintAllPages(1, 1, $num, @pages[$offset .. $#pages]);
print $q->end_div();
print $q->p({-class=>'more'}, ScriptLink("action=more;num=$num;regexp=$regexp;search=$search;mode=$mode;offset=$next", T('More...'), 'more')) if $pages[$next];
$regexp = UrlEncode($regexp);
$search = UrlEncode($search);
print $q->p({-class=>'more'}, ScriptLink("action=more;num=$numMore;regexp=$regexp;search=$search;mode=$mode;offset=$next", T('More...'), 'more')) if $pages[$next];
}
sub PrintAllPages {
@@ -880,17 +889,21 @@ sub PrintAllPages {
$q->h1($links ? GetPageLink($id)
: $q->a({-name=>$id}, UrlEncode(FreeToNormal($id))));
PrintPageHtml();
if ($comments and $id !~ /^$CommentsPrefix/o) {
print $q->p({-class=>'comment'},
GetPageLink($CommentsPrefix . $id,
T('Comments on this page')));
}
PrintPageCommentsLink($id, $comments);
print $q->end_div();
$n++; # pages actually printed
}
return $i;
}
sub PrintPageCommentsLink {
my ($id, $comments) = @_;
if ($comments and $CommentsPattern and $id !~ /$CommentsPattern/o) {
print $q->p({-class=>'comment'},
GetPageLink($CommentsPrefix . $id, T('Comments on this page')));
}
}
sub RSS {
return if $CollectingJournal; # avoid infinite loops when using full=1
local $CollectingJournal = 1;
@@ -1380,7 +1393,7 @@ sub BrowseResolvedPage {
print $q->redirect({-uri=>$resolved});
} elsif ($class && $class eq 'alias') { # an anchor was found instead of a page
ReBrowsePage($resolved);
} elsif (not $resolved and $NotFoundPg and $id !~ /^$CommentsPrefix/o) { # custom page-not-found message
} elsif (not $resolved and $NotFoundPg and $id !~ /$CommentsPattern/o) { # custom page-not-found message
BrowsePage($NotFoundPg);
} elsif ($resolved) { # an existing page was found
BrowsePage($resolved, GetParam('raw', 0));
@@ -1942,7 +1955,7 @@ sub RssItem {
$rss .= "<description>" . QuoteHtml($summary) . "</description>\n" if $summary;
$rss .= "<pubDate>" . $date . "</pubDate>\n";
$rss .= "<comments>" . ScriptUrl($CommentsPrefix . UrlEncode($id))
. "</comments>\n" if $CommentsPrefix and $id !~ /^$CommentsPrefix/o;
. "</comments>\n" if $CommentsPattern and $id !~ /$CommentsPattern/o;
$rss .= "<dc:contributor>" . $username . "</dc:contributor>\n" if $username;
$rss .= "<wiki:status>" . (1 == $revision ? 'new' : 'updated')
. "</wiki:status>\n";
@@ -2100,7 +2113,7 @@ sub DoRollback {
} elsif (not UserIsEditor() and my $rule = BannedContent($text)) {
print Ts('Rollback of %s would restore banned content.', $id), $rule, $q->br();
} else {
Save($id, $text, Ts('Rollback to %s', TimeToText($to)), $minor, ($Page{ip} ne $ENV{REMOTE_ADDR}));
Save($id, $text, Ts('Rollback to %s', TimeToText($to)), $minor, ($Page{ip} ne GetRemoteAddress()));
print Ts('%s rolled back', GetPageLink($id)), ($ts ? ' ' . Ts('to %s', TimeToText($to)) : ''), $q->br();
}
}
@@ -2201,6 +2214,10 @@ sub ScriptLinkDiff {
return ScriptLink($action, $text, 'diff');
}
sub GetRemoteAddress {
return $ENV{REMOTE_ADDR};
}
sub GetAuthor {
my ($host, $username) = @_;
return $username . ' ' . Ts('from %s', $host) if $username and $host;
@@ -2428,8 +2445,8 @@ sub GetFooterLinks {
my ($id, $rev) = @_;
my @elements;
if ($id and $rev ne 'history' and $rev ne 'edit') {
if ($CommentsPrefix) {
if ($id =~ /^$CommentsPrefix(.*)/o) {
if ($CommentsPattern) {
if ($id =~ /$CommentsPattern/o) {
push(@elements, GetPageLink($1, undef, 'original', T('a')));
} else {
push(@elements, GetPageLink($CommentsPrefix . $id, undef, 'comment', T('c')));
@@ -2460,11 +2477,11 @@ sub GetFooterLinks {
sub GetCommentForm {
my ($id, $rev, $comment) = @_;
if ($CommentsPrefix ne '' and $id and $rev ne 'history' and $rev ne 'edit'
and $id =~ /^$CommentsPrefix/o and UserCanEdit($id, 0, 1)) {
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
$q->p(GetHiddenValue('title', $id),
GetTextArea('aftertext', $comment ? $comment : $NewComment, 10)), $EditNote,
$q->p(GetHiddenValue('title', $id), $q->label({-for=>'aftertext', -accesskey=>T('c')}, $NewComment),
$q->br(), GetTextArea('aftertext', $comment, 10)), $EditNote,
$q->p($q->span({-class=>'username'},
$q->label({-for=>'username'}, T('Username:')), ' ',
$q->textfield(-name=>'username', -id=>'username',
@@ -2706,7 +2723,6 @@ sub OpenPage { # Sets global variables
local $/ = undef;
$Page{text} = <F>;
close F;
} elsif ($CommentsPrefix and $id =~ /^$CommentsPrefix(.*)/o) { # do nothing
}
}
$OpenPageName = $id;
@@ -2758,17 +2774,17 @@ sub GetKeptRevision { # Call after OpenPage
sub GetPageFile {
my ($id) = @_;
return $PageDir . '/' . GetPageDirectory($id) . "/$id.pg";
return "$PageDir/$id.pg";
}
sub GetKeepFile {
my ($id, $revision) = @_; die "No revision for $id" unless $revision; #FIXME
return $KeepDir . '/' . GetPageDirectory($id) . "/$id/$revision.kp";
return "$KeepDir/$id/$revision.kp";
}
sub GetKeepDir {
my $id = shift; die 'No id' unless $id; #FIXME
return $KeepDir . '/' . GetPageDirectory($id) . '/' . $id;
return "$KeepDir/$id";
}
sub GetKeepFiles {
@@ -2779,19 +2795,11 @@ sub GetKeepRevisions {
return sort {$b <=> $a} map { m/([0-9]+)\.kp$/; $1; } GetKeepFiles(shift);
}
sub GetPageDirectory {
my $id = shift;
if ($id =~ /^([a-zA-Z])/) {
return uc($1);
}
return 'other';
}
# Always call SavePage within a lock.
sub SavePage { # updating the cache will not change timestamp and revision!
ReportError(T('Cannot save a nameless page.'), '400 BAD REQUEST', 1) unless $OpenPageName;
ReportError(T('Cannot save a page without revision.'), '400 BAD REQUEST', 1) unless $Page{revision};
CreatePageDir($PageDir, $OpenPageName);
CreateDir($PageDir);
WriteStringToFile(GetPageFile($OpenPageName), EncodePage(%Page));
}
@@ -2802,7 +2810,8 @@ sub SaveKeepFile {
delete $Page{'diff-major'};
delete $Page{'diff-minor'};
$Page{'keep-ts'} = $Now; # expire only $KeepDays from $Now!
CreateKeepDir($KeepDir, $OpenPageName);
CreateDir($KeepDir);
CreateDir("$KeepDir/$OpenPageName");
WriteStringToFile(GetKeepFile($OpenPageName, $Page{revision}), EncodePage(%Page));
}
@@ -2877,21 +2886,9 @@ sub CreateDir {
or ReportError(Ts('Cannot create %s', $newdir) . ": $!", '500 INTERNAL SERVER ERROR');
}
sub CreatePageDir {
my ($dir, $id) = @_;
CreateDir($dir);
CreateDir($dir . '/' . GetPageDirectory($id));
}
sub CreateKeepDir {
my ($dir, $id) = @_;
CreatePageDir($dir, $id);
CreateDir($dir . '/' . GetPageDirectory($id) . '/' . $id);
}
sub GetLockedPageFile {
my $id = shift;
return $PageDir . '/' . GetPageDirectory($id) . "/$id.lck";
return "$PageDir/$id.lck";
}
sub RequestLockDir {
@@ -3017,13 +3014,13 @@ sub GetHiddenValue {
sub GetRemoteHost { # when testing, these variables are undefined.
my $rhost = $ENV{REMOTE_HOST}; # tests are written to avoid -w warnings.
if (not $rhost and $UseLookup and $ENV{REMOTE_ADDR}) {
if (not $rhost and $UseLookup and GetRemoteAddress()) {
# Catch errors (including bad input) without aborting the script
eval 'use Socket; my $iaddr = inet_aton($ENV{REMOTE_ADDR});'
eval 'use Socket; my $iaddr = inet_aton(GetRemoteAddress());'
. '$rhost = gethostbyaddr($iaddr, AF_INET) if $iaddr;';
}
if (not $rhost) {
$rhost = $ENV{REMOTE_ADDR};
$rhost = GetRemoteAddress();
}
return $rhost;
}
@@ -3032,11 +3029,9 @@ sub FreeToNormal { # trim all spaces and convert them to underlines
my $id = shift;
return '' unless $id;
$id =~ s/ /_/g;
if (index($id, '_') > -1) { # Quick check for any space/underscores
$id =~ s/__+/_/g;
$id =~ s/^_//;
$id =~ s/_$//;
}
$id =~ s/__+/_/g;
$id =~ s/^_//;
$id =~ s/_$//;
return UnquoteHtml($id);
}
@@ -3221,7 +3216,7 @@ sub UserCanEdit {
return 1 if UserIsEditor();
return 0 if !$EditAllowed or -f $NoEditFile;
return 0 if $editing and UserIsBanned(); # this call is more expensive
return 0 if $EditAllowed >= 2 and (not $CommentsPrefix or $id !~ /^$CommentsPrefix/o);
return 0 if $EditAllowed >= 2 and (not $CommentsPattern or $id !~ /$CommentsPattern/o);
return 1 if $EditAllowed >= 3 and ($comment or (GetParam('aftertext', '') and not GetParam('text', '')));
return 0 if $EditAllowed >= 3;
return 1;
@@ -3230,7 +3225,7 @@ sub UserCanEdit {
sub UserIsBanned {
return 0 if GetParam('action', '') eq 'password'; # login is always ok
my ($host, $ip);
$ip = $ENV{'REMOTE_ADDR'};
$ip = GetRemoteAddress();
$host = GetRemoteHost();
foreach (split(/\n/, GetPageContent($BannedHosts))) {
if (/^\s*([^#]\S+)/) { # all lines except empty lines and comments, trim whitespace
@@ -3243,19 +3238,22 @@ sub UserIsBanned {
}
sub UserIsAdmin {
return 0 if $AdminPass eq '';
my $pwd = GetParam('pwd', '');
foreach (split(/\s+/, $AdminPass)) {
return 1 if $pwd eq $_;
}
return 0;
return UserHasPassword(GetParam('pwd', ''), $AdminPass);
}
sub UserIsEditor {
return 1 if UserIsAdmin(); # Admin includes editor
return 0 if $EditPass eq '';
my $pwd = GetParam('pwd', ''); # Used for both passwords
foreach (split(/\s+/, $EditPass)) {
return UserHasPassword(GetParam('pwd', ''), $EditPass);
}
sub UserHasPassword {
my ($pwd, $pass) = @_;
return 0 if not $pass;
if ($PassHashFunction ne '') {
no strict 'refs';
$pwd = &$PassHashFunction($pwd . $PassSalt);
}
foreach (split(/\s+/, $pass)) {
return 1 if $pwd eq $_;
}
return 0;
@@ -3338,7 +3336,7 @@ sub AllPagesList {
if (not $refresh and -f $IndexFile) {
my ($status, $rawIndex) = ReadFile($IndexFile); # not fatal
if ($status) {
%IndexHash = split(/\s+/, $rawIndex);
%IndexHash = split(/ /, $rawIndex);
@IndexList = sort(keys %IndexHash);
return @IndexList;
}
@@ -3348,7 +3346,7 @@ sub AllPagesList {
%IndexHash = ();
# If file exists and cannot be changed, error!
my $locked = RequestLockDir('index', undef, undef, -f $IndexFile);
foreach (bsd_glob("$PageDir/*/*.pg"), bsd_glob("$PageDir/*/.*.pg")) {
foreach (bsd_glob("$PageDir/*.pg"), bsd_glob("$PageDir/.*.pg")) {
next unless m|/.*/(.+)\.pg$|;
my $id = $1;
utf8::decode($id);
@@ -3570,7 +3568,7 @@ sub Replace {
if (eval "s{$from}{$to}gi") { # allows use of backreferences
push (@result, $id);
Save($id, $_, $from . ' -> ' . $to, 1,
($Page{ip} ne $ENV{REMOTE_ADDR}));
($Page{ip} ne GetRemoteAddress()));
}
}
ReleaseLock();
@@ -3649,7 +3647,7 @@ sub DoPost {
if ($oldrev) { # the first author (no old revision) is not considered to be "new"
# prefer usernames for potential new author detection
$newAuthor = 1 if not $Page{username} or $Page{username} ne GetParam('username', '');
$newAuthor = 1 if not $ENV{REMOTE_ADDR} or not $Page{ip} or $ENV{REMOTE_ADDR} ne $Page{ip};
$newAuthor = 1 if not GetRemoteAddress() or not $Page{ip} or GetRemoteAddress() ne $Page{ip};
}
my $oldtime = $Page{ts};
my $myoldtime = GetParam('oldtime', ''); # maybe empty!
@@ -3711,7 +3709,7 @@ sub AddComment {
my $author = GetParam('username', T('Anonymous'));
my $homepage = GetParam('homepage', '');
$homepage = 'http://' . $homepage
if $homepage and not substr($homepage,0,7) eq 'http://';
if $homepage and $homepage !~ /^($UrlProtocols):/;
$author = "[$homepage $author]" if $homepage;
$string .= "\n----\n\n" if $string and $string ne "\n";
$string .= $comment . "\n\n"
@@ -3742,7 +3740,7 @@ sub Save { # call within lock, with opened page
$Page{revision} = $revision;
$Page{summary} = $summary;
$Page{username} = $user;
$Page{ip} = $ENV{REMOTE_ADDR};
$Page{ip} = GetRemoteAddress();
$Page{host} = $host;
$Page{minor} = $minor;
$Page{text} = $new;
@@ -3967,7 +3965,7 @@ sub DoDebug {
sub DoSurgeProtection {
return unless $SurgeProtection;
my $name = GetParam('username','');
$name = $ENV{'REMOTE_ADDR'} if not $name and $SurgeProtection;
$name = GetRemoteAddress() if not $name and $SurgeProtection;
return unless $name;
ReadRecentVisitors();
AddRecentVisitor($name);