Compare commits

...

48 Commits

Author SHA1 Message Date
Alex Schroeder
9238e42cfd banned-regexps: updated copyright year 2018-09-04 09:54:04 +02:00
Alex Schroeder
836f3abecb Fixed bug in banned-regexps
BannedRegexps needs to check for BannedContent before it does
anything. BannedContent checks the URLs, and so BannedRegexps wants to
check everything except for the URLs. The order is important: the old
code used to remove the URLs before handing the text to BannedContent,
so it never found a thing. Changing the order fixed this.
2018-09-04 09:45:47 +02:00
Alex Schroeder
d669f783bb t/ban: test for homepage field 2018-09-04 09:37:49 +02:00
Alex Schroeder
c18279ca4c alex-2018.css: bigger fonts, wrapping pre 2018-08-24 11:29:23 +02:00
Alex Schroeder
bbd280abf9 ban-mixed-script: better fix for encoding issue 2018-08-14 15:01:01 +02:00
Alex Schroeder
c15e6041ef ban-mixed-scripts: silence meta.t warning 2018-08-14 11:57:26 +02:00
Alex Schroeder
e4c84d9b3a ban-mixed-scripts 2018-08-12 19:43:58 +02:00
Alex Schroeder
a49afca3f7 gopher-server: fix regular expressions for links
The regular expression now also matches mardown style external links.
Now using /x and named groups. This makes the entire thing much more
readable.
2018-08-10 20:45:11 +02:00
Alex Schroeder
883f07f926 Fix URL in Makefile 2018-08-06 12:07:18 +02:00
Alex Schroeder
b5d182582f Fix URLs in AddModuleDescription 2018-08-06 11:19:45 +02:00
Alex Schroeder
fe26471abb wiki.css: update for All Modules
Although I must say, the more I see of Div Foo, the less I like it. I
can't wrap a div around the <titles ...> rule and so now I use the
following on the All_Modules page:

<list>
<titles ...>

This introduces an empty div before the div I care about and the CSS
then does .foo_list + .journal etc.
2018-07-25 17:21:01 +02:00
Alex Schroeder
96a3cbbb22 tags: fix double quote bug
The old code removed double quotes before passing the search string on
to SearchString. Thus, "a b" would become a search for "a" and "b".
2018-07-24 12:56:08 +02:00
Alex Schroeder
d303e5e955 journal-rss: switch to full page feed always 2018-07-19 16:18:17 +02:00
Alex Schroeder
dd6198ffca journal-rss.t: simplify tests accordingly
Once the journal RSS just uses the latest edits, we don't have to care
about all the stuff involving major and minor versions.
2018-07-19 16:15:29 +02:00
Alex Schroeder
ae646290ed journal-rss: fix handling of new offset parameter 2018-07-19 16:02:24 +02:00
Alex Schroeder
1a74dc9605 journal-rss: don't ignore minor changes
If a page was created by just minor edits, then it is visible in a
journal. We shouldn't skip it from the journal RSS. Furthermore:
what's the point in showing the text from the last major version? Show
the latest version!
2018-07-19 15:52:17 +02:00
Alex Schroeder
0c19bd13cd tags.t: some more tests for filtering RC 2018-07-19 15:38:14 +02:00
Alex Schroeder
42bb4888a2 rss-journal: support feed pagination 2018-07-19 14:36:27 +02:00
Alex Schroeder
9a5e44aba3 Make feed pagination possible for journal-rss.pl
This works without changes to journal-rss.pl itself. All we do is make
sure that RcSelfAction, RcPreviousAction, and RcLastAction don't get
the action (rc vs. rss) from their arguments but determine it by
looking at the script parameters. Since journal-rss simply uses the
journal action, this will then get picked up correctly.
2018-07-19 13:04:31 +02:00
Alex Schroeder
78b5dd3737 rss.t: add more tests for RSS pagination 2018-07-19 11:43:07 +02:00
Alex Schroeder
b8717c4084 rss.t: add tests for RSS pagination 2018-07-19 11:39:06 +02:00
Alex Schroeder
42f4816ae5 gopher-server: surge protection disabled for tests
Make sure the gopher server reads the config file and skips surge
protection if $SurgeProtection is set to 0. The tests do this. If we
don't do this, tests start failing after a while.
2018-07-19 00:10:24 +02:00
Alex Schroeder
ff8d671787 wiki: first draft at implementing RFC 5005
We can easily provide the latest and the previous feed URL in addition
to the current (self) URL.
2018-07-18 23:53:18 +02:00
Alex Schroeder
e16cda46c1 gopher-server: rewrote surge protection
Instead of calling DoSurgeProtection as part of process_request and
faking username and overwriting ReportError, I've moved the essential
code to allow_deny_hook which seems to be the place dedicated to this
sort of code. Sadly, push(@{$self->{server}->{deny}}, $peeraddr) seems
to have no effect. When the next request comes around, the deny list
is empty again. I guess we would have to persist this somehow. For
now, we're simply using allow_deny_hook and that seems to be just as
well.
2018-07-16 21:34:11 +02:00
Alex Schroeder
431e792e52 gopher-server: add surge protection 2018-07-16 09:37:39 +02:00
Alex Schroeder
c9f3060e42 release: accept non-numeric versions like 2.3.11a 2018-07-13 00:52:35 +02:00
Alex Schroeder
ec4987e817 rc.t: fix copyright year 2018-07-13 00:20:09 +02:00
Alex Schroeder
655d3b47c6 checkbox.t: new tests for checkbox.pl 2018-06-28 10:31:36 +02:00
Alex Schroeder
8b85c6e9dd Save Anonymous username without translation
When saving, write "Anonymous" without translating it. When displaying
"Anonymous", translate it.
2018-06-28 00:51:46 +02:00
Alex Schroeder
ef35e4eda6 rc2mail: update timestamp in any case 2018-06-28 00:49:46 +02:00
Alex Schroeder
7bb1ca239b search-inclusion: new test and fix
I must have misunderstood how 'local' works because it turns out that
you need to move it out of the block for it to work as intended.
2018-06-28 00:49:38 +02:00
Alex Schroeder
a7625b35f4 README: mention problem with non-existing $DataDir 2018-06-28 00:49:23 +02:00
Alex Schroeder
1d239aa9a6 latex.pl: don't read and write; rename instead
Using ReadFileOrDie and WriteStringToFile on a PNG file borks it
because we're decoding and encoding the file as UTF-8. Let's use
Rename instead.
2018-06-28 00:49:10 +02:00
Alex Schroeder
3f1e5caaad bootstrap.js: bootstrap integration example 2018-06-28 00:48:58 +02:00
Alex Schroeder
bdbd6c8ba7 wiki: allow mixing of search and match
Move search up in the list of actions so that it takes precedence over
match if both parameters are provided and add matching to the Filtered
function so that it is taken into account when searching.
2018-06-28 00:48:43 +02:00
Alex Schroeder
1a898ee589 wiki: anonymize rc.log entries even if they are not moved 2018-06-28 00:34:08 +02:00
Alex Schroeder
9609adb575 gopher-server: own implementation of NormaltoFree
We want to avoid turning & into &amp; when printing a menu, so we don't
want to call NormalToFree because it callse QuoteHtml. Replaced all
calls with normal_to_free which only translates underscores to spaces.
2018-06-28 00:31:29 +02:00
Alex Schroeder
34c10202b0 wiki.pl: anonymous entries report as Anonymous
Two changes: First, when moving entries from rc.log to oldrc.log, make
sure we use the string "Anonymous" for the host instead of translating
it. Second, when printing the author link using GetAuthorLink, don't
call ColorCode for anonymous authors if the IP number has been replaced
by the string "Anonymous". In this case, just return "Anonymous".
Otherwise we're getting the color code of the string "Anonymous" which
is confusing.
2018-06-28 00:31:29 +02:00
Alex Schroeder
1457fad242 wiki.pl: update copyright year 2018-06-28 00:31:29 +02:00
Alex Schroeder
ce355499c7 oddtrans: fix missing . in @INC 2018-06-28 00:31:29 +02:00
Alex Schroeder
2bb3d2f967 strip-hosts: also strip ip keys 2018-06-28 00:31:29 +02:00
Alex Schroeder
a301ab5e1c strip-host: fix data corruption 2018-06-28 00:31:29 +02:00
Alex Schroeder
d74d29fa13 Anonymize IPs that we do show using a color code. 2018-06-28 00:31:29 +02:00
Alex Schroeder
96c54d9d66 New: strip-host.pl for DSGVO 2018-06-28 00:31:29 +02:00
Alex Schroeder
9a0700cac1 Getting ready for #DSGVO
Removing IP number ("host") from page files and keep files. The IP
numbers remain in the RC file. GetAuthor and GetAuthorLink now take
username as a first argument and host as an optional second argument.
Sorry for the incompatible change.
2018-06-28 00:31:29 +02:00
Alex Schroeder
864890c168 Added logo previews 2018-06-28 00:31:29 +02:00
Alex Schroeder
a93873d1d4 Logo updates 2018-06-28 00:31:29 +02:00
Alex Schroeder
b0a8fa2141 Logo files
Added old logo by Murray Altheim and new logo by Falky.
2018-06-28 00:31:29 +02:00
51 changed files with 1119 additions and 213 deletions

View File

@@ -25,7 +25,7 @@ release:
perl stuff/release ~/oddmuse.org/releases 2.3.3
build/wiki.pl: wiki.pl
perl -lne "s/(\\\$$q->a\(\{-href=>'https:\/\/www.oddmuse.org\/'\}, 'Oddmuse'\))/\\\$$q->a({-href=>'https:\/\/git.savannah.gnu.org\/cgit\/oddmuse.git\/tag\/?id=$(VERSION_NO)'}, 'wiki.pl') . ' ($(VERSION_NO)), see ' . \$$1/; print" < $< > $@
perl -lne "s/(\\\$$q->a\(\{-href=>'https:\/\/www.oddmuse.org\/'\}, 'Oddmuse'\))/\\\$$q->a({-href=>'https:\/\/alexschroeder.ch\/cgit\/oddmuse\/tag\/?id=$(VERSION_NO)'}, 'wiki.pl') . ' ($(VERSION_NO)), see ' . \$$1/; print" < $< > $@
build/%-utf8.pl: modules/translations/%-utf8.pl
perl -lne "s/(AddModuleDescription\('[^']+', '[^']+')\)/\$$1, 'translations\/', '$(VERSION_NO)')/; print" < $< > $@

View File

@@ -156,8 +156,11 @@ WikiDataDir=$HOME/oddmuse perl server.pl daemon
}}}
This makes the server available on {{{http://localhost:3000/wiki}}}.
Make sure you create the directory before starting the server!
If you don't, you'll get a strange error:
`STDERR: : No such file or directory at ... perl5/Mojolicious/Plugin/CGI.pm`.
If it works for you, feel free to upgrade to Hypnotoad.
If it works, feel free to upgrade to Hypnotoad.
{{{
WikiDataDir=$HOME/oddmuse hypnotoad server.pl

93
contrib/emacswiki/bootstrap.js vendored Normal file
View File

@@ -0,0 +1,93 @@
// Public Domain
// initial version by Alex Schroeder <alex@gnu.org>
// with many improvements by Evgkeni Sampelnikof
$(function(){
// add fancy classes
$('div.header' ).addClass('container');
$('div.wrapper').addClass('container');
$('div.footer' ).addClass('container');
$('div.footer > .navbar' ).remove();
$('.message > p' ).addClass('alert');
$('img.portrait').addClass('img-polaroid');
$('input:text').addClass('input-medium search-query');
$('textarea').addClass('span12');
$('input:submit').addClass('btn');
$('.download a').addClass('btn btn-success');
$('.footer .gotobar').remove();
$('.footer br').first().remove();
var $gotobar = $('.gotobar')
.after($('<div>').attr('class','navbar')
.append($('<div>').attr('class','navbar-inner')
.append($('<ul>').attr('class', 'nav'))));
var $id = $('h1 a').first().text();
var $list = $('.nav')
.append($('<li>')
.append($('<a>').attr('class', 'brand').attr('href', 'http://www.emacswiki.org/')
.append('Emacs Wiki')));
$('.gotobar a').each(function() {
var $item = $('<li>');
$(this).appendTo($item);
$item.appendTo($list);
if ($(this).text() == $id) {
$item.addClass('active');
}
});
$gotobar.remove();
// search without labels, without button, without language field
$('form.search input[type=submit]').remove();
$('form.search label').remove();
$('form.search input#searchlang').remove();
$('form.search')
.css({'float': 'right',
'margin-top': '10px'});
$('.navbar').append($('form.search'));
// add button style to some links
$('.edit.bar a').addClass('btn');
// add color to Talk button for a non-existing page
$('a.btn.comment.edit').addClass('btn-warning');
// move article link and talk link below title
var $link = $('a.original').add('a.comment');
if ($link) {
$('.header h1').after($('<p>').append($link));
}
// toc
if ($('title').text() == "EmacsWiki: Wikified Emacs Lisp List") {
$('.content').addClass('ell');
}
// tables
$('table').addClass('table');
// minor edit checkbox
$('input[type=checkbox]').addClass('checkbox');
$('input[type=checkbox]').parent().addClass('checkbox');
// clean up admin page
$('li a.clear').parent().remove();
$('li a.index').parent().remove();
$('a[href="http://creativecommons.org/licenses/GPL/2.0/"]')
.parent()
.css({'margin-right': '120px',
'opacity': 0.3,
'padding-top': '1em'});
$('.footer .bar')
.after('<hr />');
var footer_wrapper = $('<div/>')
.addClass('footer_wrapper');
var footer = $('.footer.container');
footer.after(footer_wrapper);
footer_wrapper.append(footer);
var logo_image = $('<img />')
.attr('src', 'http://emacswiki.org/ew_logo.png');
$('.header .navbar .brand').html(logo_image);
});

View File

@@ -2,9 +2,9 @@
html{ text-align: center; }
body, rss {
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
font-family: "DejaVu Serif", Palatino, serif;
font-style: normal;
font-size: 14pt;
font-size: 16pt;
padding: 1em 3em;
max-width: 72ex;
display: inline-block;
@@ -13,10 +13,31 @@ body, rss {
background-color: #fff;
}
@media only screen and (max-device-width: 480px) {
@media only screen and (max-device-width: 600px) {
body {
padding: 1ex;
}
textarea {
font-size: inherit;
}
}
/* code */
textarea, pre, code, tt {
font-family: "DejaVu Mono", "Andale Mono", Monaco, "Courier New", Courier, monospace;
}
pre, code, tt {
font-size: 12pt; /* fits 80ex */
}
pre {
overflow:hidden;
white-space: pre-wrap; /* CSS 3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
@import url(file:///home/alex/alexschroeder.ch/css/alex-2017.css) print;
@@ -191,22 +212,6 @@ input[type="text"] {
line-height: 125%;
}
/* code */
textarea, pre, code, tt {
font-family: "Andale Mono", Monaco, "Courier New", Courier, monospace, "Symbola";
font-size: 75%; /* fits 80ex */
}
pre {
overflow:hidden;
white-space: pre-wrap; /* CSS 3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
/* styling for divs that will be invisible when printing
when printing. */
@@ -223,6 +228,9 @@ div.header, div.footer, div.near, div.definition, p.comment, a.tag {
div.footer form.search {
display: none;
}
/* Recent Changes */
div.rc {
overflow: hidden;
}
@@ -233,12 +241,57 @@ div.rc li strong, table.history strong, strong.description {
font-family: inherit;
font-weight: inherit;
}
.red {
background: red;
color: red;
}
.orange {
background: orange;
color: orange;
}
.yellow {
background: yellow;
color: yellow;
}
.green {
background: green;
color: green;
}
.blue {
background: blue;
color: blue;
}
.indigo {
background: indigo;
color: indigo;
}
.violet {
background: violet;
color: violet;
}
.white {
background: white;
color: white;
}
.ip-code {
border: 1px solid #666;
}
/* Diff */
div.diff {
padding-left: 5%;
padding-right: 5%;
font-size: 12pt;
color: #000;
}
div.old {
background-color: #ffffaf;

View File

@@ -172,6 +172,50 @@ span.bar a {
padding: 1ex 0;
}
.red {
background: red;
color: red;
}
.orange {
background: orange;
color: orange;
}
.yellow {
background: yellow;
color: yellow;
}
.green {
background: green;
color: green;
}
.blue {
background: blue;
color: blue;
}
.indigo {
background: indigo;
color: indigo;
}
.violet {
background: violet;
color: violet;
}
.white {
background: white;
color: white;
}
.ip-code {
border: 1px solid #666;
}
hr {
border: none;
color: black;
@@ -219,3 +263,19 @@ code {
background: #eee;
white-space: pre-wrap;
}
/* for https://oddmuse.org/wiki/All_Modules */
.foo_list + .journal h1 {
font: inherit;
border: none;
display: list-item;
margin-top: 0;
margin-left: 1em;
}
.foo_list + .journal a {
font: inherit;
border: none;
text-decoration: none;
color: #a00;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

15
logo/falky/README.md Normal file
View File

@@ -0,0 +1,15 @@
Falky schreibt:
Zum Thema Rechte am Logo möchte ich erstmal sagen, dass es mir primär
darum ging den Auftritt von oddmuse.org aufzuhübschen. Also du/ihr
sollt durch das Logo unterstützt werden, nicht eingeschränkt.
Welche Lizenz das ab besten leisten, da hab ich keinen Schimmer.
Schön wäre, wenn irgendwo ein verweis auf "Falky [falky.de]"
auftaucht, der soll aber nicht bei jeder Verwendung auftauchen,
sondern irgendwo 'auffindbar' an 'zentraler' Stelle - halt irgendwo
auf der Homepage, dass ich darauf verweisen kann und dass niemand was
anderes behaupten kann. Das wäre es schon. Und ja, falls jemand das
logo für ein anderes Projekt verwenden möchte, kann man ja höflich
nochmal fragen oder so.

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-61 -1 177 62">
<rect fill="#FFF" x="-60" y="0" width="175" height="60"/>
<text style="font:400 6px Neris Black" fill="#000" x="2" y="18">powered by</text>
<text style="font:400 16px Neris Thin" fill="#000" x="2" y="34">Oddmuse<tspan style="font-family:Neris Black">.org</tspan></text>
<g id="new" transform="translate(-30,30) scale(.18,.18)" >
<circle fill="#FFF" stroke="#000" stroke-width="3" cx="0" cy="0" r="160.5"/>
<g id="new" transform="rotate(207.2)" >
<path fill="#000" d="m42 -77 q24 0 38 20 c14 -1 42 -18 52 -29 c-1 50 -26 68 -44 70 c0 90 -44 141 -88 141 c-44 0 -88 -51 -88 -141 c-18 -2 -43 -20 -44 -70 c10 11 38 28 52 29 q14 -20 38 -20z"/>
<path fill="#000" transform="translate(0,60) rotate(-14)" d="m -22 46q1 50 22 50q21 0 22 -50z"/>
<path stroke="#FFF" stroke-width="4" fill="#000" d="m0 17 c 30 0 36 -2 46 -2 c 18 0 32 16 32 51 c 0 34 -12 50 -32 50 c-16 0 -26 -5 -46 -5 c-20 0 -30 5 -46 5 c-20 0 -33 -18 -32 -50 c0 -36 16 -52 32 -51 c10 0 16 2 46 2z"/>
<circle fill="#FFF" cx="44" cy="49" r="12"/> <circle fill="#FFF" cx="-44" cy="49" r="12"/>
<circle fill="#FFF" cx="42" cy="-31" r="40"/> <circle fill="#000" cx="42" cy="-31" r="20.5"/>
<circle fill="#FFF" cx="-42" cy="-31" r="40"/> <path fill="#000" transform="translate(-42,-31) rotate(55)" d="m-7 7 v20 q7 2 14 0 v-20 h20 q2 -7 0 -14 h-20 v-20 q -7 -2 -14 0 v20 h-20 q-2 7 0 14z"/>
</g></g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
logo/falky/oddmuse.icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-100 -100 200 200" >
<rect fill="#D88" x="-100" y="-100" width="200" height="200"/>
<g id="new" transform="rotate(180) translate(0,-19)" >
<path fill="#A00" d="m42 -77 q18 0 33 14 q 12 -6 23 -16 q-2 25 -10 43 c0 110 -44 155 -88 155 c-44 0 -88 -44 -88 -155 q-8 -18 -10 -43 q11 10 23 16 q15 -14 33 -14z"/>
<path stroke="#FFF" stroke-width="6" fill="#A00" d="m0 16 c30 0 38 -2 48 -2 c17 0 33 16 33 52 c0 34 -12 52 -31 52 c-15 0 -30 -7 -50 -7 c-20 0 -35 7 -50 7 c-19 0 -31 -18 -31 -52 c0 -36 16 -52 33 -52 c10 0 18 2 48 2z"/>
<circle fill="#FFF" cx="45" cy="49" r="12"/> <circle fill="#FFF" cx="-45" cy="49" r="12"/>
<circle fill="#FFF" cx="42" cy="-31" r="40"/> <circle fill="#000" cx="42" cy="-31" r="20.5"/>
<circle fill="#FFF" cx="-42" cy="-31" r="40"/> <path fill="#666" transform="translate(-42,-31) rotate(55)" d="m-7 7 v20 q7 2 14 0 v-20 h20 q2 -7 0 -14 h-20 v-20 q -7 -2 -14 0 v20 h-20 q-2 7 0 14z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
logo/falky/oddmuse.logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-162 -162 324 324">
<circle fill="#FEE" stroke="#000" stroke-width="3" cx="0" cy="0" r="160.5"/>
<g id="new" transform="rotate(210)" >
<path fill="#A00" d="m42 -77 q24 0 38 20 c14 -1 42 -18 52 -29 c-1 50 -26 68 -44 70 c0 90 -44 141 -88 141 c-44 0 -88 -51 -88 -141 c-18 -2 -43 -20 -44 -70 c10 11 38 28 52 29 q14 -20 38 -20z"/>
<path fill="#A00" transform="translate(0,60) rotate(-14)" d="m -22 46q1 50 22 50q21 0 22 -50z"/>
<path stroke="#FFF" stroke-width="4" fill="#D88" d="m0 17 c 30 0 36 -2 46 -2 c 18 0 32 16 32 51 c 0 34 -12 50 -32 50 c-16 0 -26 -5 -46 -5 c-20 0 -30 5 -46 5 c-20 0 -33 -18 -32 -50 c0 -36 16 -52 32 -51 c10 0 16 2 46 2z"/>
<circle fill="#FFF" cx="44" cy="49" r="12"/> <circle fill="#FFF" cx="-44" cy="49" r="12"/>
<circle fill="#FFF" cx="42" cy="-31" r="40"/> <circle fill="#000" cx="42" cy="-31" r="20.5"/>
<circle fill="#FFF" cx="-42" cy="-31" r="40"/> <path fill="#666" transform="translate(-42,-31) rotate(55)" d="m-7 7 v20 q7 2 14 0 v-20 h20 q2 -7 0 -14 h-20 v-20 q -7 -2 -14 0 v20 h-20 q-2 7 0 14z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-162 -162 324 324">
<circle fill="#FFF" stroke="#000" stroke-width="3" cx="0" cy="0" r="160.5"/>
<text style="font:400 27px Neris Black" fill="#000" text-anchor="middle" x="-5" y="137">Oddmuse</text>
<g id="new" transform="rotate(210)" >
<path fill="#000" d="m42 -77 q24 0 38 20 c14 -1 42 -18 52 -29 c-1 50 -26 68 -44 70 c0 90 -44 141 -88 141 c-44 0 -88 -51 -88 -141 c-18 -2 -43 -20 -44 -70 c10 11 38 28 52 29 q14 -20 38 -20z"/>
<path fill="#000" transform="translate(0,60) rotate(-14)" d="m -22 46q1 50 22 50q21 0 22 -50z"/>
<path stroke="#FFF" stroke-width="4" fill="#000" d="m0 17 c 30 0 36 -2 46 -2 c 18 0 32 16 32 51 c 0 34 -12 50 -32 50 c-16 0 -26 -5 -46 -5 c-20 0 -30 5 -46 5 c-20 0 -33 -18 -32 -50 c0 -36 16 -52 32 -51 c10 0 16 2 46 2z"/>
<circle fill="#FFF" cx="44" cy="49" r="12"/> <circle fill="#FFF" cx="-44" cy="49" r="12"/>
<circle fill="#FFF" cx="42" cy="-31" r="40"/> <circle fill="#000" cx="42" cy="-31" r="20.5"/>
<circle fill="#FFF" cx="-42" cy="-31" r="40"/> <path fill="#000" transform="translate(-42,-31) rotate(55)" d="m-7 7 v20 q7 2 14 0 v-20 h20 q2 -7 0 -14 h-20 v-20 q -7 -2 -14 0 v20 h-20 q-2 7 0 14z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
logo/falky/testmonial.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

4
logo/murray/README.md Normal file
View File

@@ -0,0 +1,4 @@
The logo is licensed under the [GNU Free Documentation
License](http://www.emacswiki.org/FDL) as well as the [CC BY
2.0](http://creativecommons.org/licenses/by/2.0/) license. The author
is [Murray Altheim](http://www.altheim.com/murray/).

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="0.41"
sodipodi:docname="oddmuse-logo.svg"
sodipodi:docbase="/Users/alex/Pictures/Oddmuse"
height="1000pt"
width="1000pt"
sodipodi:version="0.32"
id="svg1">
<metadata
id="metadata32">
<rdf:RDF
id="RDF34">
<cc:Work
id="Work36"
rdf:about="">
<dc:format
id="format38">image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage"
id="type40" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3" />
<sodipodi:namedview
inkscape:current-layer="svg1"
inkscape:window-y="22"
inkscape:window-x="0"
inkscape:window-height="535"
inkscape:window-width="640"
inkscape:cy="625.00000"
inkscape:cx="625.00000"
inkscape:zoom="0.28960000"
id="base" />
<g
transform="translate(-155.7935,22.13440)"
id="g176">
<path
transform="matrix(1.021740,0.000000,0.000000,1.021740,-299.4160,85.75640)"
sodipodi:ry="148.93437"
sodipodi:rx="142.30524"
sodipodi:cy="475.27606"
sodipodi:cx="1066.8474"
d="M 1209.1526 475.27606 A 142.30524 148.93437 0 1 1 924.54218,475.27606 A 142.30524 148.93437 0 1 1 1209.1526 475.27606 z"
id="path166"
style="fill:#ffde00;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#000000;stroke-width:22.021299;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000"
sodipodi:type="arc" />
<path
transform="matrix(0.591973,0.000000,0.000000,0.523224,159.0800,309.0390)"
sodipodi:ry="148.93437"
sodipodi:rx="142.30524"
sodipodi:cy="475.27606"
sodipodi:cx="1066.8474"
d="M 1209.1526 475.27606 A 142.30524 148.93437 0 1 1 924.54218,475.27606 A 142.30524 148.93437 0 1 1 1209.1526 475.27606 z"
id="path168"
style="fill:#ffde00;fill-opacity:1.0000000;fill-rule:evenodd;stroke:#000000;stroke-width:38.609402;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000"
sodipodi:type="arc" />
<rect
transform="translate(-1.416020,0.000000)"
y="561.16870"
x="694.39026"
height="88.691116"
width="195.30150"
id="rect170"
style="font-size:12.000000;fill:#ffde00;fill-opacity:1.0000000;fill-rule:evenodd;stroke-width:1.0000000pt" />
<g
id="g173">
<path
transform="translate(185.0000,-158.7500)"
sodipodi:ry="31.250000"
sodipodi:rx="25.000000"
sodipodi:cy="783.75000"
sodipodi:cx="565.00000"
d="M 590.00000 783.75000 A 25.000000 31.250000 0 1 1 540.00000,783.75000 A 25.000000 31.250000 0 1 1 590.00000 783.75000 z"
id="path171"
style="fill:#000000;fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.0000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000"
sodipodi:type="arc" />
<path
transform="translate(266.2500,-158.7500)"
sodipodi:ry="31.250000"
sodipodi:rx="25.000000"
sodipodi:cy="783.75000"
sodipodi:cx="565.00000"
d="M 590.00000 783.75000 A 25.000000 31.250000 0 1 1 540.00000,783.75000 A 25.000000 31.250000 0 1 1 590.00000 783.75000 z"
id="path172"
style="fill:#000000;fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.0000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000"
sodipodi:type="arc" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,72 @@
# Copyright (C) 2018 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/>.
=encoding utf8
=head1 Mixed Scripts
This module disallows ordinary users from posting words that consist of multiple
scripts. Stuff like this: "It's diffіcult to find knowledgeable people on this
topic, but youu sound like you know wgat you're takіng аboսt!" Did you notice
the confusable characters? The sentence contains the following:
ARMENIAN SMALL LETTER SEH
CYRILLIC SMALL LETTER A
CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
SMALL ROMAN NUMERAL FIFTY
=cut
use strict;
use v5.10;
use Unicode::UCD qw(charprop);
AddModuleDescription('ban-mixed-scripts.pl', 'Ban Mixed Scripts Extension');
*OldBanMixedScriptsBannedContent = \&BannedContent;
*BannedContent = \&NewBanMixedScriptsBannedContent;
sub NewBanMixedScriptsBannedContent {
my $rule = OldBanMixedScriptsBannedContent(@_);
$rule ||= BanMixedScript(@_);
return $rule;
}
sub BanMixedScript {
my $str = shift;
my @words = $str =~ m/\w+/g;
my %seen;
my %prop;
for my $word (@words) {
next if $seen{$word};
$seen{$word} = 1;
my $script;
for my $char (split(//, $word)) {
my $s = $prop{$char};
if (not $s) {
$s = charprop(ord($char), "Script_Extensions");
if ($s eq 'Hiragana') {
$s = 'Han'; # this mixing is ok
}
$prop{$char} = $s;
}
next if $s eq "Common";
if (not $script) {
$script = $s;
} elsif ($script ne $s) {
return "Mixed scripts in $word ($script and $s, if not more)";
}
}
}
}

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2012 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2012-2018 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
@@ -62,9 +62,10 @@ push(@MyInitVariables, sub {
sub RegexpNewBannedContent {
my $str = shift;
# remove URLs as they are controlled by $BannedContent
$str =~ s/$FullUrlPattern//g;
# check whether Banned Content complains
my $rule = RegexpOldBannedContent($str, @_);
# remove URLs as they have been checked by $BannedContent
$str =~ s/$FullUrlPattern//g;
if (not $rule) {
foreach (split(/\n/, GetPageContent($BannedRegexps))) {
next unless m/^\s*([^#]+?)\s*(#\s*(\d\d\d\d-\d\d-\d\d\s*)?(.*))?$/;

View File

@@ -1,4 +1,4 @@
# Copyright (C) 20042014 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20042018 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
@@ -30,15 +30,43 @@ sub DoJournalRss {
local $CollectingJournal = 1;
# Fake the result of GetRcLines()
local *GetRcLines = \&JournalRssGetRcLines;
local *RcSelfAction = \&JournalRssSelfAction;
local *RcPreviousAction = \&JournalRssPreviousAction;
local *RcLastAction = \&JournalRssLastAction;
SetParam('full', 1);
print GetHttpHeader('application/xml') . GetRcRss();
}
sub JournalRssParameters {
my $more = '';
foreach (@_, qw(rsslimit match search reverse monthly)) {
my $val = GetParam($_, '');
$more .= ";$_=$val" if $val;
}
return $more;
}
sub JournalRssSelfAction {
return "action=journal" . JournalRssParameters(qw(offset));
}
sub JournalRssPreviousAction {
my $num = GetParam('rsslimit', 10);
my $offset = GetParam('offset', 0) + $num;
return "action=journal;offset=$offset" . JournalRssParameters();
}
sub JournalRssLastAction {
return "action=journal" . JournalRssParameters();
}
sub JournalRssGetRcLines {
my $num = GetParam('rsslimit', 10);
my $match = GetParam('match', '^\d\d\d\d-\d\d-\d\d');
my $search = GetParam('search', '');
my $reverse = GetParam('reverse', 0);
my $monthly = GetParam('monthly', 0);
my $offset = GetParam('offset', 0);
my @pages = sort JournalSort (grep(/$match/, $search ? SearchTitleAndBody($search) : AllPagesList()));
if ($monthly and not $match) {
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime();
@@ -48,28 +76,19 @@ sub JournalRssGetRcLines {
@pages = reverse @pages;
}
# FIXME: Missing 'future' and 'past' keywords.
# FIXME: Do we need 'offset'? I don't think so.
my @result = ();
my $n = 0;
foreach my $id (@pages) {
# Now save information required for saving the cache of the current page.
local %Page;
local $OpenPageName = '';
OpenPage($id);
# If this is a minor edit, ignore it. Load the last major revision
# instead, if you can.
if ($Page{minor}) {
# Perhaps the old kept revision is gone due to $KeepMajor=0 or
# admin.pl or because a page was created as a minor change and
# never edited. Reading kept revisions in this case results in
# an error.
eval {
%Page = GetKeptRevision($Page{lastmajor});
};
next if $@;
}
next if $Page{text} =~ /^\s*$/; # only whitespace is also to be deleted
next if $DeletedPage && substr($Page{text}, 0, length($DeletedPage))
eq $DeletedPage; # no regexp
eq $DeletedPage; # no regexp
# OK, this is a candidate page
$n++;
next if $n <= $offset;
# Generate artifical rows in the list to pass to GetRcRss. We need
# to open every single page, because the meta-data ordinarily
# available in the rc.log file is not available to us. This is why
@@ -79,7 +98,7 @@ sub JournalRssGetRcLines {
push (@result, [$Page{ts}, $id, $Page{minor}, $Page{summary}, $Page{host},
$Page{username}, $Page{revision}, \@languages,
GetCluster($Page{text})]);
last if $num ne 'all' and $#result + 1 >= $num;
last if @result >= $num;
}
return @result;
}

View File

@@ -198,8 +198,7 @@ sub MakeLaTeX {
}
if (not $error and IsFile('srender1.png') and not ZeroSize('srender1.png')) {
my $png = ReadFileOrDie("srender1.png");
WriteStringToFile ("$LatexDir/$hash.png", $png);
Rename("srender1.png", "$LatexDir/$hash.png");
} else {
$error = "[Error retrieving image for $latex]";
}

View File

@@ -239,6 +239,11 @@ sub TagFind {
return @result;
}
sub TagsTerms {
my $string = shift;
return grep(/./, $string =~ /\"([^\"]+)\"|(\S+)/g);
}
*OldTagFiltered = \&Filtered;
*Filtered = \&NewTagFiltered;
@@ -246,7 +251,7 @@ sub NewTagFiltered { # called within a lock!
my ($string, @pages) = @_;
my %page = map { $_ => 1 } @pages;
# looking at all the "tag:SOME TERMS" and and tag:TERM
my @tagterms = map { FreeToNormal($_) } grep(/^-?tag:/, shift =~ /\"([^\"]+)\"|(\S+)/g);
my @tagterms = map { FreeToNormal($_) } grep(/^-?tag:/, TagsTerms($string));
my @positives = map {substr($_, 4)} grep(/^tag:/, @tagterms);
my @negatives = map {substr($_, 5)} grep(/^-tag:/, @tagterms);
if (@positives) {
@@ -260,8 +265,9 @@ sub NewTagFiltered { # called within a lock!
foreach my $id (TagFind(@negatives)) {
delete $page{$id};
}
# filter out the tags from the search string
$string = join(' ', grep(!/^-?tag:/, $string =~ /\"([^\"]+)\"|(\S+)/g));
# filter out the tags from the search string, and add quotes which might have
# been stripped
$string = join(' ', map { qq{"$_"} } grep(!/^-?tag:/, TagsTerms($string)));
# run the old code for any remaining search terms
return OldTagFiltered($string, sort keys %page);
}
@@ -279,11 +285,12 @@ We're need to remove all tag terms (again) in order to not confuse it.
*SearchString = \&NewTagSearchString;
sub NewTagSearchString {
# filter out the negative tags from the search string
my $string = join(' ', map { NormalToFree($_) }
grep(!/^-tag:/, shift =~ /\"([^\"]+)\"|(\S+)/g));
my ($string, @rest) = @_;
# filter out the negative tags from the search string, and add quotes which
# might have been stripped
$string = join(' ', map { NormalToFree($_) } map { qq{"$_"} } grep(!/^-tag:/, TagsTerms($string)));
return 1 unless $string;
return OldTagSearchString($string, @_);
return OldTagSearchString($string, @rest);
}
=pod

View File

@@ -206,10 +206,12 @@ sub send_mail {
sub main {
my $rss = get_rss();
return unless @{$rss->{items}};
my $subscribers = get_subscribers();
return unless %{$subscribers};
send_files($rss, $subscribers);
if (@{$rss->{items}}) {
my $subscribers = get_subscribers();
if (%{$subscribers}) {
send_files($rss, $subscribers);
}
}
update_timestamp();
}

87
scripts/strip-host.pl Executable file
View File

@@ -0,0 +1,87 @@
#! /usr/bin/perl -w
# Copyright (C) 2018 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/>.
sub ParseData {
my $data = shift;
my %result;
while ($data =~ /(\S+?): (.*?)(?=\n[^ \t]|\Z)/sg) {
my ($key, $value) = ($1, $2);
$value =~ s/\n\t/\n/g;
$result{$key} = $value;
}
return %result;
}
sub EncodePage {
my @data = @_;
my $result = '';
$result .= (shift @data) . ': ' . EscapeNewlines(shift @data) . "\n" while (@data);
return $result;
}
sub EscapeNewlines {
$_[0] =~ s/\n/\n\t/g; # modify original instead of copying
return $_[0];
}
sub main {
die "There is no temp directory, here.\n"
. "Perhaps this isn't an Oddmuse data directory?\n"
unless -d 'temp';
die "The main lock already exists in the temp directory.\n"
if -d "temp/lockmain";
mkdir "temp/lockmain" or die "Cannot create main lock in temp: $!\n";
local $/ = undef; # Read complete files
foreach my $dir (qw/keep page/) {
warn "Did not find the $dir directory.\n" unless -d $dir;
}
# include dotfiles!
my $t = 0;
my $n = 0;
foreach my $file (glob("page/*.pg page/.*.pg"),
glob("keep/*/*.kp keep/.*.kp")) {
$t++;
open(my $fh, '<', $file) or die "Cannot read $file file: $!\n";
my $data = <$fh>;
close($fh);
next unless $data;
my %result = ParseData($data);
if (exists($result{host}) or exists($result{ip})) {
delete($result{host});
delete($result{ip});
open($fh,'>', "$file~") or die "Cannot $file~: $!\n";
print $fh EncodePage(%result);
close($fh);
rename("$file~", $file) or die "Cannot rename $file~ to $file: $!\n";
$n++;
}
}
rmdir "temp/lockmain" or die "Cannot remove main lock: $!\n";
print "I looked at $t files and found $n host or ip keys which I removed.\n";
}
if (@ARGV) {
print qq{
Usage: $0 [--page DIR]
Goes through the wiki and removes the hostname or IP number from page and
keep files. Make a backup before running this script! Run this script in
your data directory.
}
} else {
main ();
}

View File

@@ -20,10 +20,13 @@ use base qw(Net::Server::Fork); # any personality will do
use MIME::Base64;
use Text::Wrap;
use List::Util qw(first);
use Socket;
our($RunCGI, $DataDir, %IndexHash, @IndexList, $IndexFile, $TagFile, $q,
%Page, $OpenPageName, $MaxPost, $ShowEdits, %Locks, $CommentsPattern,
$CommentsPrefix, $EditAllowed, $NoEditFile, $SiteName, $ScriptName);
our($RunCGI, $DataDir, %IndexHash, @IndexList, $IndexFile, $TagFile, $q, %Page,
$OpenPageName, $MaxPost, $ShowEdits, %Locks, $CommentsPattern,
$CommentsPrefix, $EditAllowed, $NoEditFile, $SiteName, $ScriptName, $Now,
%RecentVisitors, $SurgeProtection, $SurgeProtectionTime,
$SurgeProtectionViews);
my $external_image_path = '/home/alex/alexschroeder.ch/pics/';
@@ -93,6 +96,12 @@ sub post_configure_hook {
# make sure search is sorted newest first because NewTagFiltered resorts
*OldGopherFiltered = \&Filtered;
*Filtered = \&NewGopherFiltered;
*ReportError = sub {
my ($error, $status, $log, @html) = @_;
$self->print_error("Error: $error");
map { ReleaseLockDir($_); } keys %Locks;
exit 2;
};
}
my $usage = << 'EOT';
@@ -177,6 +186,12 @@ sub NewGopherFiltered {
return @pages;
}
sub normal_to_free {
my $title = shift;
$title =~ s/_/ /g;
return $title;
}
sub print_text {
my $self = shift;
my $text = shift;
@@ -222,17 +237,17 @@ sub serve_main_menu {
$self->print_info("Phlog:");
my @pages = sort { $b cmp $a } grep(/^\d\d\d\d-\d\d-\d\d/, @IndexList);
for my $id (@pages[0..9]) {
$self->print_menu("1" . NormalToFree($id), "$id/menu");
$self->print_menu("1" . normal_to_free($id), "$id/menu");
}
$self->print_menu("1" . "More...", "do/more");
$self->print_info("");
for my $id (@{$self->{server}->{wiki_pages}}) {
$self->print_menu("1" . NormalToFree($id), "$id/menu");
$self->print_menu("1" . normal_to_free($id), "$id/menu");
}
for my $id (@{$self->{server}->{menu}}) {
$self->print_menu("1" . NormalToFree($id), "map/$id");
$self->print_menu("1" . normal_to_free($id), "map/$id");
}
$self->print_menu("1" . "Recent Changes", "do/rc");
@@ -255,7 +270,7 @@ sub serve_phlog_archive {
$self->log(3, "Serving phlog archive");
my @pages = sort { $b cmp $a } grep(/^\d\d\d\d-\d\d-\d\d/, @IndexList);
for my $id (@pages) {
$self->print_menu("1" . NormalToFree($id), "$id/menu");
$self->print_menu("1" . normal_to_free($id), "$id/menu");
}
}
@@ -263,7 +278,7 @@ sub serve_index {
my $self = shift;
$self->log(3, "Serving index of all pages");
for my $id (sort newest_first @IndexList) {
$self->print_menu("1" . NormalToFree($id), "$id/menu");
$self->print_menu("1" . normal_to_free($id), "$id/menu");
}
}
@@ -274,7 +289,7 @@ sub serve_match {
$self->print_info("Use a regular expression to match page titles.");
$self->print_info("Spaces in page titles are underlines, '_'.");
for my $id (sort newest_first grep(/$match/i, @IndexList)) {
$self->print_menu( "1" . NormalToFree($id), "$id/menu");
$self->print_menu( "1" . normal_to_free($id), "$id/menu");
}
}
@@ -285,7 +300,7 @@ sub serve_search {
$self->print_info("Use regular expressions separated by spaces.");
SearchTitleAndBody($str, sub {
my $id = shift;
$self->print_menu("1" . NormalToFree($id), "$id/menu");
$self->print_menu("1" . normal_to_free($id), "$id/menu");
});
}
@@ -299,7 +314,7 @@ sub serve_tags {
$count{$tag} = @{$h{$tag}};
}
foreach my $id (sort { $count{$b} <=> $count{$a} } keys %count) {
$self->print_menu("1" . NormalToFree($id), "$id/tag");
$self->print_menu("1" . normal_to_free($id), "$id/tag");
}
}
@@ -326,7 +341,7 @@ sub serve_rc {
sub {
my($id, $ts, $author_host, $username, $summary, $minor, $revision,
$languages, $cluster, $last) = @_;
$self->print_menu("1" . NormalToFree($id), "$id/menu");
$self->print_menu("1" . normal_to_free($id), "$id/menu");
for my $line (split(/\n/, wrap(' ', ' ', $summary))) {
$self->print_info($line);
}
@@ -403,7 +418,7 @@ sub serve_file_page_menu {
my $revision = shift;
my $code = substr($type, 0, 6) eq 'image/' ? 'I' : '9';
$self->log(3, "Serving file page menu for " . UrlEncode($id));
$self->print_menu($code . NormalToFree($id)
$self->print_menu($code . normal_to_free($id)
. ($revision ? "/$revision" : ""), $id);
$self->serve_page_comment_link($id, $revision);
$self->serve_page_history_link($id, $revision);
@@ -418,23 +433,35 @@ sub serve_text_page_menu {
. ($revision ? "/$revision" : ""));
$self->print_info("The text of this page:");
$self->print_menu("0" . NormalToFree($id),
$self->print_menu("0" . normal_to_free($id),
$id . ($revision ? "/$revision" : ""));
$self->print_menu("h" . NormalToFree($id),
$self->print_menu("h" . normal_to_free($id),
$id . ($revision ? "/$revision" : "") . "/html");
$self->print_menu("w" . "Replace " . NormalToFree($id),
$self->print_menu("w" . "Replace " . normal_to_free($id),
$id . "/write/text");
$self->serve_page_comment_link($id, $revision);
$self->serve_page_history_link($id, $revision);
my $first = 1;
while ($page->{text} =~ /\[\[([^\]|]*)(?:\|([^\]]*))?\]\]|\[(https?:\/\/\S+)\s+([^\]]*)\]|\[gopher:\/\/([^:\/]*)(?::(\d+))?(?:\/(\d)(\S+))?\s+([^\]]+)\]/g) {
my ($title, $text, $url, $hostname, $port, $type, $selector)
= ($1, $2||$4||$9, $3, $5, $6||70, $7||1, $8);
while ($page->{text} =~ /
\[\[ (?<title>[^\]|]*) (?:\|(?<text>[^\]]*))? \]\]
| \[ (?<url>https?:\/\/\S+) \s+ (?<text>[^\]]*) \]
| \[ (?<text>[^\]]*) \] \( (?<url>https?:\/\/\S+) \)
| \[ gopher:\/\/ (?<hostname>[^:\/]*) (?::(?<port>\d+))?
(?:\/(?<type>\d) (?<selector>\S+))?
\s+ (?<text>[^\]]+)\]
| \[ (?<text>[^\]]+) \]
\( gopher:\/\/ (?<hostname>[^:\/]*) (?::(?<port>\d+))?
(?:\/(?<type>\d) (?<selector>\S+))? \)
/xg) {
my ($title, $text, $url, $hostname,
$port, $type, $selector)
= ($+{title}, $+{text}, $+{url}, $+{hostname},
$+{port}||70, $+{type}||1, $+{selector});
if ($first) {
$self->print_info("");
$self->print_info("Links leaving " . NormalToFree($id) . ":");
$self->print_info("Links leaving " . normal_to_free($id) . ":");
$first = 0;
}
if ($hostname) {
@@ -447,8 +474,8 @@ sub serve_text_page_menu {
} elsif ($title =~ s!^image[/a-z]* external:!pics/!) {
$self->print_menu("I" . $text||$title, $title);
} elsif ($title) {
$title =~ s!^image[/[a-z]]*:!!;
$self->print_menu("1" . $text||$title, $title . "/menu");
$title =~ s!^image[/a-z]*:!!i;
$self->print_menu("1" . ($text||$title), $title . "/menu");
}
}
@@ -478,18 +505,18 @@ sub serve_page_history {
$self->log(3, "Serving history of " . UrlEncode($id));
OpenPage($id);
$self->print_menu("1" . NormalToFree($id) . " (current)", "$id/menu");
$self->print_menu("1" . normal_to_free($id) . " (current)", "$id/menu");
$self->print_info(CalcTime($Page{ts})
. " by " . GetAuthor($Page{host}, $Page{username})
. " by " . GetAuthor($Page{username})
. ($Page{summary} ? ": $Page{summary}" : "")
. ($Page{minor} ? " (minor)" : ""));
foreach my $revision (GetKeepRevisions($OpenPageName)) {
my $keep = GetKeptRevision($revision);
$self->print_menu("1" . NormalToFree($id) . " ($keep->{revision})",
$self->print_menu("1" . normal_to_free($id) . " ($keep->{revision})",
"$id/$keep->{revision}/menu");
$self->print_info(CalcTime($keep->{ts})
. " by " . GetAuthor($keep->{host}, $keep->{username})
. " by " . GetAuthor($keep->{username})
. ($keep->{summary} ? ": $keep->{summary}" : "")
. ($keep->{minor} ? " (minor)" : ""));
}
@@ -570,7 +597,7 @@ sub serve_page_html {
$self->log(3, "Serving " . UrlEncode($id) . " as HTML");
my $title = NormalToFree($id);
my $title = normal_to_free($id);
print GetHtmlHeader(Ts('%s:', $SiteName) . ' ' . UnWiki($title), $id);
print GetHeaderDiv($id, $title);
print $q->start_div({-class=>'wrapper'});
@@ -631,7 +658,7 @@ sub serve_tag_list {
my $tag = shift;
$self->print_info("Search result for tag $tag:");
for my $id (sort newest_first TagFind($tag)) {
$self->print_menu("1" . NormalToFree($id), "$id/menu");
$self->print_menu("1" . normal_to_free($id), "$id/menu");
}
}
@@ -641,7 +668,7 @@ sub serve_tag {
$self->log(3, "Serving tag " . UrlEncode($tag));
if ($IndexHash{$tag}) {
$self->print_info("This page is about the tag $tag.");
$self->print_menu("1" . NormalToFree($tag), "$tag/menu");
$self->print_menu("1" . normal_to_free($tag), "$tag/menu");
$self->print_info("");
}
$self->serve_tag_list($tag);
@@ -679,7 +706,7 @@ sub write_page_ok {
my $self = shift;
my $id = shift;
$self->print_info("Page was saved.");
$self->print_menu("1" . NormalToFree($id), "$id/menu");
$self->print_menu("1" . normal_to_free($id), "$id/menu");
}
sub write_page_error {
@@ -795,13 +822,41 @@ sub read_text {
return $buf;
}
sub process_request {
sub allow_deny_hook {
my $self = shift;
my $client = shift;
# clear cookie and all that
# clear cookie, read config file
$q = undef;
Init();
# don't do surge protection if we're testing
return 1 unless $SurgeProtection;
# get the client IP number
my $peeraddr = $self->{server}->{'peeraddr'};
# implement standard surge protection using Oddmuse tools but without using
# ReportError and all that
$self->log(4, "Adding visitor $peeraddr");
ReadRecentVisitors();
AddRecentVisitor($peeraddr);
if (RequestLockDir('visitors')) { # not fatal
WriteRecentVisitors();
ReleaseLockDir('visitors');
my @entries = @{$RecentVisitors{$peeraddr}};
my $ts = $entries[$SurgeProtectionViews];
if (($Now - $ts) < $SurgeProtectionTime) {
$self->log(2, "Too many requests by $peeraddr");
return 0;
}
}
return 1;
}
sub process_request {
my $self = shift;
# refresh list of pages
if (IsFile($IndexFile) and ReadIndex()) {
# we're good

View File

@@ -41,7 +41,7 @@ while ($arg =~ /^-l/) {
die $help unless -f $file;
my %backup = %Translate;
header_info_extract($file); # keep the header information of the translation files
do $file or die "Cannot do $file";
do "./$file" or die "Cannot do $file";
foreach my $key (keys %Translate) {
$backup{$key} = $Translate{$key};
}

View File

@@ -33,7 +33,7 @@ EOT
my $min = version->parse(shift || "2.3.0");
my @tags = grep { /\d+\.\d+\.\d+/ and version->parse($_) >= $min }
my @tags = grep { /(\d+\.\d+\.\d+)/ and version->parse($1) >= $min }
split(/\n/, qx{git tag --list});
unless (@tags) {

33
t/ban-mixed-scripts.t Normal file
View File

@@ -0,0 +1,33 @@
# Copyright (C) 2018 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 => 4;
use utf8; # tests contain UTF-8 characters and it matters
add_module('ban-mixed-scripts.pl');
# ordinary page editing still works
test_page(update_page('Test', 'This is a test'),
'This is a test');
test_page(update_page('Test', '🙇🏽‍ 本当にごめんね I am really sorry.'),
'I am really sorry');
# mixed scripts are not ok
test_page(update_page('Test', "It's diffіcult to find knowledgeable people on this topic, but youu sound like you know wgat you're takіng аboսt!"),
'I am really sorry');
# error message is shown
test_page($redirect, "Mixed scripts");

24
t/ban.t
View File

@@ -1,4 +1,4 @@
# Copyright (C) 2006, 2007, 2010 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2006-2018 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 => 23;
use Test::More tests => 27;
$localhost = 'confusibombus';
$ENV{'REMOTE_ADDR'} = $localhost;
@@ -96,3 +96,23 @@ test_page(get_page("action=rollback to=$ts id=CriminalPage username=Alex"),
test_page(get_page("action=rollback to=$ts id=CriminalPage pwd=foo"),
'Rolling back changes',
'CriminalPage</a> rolled back');
# make sure it also doesn't work in the homepage field for comments
AppendStringToFile($ConfigFile, "\$CommentsPrefix = 'Comments on ';\n");
# mafia is still banned
test_page(get_page(join(' ', 'title=Comments_on_CriminalPage',
'aftertext=Innocent',
'username=Alex',
'homepage=http://mafia.example.com')),
'Reason: crime');
# but it still works!
test_page(get_page(join(' ', 'title=Comments_on_CriminalPage',
'aftertext=Innocent',
'username=Alex',
'homepage=http://police.example.com')),
'Status: 302');
test_page(get_page('Comments_on_CriminalPage'),
'Innocent',
'http://police.example.com');

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2017 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2017-2018 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 => 13;
use Test::More tests => 15;
add_module('banned-regexps.pl');
@@ -63,3 +63,13 @@ test_page(update_page('Test2', 'Voldemort', 'one banned word'),
# Make sure the underscores don't show up in the page link
test_page(get_page('action=admin'), 'Local Banned Regexps');
# Make sure it doesn't break BannedContent!
# mafia is banned
update_page('BannedContent', 'mafia', 'one banned word', 0, 1);
test_page(update_page('CriminalPage', 'This is about http://mafia.example.com'),
'This page does not exist');
# error message is shown
test_page($redirect, 'Edit Denied');

40
t/checkbox.t Normal file
View File

@@ -0,0 +1,40 @@
# Copyright (C) 2018 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 => 5;
add_module('checkbox.pl');
my $text = q{
There's some stuff I want to work on:
[[ :something to do]]
[[x:something done]]
[[save:update the list]]
Let's do this!
};
$page = update_page('TODO', $text, 'saving it');
xpath_test(
$page,
'//p[text()="There\'s some stuff I want to work on:"]',
'//form[@class="checkboxes"]/p/label/input[@type="checkbox"][@name="something_to_do"]/following-sibling::text()[string()="something to do"]',
'//form[@class="checkboxes"]/p/label/input[@type="checkbox"][@name="something_done"][@checked="checked"]/following-sibling::text()[string()="something done"]',
'//form[@class="checkboxes"]/p/input[@type="submit"][@name="update the list"]',
'//p[text()="Let\'s do this!"]',);

View File

@@ -226,7 +226,7 @@ like($page, qr/^$haiku_re/, "Haiku updated");
$page = query_gopher("Haiku/history");
like($page, qr/^1Haiku \(current\)\tHaiku\/menu\t/m, "Haiku (current)");
like($page, qr/^i\d\d:\d\d UTC by Alex from \S+: typos \(minor\)/m,
like($page, qr/^i\d\d:\d\d UTC by Alex: typos \(minor\)/m,
"Metadata recorded");
like($page, qr/^1Haiku \(1\)\tHaiku\/1\/menu\t/m, "Haiku (1)");

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2008, 2009 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2008-2018 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,33 +15,26 @@
require './t/test.pl';
package OddMuse;
use Test::More tests => 48;
use Test::More tests => 41;
add_module('journal-rss.pl');
# summaries eq page content since no summaries are provided
update_page('2008-09-21', 'first page');
update_page('2008-09-22', 'second page'); # major
sleep(1);
update_page('2008-09-22', 'third edit', 'third edit', 1); # minor
update_page('2008-09-22', 'third edit content', 'third edit summary', 1); # minor
update_page('unrelated', 'wrong page');
my $page = get_page('action=journal');
test_page($page,
'2008-09-21', 'first page',
# ignore minor edits are ignored: show last major edit
# instead
'2008-09-22', 'second page',
# make sure we're showing full page content, not summaries
'2008-09-22', 'third edit content',
# reverse sort is the default
'2008-09-22(.*\n)+.*2008-09-21');
# make sure unrelated pages and minor edits don't show up
test_page_negative($page, 'unrelated', 'wrong page',
'third edit');
# verify the order of pages
test_page(get_page('action=journal'),
'2008-09-22(.*\n)+.*2008-09-21');
test_page_negative($page, 'unrelated', 'wrong page');
# reverse the order
test_page(get_page('action=journal reverse=1'),
@@ -52,21 +45,6 @@ $page = get_page('action=journal match=21');
test_page($page, '2008-09-21', 'first page');
test_page_negative($page, '2008-09-22', 'second page');
# search parameter
$page = get_page('action=journal search=second');
# no pages found, since this is for an old revision!
test_page_negative($page,
'2008-09-21', 'first page',
'2008-09-22', 'second page',
'third edit');
# strange but true: search returns a page based on the minor edit but
# shows the latest major revision that doesn't actually match.
$page = get_page('action=journal search=third');
test_page($page, '2008-09-22', 'second page');
test_page_negative($page, '2008-09-21', 'first page', 'third edit');
# testing the limit default
update_page('2008-09-05', 'page');
update_page('2008-09-06', 'page');
@@ -98,17 +76,23 @@ $page = get_page('action=journal rsslimit=1');
test_page($page, '2008-09-22');
test_page_negative($page, '2008-09-21');
$page = get_page('action=journal rsslimit=all');
test_page($page, '2008-09-22', '2008-09-05');
# make sure we start from a well-known point in time
AppendStringToFile($ConfigFile, "push(\@MyInitVariables, sub { \$Now = '$Now' });\n");
# Now let's show that we're using the timestamp of the last major
# change if possible.
# check default RSS
xpath_test(get_page('action=journal'),
'//atom:link[@rel="self"][@href="http://localhost/wiki.pl?action=journal"]',
'//atom:link[@rel="last"][@href="http://localhost/wiki.pl?action=journal"]',
'//atom:link[@rel="previous"][@href="http://localhost/wiki.pl?action=journal;offset=10"]');
my @dates = get_page('action=rss showedit=1 all=1 match=2008-09-22')
=~ m!<pubDate>(.*?)</pubDate>!g;
# $dates[0] is the channel pubDate
my $date2 = $dates[1]; # revision 2 comes first
my $date1 = $dates[2]; # revision 1 comes second
my ($item) = $page =~ m!(<item>\n<title>2008-09-22</title>\n(.*\n)+?</item>\n)!;
test_page($item, "<pubDate>$date1</pubDate>");
test_page_negative($item, "<pubDate>$date2</pubDate>");
# check next page
xpath_test(get_page('action=journal offset=10'),
'//atom:link[@rel="self"][@href="http://localhost/wiki.pl?action=journal;offset=10"]',
'//atom:link[@rel="last"][@href="http://localhost/wiki.pl?action=journal"]',
'//atom:link[@rel="previous"][@href="http://localhost/wiki.pl?action=journal;offset=20"]');
# check next page but with a tag search
xpath_test(get_page('action=journal search=tag:oddmuse'),
'//atom:link[@rel="self"][@href="http://localhost/wiki.pl?action=journal;search=tag:oddmuse"]',
'//atom:link[@rel="last"][@href="http://localhost/wiki.pl?action=journal;search=tag:oddmuse"]',
'//atom:link[@rel="previous"][@href="http://localhost/wiki.pl?action=journal;offset=10;search=tag:oddmuse"]');

View File

@@ -1,4 +1,4 @@
# Copyright (C) 20092015 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20092018 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,18 +15,66 @@
require './t/test.pl';
package OddMuse;
use Test::More tests => 10;
use Test::More tests => 20;
my $hundred_days_ago = $Now - 100 * 24 * 60 * 60;
my $ten_days_ago = $Now - 10 * 24 * 60 * 60;
my $two_days_ago = $Now - 2 * 24 * 60 * 60;
my $ip = '127.0.0.1';
# $ts, $id, $minor, $summary, $host, @rest
# First, make sure that moving all the entries from rc.log leaves no newline in
# the file.
my $log = join("\n",
join($FS, $hundred_days_ago, 'Two_Hundred_Days_Ago', '',
'Boring', $ip,
'Alex', '1', '', ''),
'');
WriteStringToFile($RcFile, $log);
# old log entry to be moved
my $log = join($FS, '1235079422', 'Ganz_und_Gar', '',
'Ladenbeschreibung und Preisliste', '62.12.165.34',
'Alex', '1', '', '');
WriteStringToFile($RcFile, $log . "\n");
test_page(get_page('action=maintain pwd=foo'),
'Moving 1 log entries');
test_page(ReadFileOrDie($RcOldFile),
"^1235079422$FS");
is(ReadFileOrDie($RcFile), '', 'empty rc.log');
'Moving 1 log entries',
'Removing IP numbers from 0 log entries');
$log = ReadFileOrDie($RcOldFile);
test_page($log, "Hundred_Days_Ago.*Anonymous");
test_page_negative($log, $ip);
$log = ReadFileOrDie($RcFile);
is($log, '', 'rc.log is empty');
# Now let's make sure that an old entry get anonymized and moved (like the
# previous test), and that those that are not moved care anonymized if they are
# old enough.
$log = join("\n",
join($FS, $hundred_days_ago, 'One_Hundred_Days_Ago', '',
'Boring', $ip,
'Alex', '1', '', ''),
join($FS, $ten_days_ago, 'Ten_Days_Ago', '',
'Boring', $ip,
'Alex', '1', '', ''),
join($FS, $two_days_ago, 'Two_Days_Ago', '',
'Boring', $ip,
'Alex', '1', '', ''),
'');
WriteStringToFile($RcFile, $log);
test_page(get_page('action=maintain pwd=foo'),
'Moving 1 log entries',
'Removing IP numbers from 1 log entries');
$log = ReadFileOrDie($RcOldFile);
test_page($log,
"Two_Hundred_Days_Ago.*Anonymous",
"One_Hundred_Days_Ago.*Anonymous");
test_page_negative($log, $ip);
$log = ReadFileOrDie($RcFile);
test_page($log,
"Ten_Days_Ago.*Anonymous",
"Two_Days_Ago.*$ip");
test_page_negative($log, "Hundred_Days_Ago");
# Let's make sure that updating pages write the right rc lines.
update_page('test', 'this is a test');
my $log = ReadFileOrDie($RcFile);
test_page($log,
@@ -34,7 +82,8 @@ test_page($log,
"${FS}this is a test${FS}");
test_page_negative($log, "^\n");
# old page to be deleted
# Make sure that pages to be deleted are in fact deleted.
OpenPage('test');
$Page{ts} = 1;
$Page{revision} = 1;

4
t/rc.t
View File

@@ -1,4 +1,4 @@
# Copyright (C) 200620015 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20062015 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
@@ -51,7 +51,7 @@ test_page(get_page('action=rc raw=1'), 'title: Wiki');
WriteStringToFile($RcFile, "1${FS}test${FS}${FS}test${FS}127.0.0.1${FS}${FS}1${FS}${FS}\n");
test_page_negative(get_page('action=rc raw=1'), 'title: test');
test_page(get_page('action=rc raw=1 from=1'), 'title: Wiki', 'title: test',
'description: test', 'generator: 127.0.0.1',
'description: test', 'generator: Anonymous',
'link: http://localhost/wiki.pl/test',
'last-modified: 1970-01-01T00:00Z', 'revision: 1');

34
t/rss.t
View File

@@ -1,4 +1,4 @@
# Copyright (C) 20062015 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20062018 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 => 126;
use Test::More tests => 135;
use utf8; # tests contain UTF-8 characters and it matters
AppendStringToFile($ConfigFile, "\$CommentsPrefix = 'Comments on ';\n");
@@ -107,6 +107,36 @@ update_page('big', "mee too\n" x 2 . 'x' x 55000);
test_page(get_page('action=rss full=1'), 'too big to send over RSS');
test_page(get_page('action=rss full=1 diff=1'), 'mee too', 'too big to send over RSS');
# pagination
my $interval = $RcDefault * 24 * 60 * 60;
my $t1 = $Now - $interval;
my $t2 = $Now - 2 * $interval;
my $t3 = $Now - 3 * $interval;
my $action1 = " from=$t2 upto=$t1";
my $window1 = ";from=$t2;upto=$t1";
my $window2 = ";from=$t3;upto=$t2";
# make sure we start from a well-known point in time
AppendStringToFile($ConfigFile, "push(\@MyInitVariables, sub { \$Now = '$Now' });\n");
# check default RSS
xpath_test(get_page('action=rss'),
'//atom:link[@rel="self"][@href="http://localhost/wiki.pl?action=rss"]',
'//atom:link[@rel="last"][@href="http://localhost/wiki.pl?action=rss"]',
'//atom:link[@rel="previous"][@href="http://localhost/wiki.pl?action=rss' . $window1 . '"]');
# check next page
xpath_test(get_page('action=rss' . $action1),
'//atom:link[@rel="self"][@href="http://localhost/wiki.pl?action=rss' . $window1 . '"]',
'//atom:link[@rel="last"][@href="http://localhost/wiki.pl?action=rss"]',
'//atom:link[@rel="previous"][@href="http://localhost/wiki.pl?action=rss' . $window2 . '"]');
# check next page but with full pages
xpath_test(get_page('action=rss full=1' . $action1),
'//atom:link[@rel="self"][@href="http://localhost/wiki.pl?action=rss' . $window1 . ';full=1"]',
'//atom:link[@rel="last"][@href="http://localhost/wiki.pl?action=rss;full=1"]',
'//atom:link[@rel="previous"][@href="http://localhost/wiki.pl?action=rss' . $window2 . ';full=1"]');
SKIP: {
eval {

23
t/search-inclusion.t Normal file
View File

@@ -0,0 +1,23 @@
# Copyright (C) 2018 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 => 3;
test_page(update_page("Omega", "last page"), "last page");
test_page(update_page("Alpha", "first page\n<search page>"),
"Omega");
test_page(get_page('Omega'), 'last page');

View File

@@ -1,4 +1,4 @@
# Copyright (C) 20062015 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20062018 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
@@ -156,7 +156,7 @@ xpath_test(update_page('IncludeSearch',
'//p[text()="first line "]', # note the NL -> SPC
'//div[@class="search"]/p/span[@class="result"]/a[@class="local"][@href="http://localhost/wiki.pl/NegativeSearchTest"][text()="NegativeSearchTest"]',
'//div[@class="search"]/p/span[@class="result"]/a[@class="local"][@href="http://localhost/wiki.pl/NegativeSearchTestTwo"][text()="NegativeSearchTestTwo"]',
'//p[text()=" last line"]'); # note the NL -> SPC
'//p[text()=" last line"]'); # note the NL -> SPC
xpath_test(get_page('search=Schröder'),
'//input[@name="search"][@value="Schröder"]');

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2006, 2009 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2006-2018 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,12 +15,27 @@
require './t/test.pl';
package OddMuse;
use Test::More tests => 70;
use Test::More tests => 94;
use utf8;
add_module('tags.pl');
InitVariables();
# first, let's make sure the old search still works
test_page(join(':', TagsTerms('a b "c d"')), 'a:b:c d');
update_page('test', 'foo bar baz');
test_page(get_page('search=foo'), 'test', 'Search for: foo', '<strong>foo</strong>');
test_page(get_page('search=foo+bar'), 'test', 'Search for: foo bar', '<strong>foo</strong> <strong>bar</strong>');
test_page(get_page('search=bar+foo'), 'test', 'Search for: bar foo', '<strong>foo</strong> <strong>bar</strong>');
test_page(get_page('search=foo+baz'), 'test', 'Search for: foo baz', '<strong>foo</strong> bar <strong>baz</strong>');
$page = get_page('search=%22foo+baz%22');
test_page($page, 'Search for: "foo baz"');
test_page_negative($page, 'test');
test_page(get_page('search=%22foo+bar%22'), 'test', 'Search for: "foo bar"', '<strong>foo bar</strong>');
# testing other stuff
$TagFeedIcon = 'http://www.example.org/pics/rss.png';
xpath_run_tests(split('\n',<<'EOT'));
@@ -160,6 +175,12 @@ $page = update_page('Podcasts', '<journal "." search tag:podcast>');
test_page($page, qw(Pödgecäst´s Brilliant Sons));
test_page_negative($page, qw(Alex Foo));
# check recent changes
test_page(get_page('action=rc'), qw(Brilliant Alex Jeff));
$page = get_page('action=rc rcfilteronly=tag:Podcast');
test_page($page, qw(Brilliant));
test_page_negative($page, qw(Alex Jeff));
# check the tag cloud
xpath_test(get_page('action=tagcloud'),
'//h1[text()="Tag Cloud"]',

156
wiki.pl
View File

@@ -1,5 +1,5 @@
#! /usr/bin/perl
# Copyright (C) 2001-2015
# Copyright (C) 2001-2018
# Alex Schroeder <alex@gnu.org>
# Copyright (C) 2014-2015
# Alex Jakimenko <alex.jakimenko@gmail.com>
@@ -35,6 +35,7 @@ use warnings;
no warnings 'numeric';
no warnings 'uninitialized';
use utf8; # in case anybody ever adds UTF8 characters to the source
use B;
use CGI qw/-utf8/;
use CGI::Carp qw(fatalsToBrowser);
use File::Glob ':glob';
@@ -124,6 +125,7 @@ our $DeletedPage = 'DeletedPage'; # Pages starting with this can be deleted
our $RCName = 'RecentChanges'; # Name of changes page
our @RcDays = qw(1 3 7 30 90); # Days for links on RecentChanges
our $RcDefault = 30; # Default number of RecentChanges days
our $KeepHostDays = 4; # Days to keep IP numbers for
our $KeepDays = 0; # Days to keep old revisions (0 means keep forever)
our $KeepMajor = 1; # 1 = keep at least one major rev when expiring pages
our $SummaryHours = 4; # Hours to offer the old subject when editing a page
@@ -1324,12 +1326,12 @@ sub DoBrowseRequest {
eval { local $SIG{__DIE__}; MyActions(); };
} elsif ($action) {
ReportError(Ts('Invalid action parameter %s', $action), '501 NOT IMPLEMENTED');
} elsif (GetParam('match', '') ne '') {
SetParam('action', 'index'); # make sure this gets a NOINDEX
DoIndex();
} elsif (GetParam('search', '') ne '') { # allow search for "0"
SetParam('action', 'search'); # make sure this gets a NOINDEX
DoSearch();
} elsif (GetParam('match', '') ne '') {
SetParam('action', 'index'); # make sure this gets a NOINDEX
DoIndex();
} elsif (GetParam('title', '') and not GetParam('Cancel', '')) {
DoPost(GetParam('title', ''));
} else {
@@ -1698,15 +1700,35 @@ sub RcHeader {
return $html;
}
sub GetScriptUrlWithRcParameters {
my $url = "$ScriptName?action=rss";
foreach my $param (qw(from upto days all showedit rollback rcidonly rcuseronly
rchostonly rcclusteronly rcfilteronly match lang
followup page diff full)) {
my $val = GetParam($param, undef);
$url .= ";$param=$val" if defined $val;
sub RcOtherParameters {
my $more = '';
foreach (@_, qw(page diff full all showedit rollback rcidonly rcuseronly rchostonly rcclusteronly rcfilteronly match lang followup)) {
my $val = GetParam($_, '');
$more .= ";$_=$val" if $val;
}
return $url;
return $more;
}
sub RcSelfAction {
my $action = GetParam('action', 'rc');
return "action=$action" . RcOtherParameters(qw(from upto days));
}
sub RcPreviousAction {
my $action = GetParam('action', 'rc');
my $interval = GetParam('days', $RcDefault) * 86400;
# use delta between from and upto, or use days, whichever is available
my $to = GetParam('from', GetParam('upto', $Now - $interval));
my $from = $to - (GetParam('upto') ? GetParam('upto') - GetParam('from') : $interval);
return "action=$action;from=$from;upto=$to" . RcOtherParameters();
}
sub RcLastAction {
my $action = GetParam('action', 'rc');
my $more = "action=$action";
my $days = GetParam('days', $RcDefault);
$more .= ";days=$days" if $days != $RcDefault;
return $more . RcOtherParameters();
}
sub GetFilterForm {
@@ -1757,7 +1779,7 @@ sub RcHtml {
$languages, $cluster, $last) = @_;
my $all_revision = $last ? undef : $revision; # no revision for the last one
$host = QuoteHtml($host);
my $author = GetAuthorLink($host, $username);
my $author = GetAuthorLink($username, $host);
my $sum = $summary ? $q->span({class=>'dash'}, ' &#8211; ')
. $q->strong(QuoteHtml($summary)) : '';
my $edit = $minor ? $q->em({class=>'type'}, T('(minor)')) : '';
@@ -1796,16 +1818,7 @@ sub RcHtml {
};
ProcessRcLines($printDailyTear, $printRCLine);
$html .= '</ul>' if $inlist;
# use delta between from and upto, or use days, whichever is available
my $to = GetParam('from', GetParam('upto', $Now - GetParam('days', $RcDefault) * 86400));
my $from = $to - (GetParam('upto') ? GetParam('upto') - GetParam('from') : GetParam('days', $RcDefault) * 86400);
my $more = "action=rc;from=$from;upto=$to";
foreach (qw(all showedit rollback rcidonly rcuseronly rchostonly
rcclusteronly rcfilteronly match lang followup)) {
my $val = GetParam($_, '');
$more .= ";$_=$val" if $val;
}
$html .= $q->p({-class=>'more'}, ScriptLink($more, T('More...'), 'more'));
$html .= $q->p({-class=>'more'}, ScriptLink(RcPreviousAction(), T('More...'), 'more'));
return GetFormStart(undef, 'get', 'rc') . $html . $q->end_form;
}
@@ -1840,7 +1853,7 @@ sub RcTextRevision {
: ($UsePathInfo ? '/' : '?') . UrlEncode($id));
print "\n", RcTextItem('title', NormalToFree($id)),
RcTextItem('description', $summary),
RcTextItem('generator', GetAuthor($host, $username)),
RcTextItem('generator', GetAuthor($username)),
RcTextItem('language', join(', ', @{$languages})), RcTextItem('link', $link),
RcTextItem('last-modified', TimeToW3($ts)),
RcTextItem('revision', $revision),
@@ -1882,8 +1895,9 @@ sub GetRcRss {
my $title = QuoteHtml($SiteName) . ': ' . GetParam('title', QuoteHtml(NormalToFree($HomePage)));
$rss .= "<title>$title</title>\n";
$rss .= "<link>" . ScriptUrl($HomePage) . "</link>\n";
$rss .= qq{<atom:link href="} . GetScriptUrlWithRcParameters()
. qq{" rel="self" type="application/rss+xml" />\n};
$rss .= qq{<atom:link href="$ScriptName?} . RcSelfAction() . qq{" rel="self" type="application/rss+xml" />\n};
$rss .= qq{<atom:link href="$ScriptName?} . RcPreviousAction() . qq{" rel="previous" type="application/rss+xml" />\n};
$rss .= qq{<atom:link href="$ScriptName?} . RcLastAction() . qq{" rel="last" type="application/rss+xml" />\n};
if ($SiteDescription) {
$rss .= "<description>" . QuoteHtml($SiteDescription) . "</description>\n"
}
@@ -1925,7 +1939,6 @@ sub RssItem {
}
my $date = TimeToRFC822($ts);
$username = QuoteHtml($username);
$username ||= $host;
my $rss = "<item>\n";
$rss .= "<title>$name</title>\n";
my $link = ScriptUrl(GetParam('all', $cluster)
@@ -2034,8 +2047,7 @@ sub GetHistoryLine {
$html .= ' ' . GetOldPageLink('browse', $id, $revision,
Ts('Revision %s', $revision));
}
my $host = $data{host} || $data{ip};
$html .= T(' . . . .') . ' ' . GetAuthorLink($host, $data{username});
$html .= T(' . . . .') . ' ' . GetAuthorLink($data{username});
$html .= $q->span({class=>'dash'}, ' &#8211; ')
. $q->strong(QuoteHtml($data{summary})) if $data{summary};
$html .= ' ' . $q->em({class=>'type'}, T('(minor)')) . ' ' if $data{minor};
@@ -2197,26 +2209,37 @@ sub ScriptLinkDiff {
return ScriptLink($action, $text, 'diff');
}
sub ColorCode {
my ($str) = @_;
my $num = unpack("L",B::hash($str)); # 32-bit integer
my $code = sprintf("%o", $num); # octal is 0-7
my @indexes = split(//, substr($code, 0, 4)); # four numbers
my @colors = qw/red orange yellow green blue indigo violet white/;
return $q->span({-class => 'ip-code', -title => T('Anonymous')},
join('', map { $q->span({-class => $colors[$_]}, $_) }
@indexes));
}
sub GetAuthor {
my ($host, $username) = @_;
return $username . ' ' . Ts('from %s', $host) if $username and $host;
my ($username) = @_;
return $username if $username;
return T($host); # could be 'Anonymous'
return T('Anonymous');
}
sub GetAuthorLink {
my ($host, $username) = @_;
my ($username, $host) = @_;
$username = FreeToNormal($username);
my $name = NormalToFree($username);
if (ValidId($username) ne '') { # ValidId() returns error string
$username = ''; # Just pretend it isn't there.
}
if ($username and $RecentLink) {
return ScriptLink(UrlEncode($username), $name, 'author', undef, $host);
return ScriptLink(UrlEncode($username), $name, 'author');
} elsif ($username) {
return $q->span({-class=>'author'}, $name);
}
return T($host); # could be 'Anonymous'
return T('Anonymous') if $host eq 'Anonymous';
return ColorCode($host);
}
sub GetHistoryLink {
@@ -2437,7 +2460,7 @@ sub GetFooterTimestamp {
$page //= \%Page;
if ($id and $rev ne 'history' and $rev ne 'edit' and $page->{revision}) {
my @elements = (($rev eq '' ? T('Last edited') : T('Edited')), TimeToText($page->{ts}),
Ts('by %s', GetAuthorLink($page->{host}, $page->{username})));
Ts('by %s', GetAuthorLink($page->{username})));
push(@elements, ScriptLinkDiff(2, $id, T('(diff)'), $rev)) if $UseDiff and $page->{revision} > 1;
return $q->div({-class=>'time'}, @elements);
}
@@ -3473,8 +3496,8 @@ sub SearchTitleAndBody {
foreach my $id (Filtered($regex, AllPagesList())) {
my $name = NormalToFree($id);
my ($text) = PageIsUploadedFile($id); # set to mime-type if this is an uploaded file
local ($OpenPageName, %Page); # this is local!
if (not $text) { # not uploaded file, therefore allow searching of page body
local ($OpenPageName, %Page); # this is local!
OpenPage($id); # this opens a page twice if it is not uploaded, but that's ok
if ($lang) {
my @languages = split(/,/, $Page{languages});
@@ -3490,8 +3513,11 @@ sub SearchTitleAndBody {
return @found;
}
sub Filtered { # this is overwriten in extensions such as tags.pl
return @_[1 .. $#_]; # ignores $regex and returns all pages
sub Filtered { # this is overwriten in extensions such as tags.pl
my ($string, @pages) = @_;
my $match = GetParam('match', '');
@pages = grep /$match/i, @pages if $match;
return @pages;
}
sub SearchString {
@@ -3526,20 +3552,19 @@ sub PrintSearchResult {
$entry{size} = int((length($text) / 1024) + 1) . 'K';
$entry{'last-modified'} = TimeToText($Page{ts});
$entry{username} = $Page{username};
$entry{host} = $Page{host};
PrintSearchResultEntry(\%entry);
}
sub PrintSearchResultEntry {
my %entry = %{(shift)}; # get value from reference
if (GetParam('raw', 0)) {
$entry{generator} = GetAuthor($entry{host}, $entry{username});
foreach my $key (qw(title description size last-modified generator username host)) {
$entry{generator} = GetAuthor($entry{username});
foreach my $key (qw(title description size last-modified generator username)) {
print RcTextItem($key, $entry{$key});
}
print RcTextItem('link', "$ScriptName?$entry{title}"), "\n";
} else {
my $author = GetAuthorLink($entry{host}, $entry{username});
my $author = GetAuthorLink($entry{username});
$author ||= $entry{generator};
my $id = $entry{title};
my ($class, $resolved, $title, $exists) = ResolveId($id);
@@ -3593,8 +3618,8 @@ sub ReplaceAndSave {
RequestLockOrError(); # fatal
my @result = Replace($from, $to, 1, sub {
my ($id, $new) = @_;
Save($id, $new, $from . ' → ' . $to, 1, ($Page{host} ne $q->remote_addr()));
});
Save($id, $new, $from . ' → ' . $to, 1);
});
ReleaseLock();
return @result;
}
@@ -3713,9 +3738,7 @@ sub DoPost {
}
my $newAuthor = 0;
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 $q->remote_addr() or not $Page{host} or $q->remote_addr() ne $Page{host};
}
my $oldtime = $Page{ts};
my $myoldtime = GetParam('oldtime', ''); # maybe empty!
@@ -3789,7 +3812,6 @@ sub AddComment {
sub Save { # call within lock, with opened page
my ($id, $new, $summary, $minor, $upload) = @_;
my $user = GetParam('username', '');
my $host = $q->remote_addr();
my $revision = $Page{revision} + 1;
my $old = $Page{text};
my $olddiff = $Page{'diff-major'} == '1' ? $Page{'diff-minor'} : $Page{'diff-major'};
@@ -3805,8 +3827,8 @@ sub Save { # call within lock, with opened page
ExpireKeepFiles();
$Page{lastmajor} = $revision unless $minor;
$Page{lastmajorsummary} = $summary unless $minor;
@Page{qw(ts revision summary username host minor text)} =
($Now, $revision, $summary, $user, $host, $minor, $new);
@Page{qw(ts revision summary username minor text)} =
($Now, $revision, $summary, $user, $minor, $new);
if ($UseDiff and $UseCache > 1 and $revision > 1 and not $upload and not TextIsFile($old)) {
UpdateDiffs($old, $new, $olddiff); # sets diff-major and diff-minor
}
@@ -3817,6 +3839,7 @@ sub Save { # call within lock, with opened page
if ($revision == 1 and $LockOnCreation{$id}) {
WriteStringToFile(GetLockedPageFile($id), 'LockOnCreation');
}
my $host = $q->remote_addr();
WriteRcLog($id, $summary, $minor, $revision, $user, $host, $languages, GetCluster($new));
AddToIndex($id) if ($revision == 1);
}
@@ -3890,7 +3913,7 @@ sub DoMaintain {
RequestLockOrError();
print $q->p(T('Main lock obtained.'));
print $q->p(Ts('Moving part of the %s log file.', $RCName));
# Determine the number of days to go back
# Determine the number of days to go back, default is largest of @RcDays
my $days = 0;
foreach (@RcDays) {
$days = $_ if $_ > $days;
@@ -3901,23 +3924,36 @@ sub DoMaintain {
if (not $status) {
print $q->p($q->strong(Ts('Could not open %s log file', $RCName) . ':') . ' ' . $RcFile),
$q->p(T('Error was') . ':'), $q->pre($!), $q->p(T('Note: This error is normal if no changes have been made.'));
} else {
WriteStringToFile($RcFile . '.old', $data);
}
# Move the old stuff from rc to temp
my @rc = split(/\n/, $data);
my @tmp = ();
for my $line (@rc) {
my $line;
my $changed = 0;
while ($line = shift(@rc)) {
my ($ts, $id, $minor, $summary, $host, @rest) = split(/$FS/, $line);
last if $ts >= $starttime;
push(@tmp, join($FS, $ts, $id, $minor, $summary, 'Anonymous', @rest));
$changed = 1;
}
unshift(@rc, $line) if $line; # this one ended the loop
print $q->p(Ts('Moving %s log entries.', scalar(@tmp)));
if (@tmp) {
# Write new files, and backups
AppendStringToFile($RcOldFile, join("\n", @tmp) . "\n");
WriteStringToFile($RcFile . '.old', $data);
splice(@rc, 0, scalar(@tmp)); # strip
WriteStringToFile($RcFile, @rc ? join("\n", @rc) . "\n" : '');
AppendStringToFile($RcOldFile, join("\n", @tmp) . "\n") if @tmp;
# remove IP numbers from all but the last few days
$starttime = $Now - $KeepHostDays * 86400; # 24*60*60
@tmp = ();
while ($line = shift(@rc)) {
my ($ts, $id, $minor, $summary, $host, @rest) = split(/$FS/, $line);
last if $ts >= $starttime;
push(@tmp, join($FS, $ts, $id, $minor, $summary, 'Anonymous', @rest));
$changed = 1;
}
unshift(@rc, $line) if $line; # this one ended the loop
unshift(@rc, @tmp) if @tmp;
print $q->p(Ts('Removing IP numbers from %s log entries.', scalar(@tmp)));
WriteStringToFile($RcFile, @rc ? join("\n", @rc) . "\n" : '') if $changed;
if (opendir(DIR, $RssDir)) { # cleanup if they should expire anyway
foreach (readdir(DIR)) {
Unlink("$RssDir/$_") if $Now - Modified($_) > $RssCacheHours * 3600;
@@ -4076,8 +4112,8 @@ sub TextIsFile { $_[0] =~ /^#FILE (\S+) ?(\S+)?\n/; }
sub AddModuleDescription { # cannot use $q here because this is module init time
my ($filename, $page, $dir, $tag) = @_;
my $src = "http://git.savannah.gnu.org/cgit/oddmuse.git/tree/modules/$dir" . UrlEncode($filename) . ($tag ? '?' . $tag : '');
my $doc = 'https://www.oddmuse.org/cgi-bin/oddmuse/' . UrlEncode(FreeToNormal($page));
my $src = "https://alexschroeder.ch/cgit/oddmuse/tree/modules/$dir" . UrlEncode($filename) . ($tag ? '?' . $tag : '');
my $doc = 'https://www.oddmuse.org/wiki/' . UrlEncode(FreeToNormal($page));
$ModulesDescription .= "<p><a href=\"$src\">" . QuoteHtml($filename) . "</a>" . ($tag ? " ($tag)" : '');
$ModulesDescription .= T(', see') . " <a href=\"$doc\">" . QuoteHtml($page) . "</a>" if $page;
$ModulesDescription .= "</p>";