Compare commits

..

154 Commits

Author SHA1 Message Date
Alex Schroeder
1e223279d9 common-mark: small fixes
Down to 446 failed tests.

Fixed attribute names for HTML.
Fixed regexp for unquoted parts in HTML.
Added beginning of image link handling.
Fixed regexp for single backquote code.
2020-06-02 23:25:46 +02:00
Alex Schroeder
a69d28596f common-mark: down to 450 failed tests 2020-06-01 09:31:11 +02:00
Alex Schroeder
fb77a7a326 common-mark: down to 470 failed tests 2020-06-01 09:31:11 +02:00
Alex Schroeder
244d06ca3b gemini-server: fix recent changes and rss 2020-05-31 19:40:51 +02:00
Alex Schroeder
1a59075b51 gemini-server: new 2020-05-31 00:34:59 +02:00
Alex Schroeder
e0b3c18499 mail: allow unsubscription from all pages
Fix unsubscription from all pages when no list of pages has been
provided: do not return early.
2020-05-27 08:36:34 +02:00
Alex Schroeder
5434136a4d mail: allow unsubscription from all pages
Simplify code. Fix display of email list: URL decoding email addresses
and printing the email addresses in the list. Fix the feedback message
after unsubscribing somebody from all pages.
2020-05-27 08:32:39 +02:00
Alex Schroeder
aeeb182dad mail: allow unsubscription from all pages
A new action (subscribers) also just lists email addresses to make
this easier.
2020-05-27 07:54:07 +02:00
Alex Schroeder
4d6882ffc7 ip-to-regexp.pl: new command line script
Helps to block entire IP blocks.
2020-05-27 07:51:56 +02:00
Alex Schroeder
828482f439 webmention: various updates
scripts/webmention.pl now extracts the author of FROM

modules/webmention.pl now is less complicated about skipping
webmention endpoint advertising and it tries to use the author and
link advertised from the incoming webmention page

And the tests were amended.
2020-05-03 21:09:30 +02:00
Alex Schroeder
2b6f2dfa0c pygmentize: add a test 2020-05-03 14:33:58 +02:00
Alex Schroeder
413b43174c webmention: avoid warning about CGI::param
We're using it in list context, now, and that generates a warning.
Using multi_param avoids this.
2020-05-03 11:49:19 +02:00
Alex Schroeder
9709c87185 webmention: better UI 2020-05-03 10:54:50 +02:00
Alex Schroeder
0cca358de2 webmention: add UI 2020-05-03 10:02:44 +02:00
Alex Schroeder
acff0cb69f Add tests for grep-filtered 2020-03-19 20:45:14 +01:00
Alex Schroeder
8d5956cb7f grep-filtered: old/new module 2020-03-19 14:07:59 +01:00
Alex Schroeder
e9773ea694 Add wordcount module 2020-02-06 14:01:33 +01:00
Alex Schroeder
7094ec098b stuff/server: fix UTF-8 encoding problem
Sadly, I would have thought that the correct solution is the
following, at the end, once we have $server:

$server->cgi_init(
  sub {
    require CGI;
    CGI::initialize_globals();
    CGI->import(qw(-utf8));
  });

This is based on the documentation of HTTP::Server::Simple::CGI, and
skimming its source code, and the documentation of the use pragma, and
the pseudo code in its documentation. The workaround of setting
$CGI::PARAM_UTF8 directly is based on reading the CGI.pm source code.
This is definitely a hack.
2019-12-28 18:11:49 +01:00
Alex Schroeder
04fe4d6991 stuff/server: better instructions at the beginning 2019-12-28 13:59:55 +01:00
Alex Schroeder
570a6b1f07 stuff/server: use ./wiki.pl instead of wiki.pl 2019-12-28 13:45:19 +01:00
Alex Schroeder
4211d6aa03 definition-lists: new 2019-10-14 22:36:32 +02:00
Alex Schroeder
5941fcd7e6 recaptcha: fix tests
check_answer_v2 requires an answer or it throws the error "To check
answer, the user response token must be provided". That causes all the
tests to fail. Thus, if no answer is provided, we return 0 (false).
2019-10-06 18:04:44 +02:00
Alex Schroeder
f3df2eb289 Merge pull request #21 from fancypantalons/master
Switch to reCAPTCH v2 API.
2019-10-06 09:00:03 +02:00
Brett Kosinski
8b975cd5e5 Switch to reCAPTCH v2 API.
The v1 API has been deprecated, so this changeset switches to using the
v2 API as exposed by the Captch::reCAPTCHA module.

The main changes are to switch to the _v2 methods for get_html and
check_answer, plus necessary changes to the check_answer parameters to
make the code compatible with the new form field naming.
2019-10-05 19:37:27 -06:00
Alex Schroeder
850f292260 rc2mail: switch to Net::SMTP and Authen::SASL 2019-10-04 18:33:32 +02:00
Alex Schroeder
ced1252b9d new-utf8: new modules add new translations 2019-10-04 18:32:14 +02:00
Alex Schroeder
c0a6bfebf2 Add language to body element and various divs
The benefit is that this allows us to use the CSS rule that allows the
browser to hyphenate words:

body { hyphens: auto }

This required a number of changes.

wiki.pl

- new option, $CurrentLanguage
- GetHtmlHeader adds lang attribute to body
- PrintAllPages adds lang attribute to div
- PrintPageContent adds lang attribute to div
- PageHtml adds div with lang attribute
- GetLanguages sorts languages by occurences
- GetLanguage is new and returns the first language of GetLanguages

Modules that had to be changed because they refer to the divs that
have now been changed:

- crossbar.pl

Tests that had to be changed:

- atom.t
- crossbar.t
- hr.t
- languages.t
- portrait-support.t
- rss.t
2019-09-27 21:34:32 +02:00
Alex Schroeder
36815767f3 markdown-rule: allow to set text-align style 2019-09-19 10:38:47 +02:00
Alex Schroeder
2cba2d30b7 Makefile: add a morbo target
This makes it easy to quickly deploy the wiki standalone using
Mojolicious. It reloads automatically when any of the relevant files
are changed. This might be easier to work with than a local webserver
installation.
2019-07-16 23:02:10 +02:00
Alex Schroeder
fded17520c namespaces: add test for numerical namespaces 2019-07-16 22:58:43 +02:00
Alex Schroeder
46580a3958 gopher-server: fix linebreaks in links 2019-06-24 12:08:36 +02:00
Alex Schroeder
d584899447 markdown-rule: test removal of MarkdownExtraRule 2019-06-24 11:03:53 +02:00
Alex Schroeder
79c0a9fd02 gopher-server: fix test
The test didn't specify a host explicitly. On my systems, that meant
"localhost" sometimes ended up being "127.0.0.1" or "::1" but for
Alexine it ended up being "::ffff:127.0.0.1". Let's hope that
explicitly setting "127.0.0.1" fixes all of this.
2019-06-24 10:50:14 +02:00
Alex Schroeder
c3f21d60c2 markdown-rule: fix > block quoting 2019-06-24 10:29:02 +02:00
Alex Schroeder
43221ea8a9 markdown-rule: fix """ quotes 2019-06-24 10:19:33 +02:00
Alex Schroeder
25350e93cb markdown-rule: allow newlines in link text
Also added """ for blockquotes.
2019-06-21 23:51:50 +02:00
Alex Schroeder
415a8aa9a8 gopher-server: more links, more tests 2019-06-20 14:27:32 +02:00
Alex Schroeder
a3d740aa67 gopher-server: Gopher URLs can have no type 2019-06-20 12:35:04 +02:00
Alex Schroeder
1956335640 Support gophers URL scheme 2019-06-20 12:31:31 +02:00
Alex Schroeder
b89c8b99e8 Example configs for Mojolicious 2019-06-10 15:02:10 +02:00
Alex Schroeder
4e4d8ee784 Webmention: new feature
This deprecates pingback-server!
2019-05-24 21:43:25 +02:00
Alex Schroeder
7ecd7b784e t: updated copyright years 2019-05-08 08:01:54 +02:00
Alex Schroeder
0d9764b0de gopher-server: fix space normalisation in links 2019-05-04 18:33:36 +02:00
Alex Schroeder
5d7964977c tags: hide the taglist action 2019-04-13 19:33:36 +02:00
Alex Schroeder
d50cda9cea tags: add $TagCloudSize 2019-04-13 19:31:32 +02:00
Alex Schroeder
7257bfb6d5 tags.pl: use HTML::TagCloud to generate cloud 2019-04-13 19:21:55 +02:00
Alex Schroeder
f2470256ae server: fix shebang line with better env path 2019-04-13 19:21:55 +02:00
Alex Schroeder
3c7a56ba5a gopher-server: documentation in the README
And:

- fix typo in the comments of gopher-server.pl
- add log level 4 output to debug how the data directory is sets
2019-04-13 19:21:55 +02:00
Alex Schroeder
07d68b4400 journal-rss: fix tests 2019-03-20 12:12:08 +01:00
Alex Schroeder
51d2d3e0ca tarballs: support "latest" release 2019-03-19 14:50:43 +01:00
Alex Schroeder
0a361873de Fix RSS links in feed
Trying to fix all the issues the validator noticed.
2019-03-19 14:47:39 +01:00
Alex Schroeder
6af02a8a1e Fix quoting of RC and RSS link parameters
When generating RC and RSS link parameters, the values of the
parameters were not URL encoded. This is now fixed.
2019-03-19 14:46:29 +01:00
Alex Schroeder
dc283ea828 tarballs: use Sort::Versions 2019-03-02 14:44:36 +01:00
Alex Schroeder
8e3917b933 rename-pages: add a space in front of the button 2019-03-02 14:27:06 +01:00
Alex Schroeder
5a2209a0c2 rename-pages: fix 'for' attribute for label 2019-03-01 21:00:04 +01:00
Alex Schroeder
73b6949b95 meta: Fix text for 'rename' 2019-03-01 20:59:50 +01:00
Alex Schroeder
771b07e661 rename-pages: new 2019-03-01 20:34:11 +01:00
Alex Schroeder
629693e353 Buf fix in DoMaintain
Frank Betten noted about the old code "$Now - Modified($fname) < 0.5"
that the "left-hand side is in seconds, right-hand side in days".
This was fixed by replacing 0.5 with 12 * 60 * 60.
2019-02-11 21:29:49 +01:00
Alex Schroeder
a45e13f74c pingback-client: add license and intro paragraph 2019-02-01 23:28:03 +01:00
Alex Schroeder
a919f4b8fd Add pingback-client for some experimenting 2019-01-29 13:56:14 +01:00
Alex Schroeder
6d37baabd9 Add pingback-server tests
Also change the HTTP status messages from the pingback-server for
easier debugging. After all, the message text can be whatever we want.
In order to know which of code 400 errors we ran into, use different
messages for each.
2019-01-27 20:07:31 +01:00
Alex Schroeder
41f18d0b3e Make sure we can start multiple servers in tests 2019-01-27 20:06:53 +01:00
Alex Schroeder
8a5cce103e pingback-server: complete rewrite 2019-01-26 23:38:44 +01:00
Alex Schroeder
65cfd93de9 Fix @IndexOptions
Frank Betten reported that after installing the module for Permanent
Anchors the parameter permanentanchors has no effect and there's no
checkbox on the index page. The reason is that Permanent Anchors used
to add to @IndexOptions upon loading (InitModules), but commit
15263102 had moved the setting of @IndexOptions into InitVariables,
thus overwriting any changes made during InitModules. In order to have
an effect, @IndexOptions has to be modified via @MyInitVariables. All
of this is necessary because @IndexOptions uses translated strings and
these are themselves loaded via modules so setting @IndexOptions at
InitModules time can be too early. This was fixed with commit deec99c,
necessitating all these follow-up changes.
2019-01-24 22:22:45 +01:00
Alex Schroeder
a67cac80f5 More support for microformats
On Journal pages, add the classes h-feed for the entire feed, h-entry
for a single page, entry-title for the header, and entry-content for
the page content.
2019-01-20 21:01:35 +01:00
Alex Schroeder
c73f224f2d Allow - in image classes for hCard compatibility 2019-01-19 12:09:39 +01:00
Alex Schroeder
f241d7cd77 Format the summaries of changes in a diff
Make sure to always call QuoteHtml on the summary to avoid XSS
attacks.
2019-01-19 09:33:07 +01:00
Alex Schroeder
a5f5607667 Format the summaries of changes in a diff
Make sure entries consisting of nothing but whitespace are also
skipped.
2019-01-18 21:09:07 +01:00
Alex Schroeder
2d7451402f Format the summaries of changes in a diff
When there is more than one summary, use a list.
2019-01-18 20:15:07 +01:00
Alex Schroeder
7e13816d40 diff.t: removed duplicate tests
The last commit duplicated some tests. This commit simply removes them
again and fixes the number of tests to run.
2019-01-18 19:21:50 +01:00
Alex Schroeder
c2231b9e39 Show all the summaries of changes in a diff 2019-01-18 19:19:42 +01:00
Alex Schroeder
58ecb96975 Add UplinkedIntelligence
Set $HomePage
2018-12-10 13:40:19 +01:00
Alex Schroeder
d24b7d8bd3 mail: allow unsubscription from non-existing pages 2018-12-09 18:01:33 +01:00
Alex Schroeder
91e054cea4 wiki: fix RSS 3.0 and support full parameter 2018-11-30 23:49:30 +01:00
Alex Schroeder
be2abffde2 Revert "gopher-server: add support for RSS Gopher Module"
This reverts commit 93b706dd28.
2018-11-30 22:18:05 +01:00
Alex Schroeder
93b706dd28 gopher-server: add support for RSS Gopher Module 2018-11-30 16:53:52 +01:00
Alex Schroeder
8f43c818df journal-rss: fix bug
The old code assumed that a kept revision for the last major revision
would always exist. But if you just create a page using a minor edit, it
still gets shown, triggering a call to die. This fixes it.
2018-11-26 08:58:21 +01:00
Alex Schroeder
8194d6bb81 permanent-anchors: fixed page deletion
NewPermanentAnchorsDeletePage called DeletePermanentAnchors which
ended with ReleaseLockDir and that is wrong if a lock exists: it’ll
return the value in %Locks which is going to be 1. Thus, with this
module installed, pages would get deleted, but maintenance would still
print: "X not deleted: 1". We fix this by return the empty string from
NewPermanentAnchorsDeletePage.
2018-11-23 18:55:25 +01:00
Alex Schroeder
dc203917b9 german-utf8: updated translation 2018-11-21 10:15:51 +01:00
Alex Schroeder
e7818ef43e translations: updated with new keys 2018-11-21 10:12:24 +01:00
Alex Schroeder
37c4a19f1f Makefile: sort modules
This makes sure that translations have small diffs.
2018-11-21 10:11:48 +01:00
Alex Schroeder
864f294761 translation-stats: fix @INC 2018-11-21 09:57:05 +01:00
Alex Schroeder
1534d6dd8f markdown-convert: all users may use the menu 2018-11-21 09:40:03 +01:00
Alex Schroeder
9ae728bf13 markdown-convert: fix another hungry regexp 2018-11-17 00:03:28 +01:00
Alex Schroeder
768244860f markdown-convert: inline code, fixed 2018-11-17 00:01:18 +01:00
Alex Schroeder
95e566ae97 markdown-conert: inline code 2018-11-16 23:55:21 +01:00
Alex Schroeder
040545269a markdown-conert: fix subheadings
Don't require equal signs at the end; be flexible with spaces at the
beginning.
2018-11-16 23:52:07 +01:00
Alex Schroeder
c6b7d952bd markdown-conert: fix multiple code blocks 2018-11-16 23:48:52 +01:00
Alex Schroeder
06067dd1c1 markdown-convert: strip marker from HTML
On campaignwiki.org we use the code
https://oddmuse.org/wiki/Creole_or_Markdown which also strips the
marker, but we want to support people who don't install that code.
2018-11-16 13:39:36 +01:00
Alex Schroeder
6ee6b384cf markdown-convert: skip uploaded files 2018-11-16 13:17:36 +01:00
Alex Schroeder
7cdf2abe2d markdown-convert: use utf8 2018-11-16 13:11:28 +01:00
Alex Schroeder
942d77f599 markdown-convert: use ordered list for candidates 2018-11-16 13:10:16 +01:00
Alex Schroeder
71baa51a54 markdown-convert: add option to list unconverted pages 2018-11-16 13:05:54 +01:00
Alex Schroeder
8819183be3 markdown-convert: fix space issue 2018-11-16 12:43:17 +01:00
Alex Schroeder
0941a09c19 markdown-convert: fix AddModuleDescription 2018-11-16 12:30:12 +01:00
Alex Schroeder
eae48212be markdown-convert: Add #MARKDOWN marker 2018-11-16 11:45:19 +01:00
Alex Schroeder
4f9641c43f markdown-convert: new module 2018-11-15 22:18:02 +01:00
Alex Schroeder
32fa6057c7 gotobar: support $$id in the GotoBar page 2018-11-08 08:29:54 +01:00
Alex Schroeder
025907f65a gotobar: support $id in the GotoBar page 2018-11-08 08:12:53 +01:00
Alex Schroeder
a259edc0bb bbcode: add [br] 2018-10-21 10:01:48 +02:00
Alex Schroeder
94fd9c0c2a journal-rss: minor changes don't update pubDate
If this is a minor edit, let's keep everything as it is, but show the
date of the last major change, if possible. This is important for
blogs that get added to a Planet. A minor change doesn't mean that the
page needs to go to the front of the Planet.
2018-10-17 23:09:10 +02:00
Alex Schroeder
0affd5a1b7 rc2mail: suppress one more print if -q is given 2018-10-07 18:15:30 +02:00
Alex Schroeder
f27ab9a8a0 gopher-server: support gopher tags
See gopher://circumlunar.space:70/0/~solderpunk/phlog/tagged-gophers.txt
2018-10-07 15:39:51 +02:00
Alex Schroeder
086a992b5a olocalmap.pl: use GetId 2018-09-11 13:11:44 +02:00
Alex Schroeder
54cae9bc1d $SurgeProtection is used by gopher-server.pl 2018-09-10 12:15:02 +02:00
Alex Schroeder
9d9c196441 Add comment, removed unused $SurgeProtection 2018-09-10 11:29:56 +02:00
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
132 changed files with 12232 additions and 665 deletions

View File

@@ -4,7 +4,7 @@
VERSION_NO=$(shell git describe --tags)
TRANSLATIONS=$(wildcard modules/translations/[a-z]*-utf8.pl$)
MODULES=$(wildcard modules/*.pl)
MODULES=$(sort $(wildcard modules/*.pl))
BUILD=build/wiki.pl $(foreach file, $(notdir $(MODULES)) $(notdir $(TRANSLATIONS)), build/$(file))
# PREPARE/BUILD: this creates copies of wiki.pl and all the modules
@@ -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" < $< > $@
@@ -60,3 +60,10 @@ jobs ?= 4
test:
prove t/setup.pl
prove --jobs=$(jobs) --state=slow,save t
# Spin up a quick test
development:
morbo --listen http://*:8080 \
--watch wiki.pl --watch test-data/config --watch test-data/modules/ \
stuff/mojolicious-app.pl

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

View File

@@ -1,6 +1,6 @@
#! /usr/bin/perl
# Copyright (C) 20112015 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20112018 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
@@ -23,7 +23,8 @@ use utf8;
# load Oddmuse core
$RunCGI = 0;
do "wiki.pl";
$DataDir = '/home/alex/campaignwiki';
do "/home/alex/farm/wiki.pl";
# globals depending on the name of the script
my ($self, $name, $wiki);
@@ -35,6 +36,9 @@ if ($0 eq '/home/alex/campaignwiki.org/add-link.pl') {
$self = "https://campaignwiki.org/add-adventure";
$name = "OSR Links to Adventures";
$wiki = 'Adventures';
} elsif ($0 eq '/home/alex/campaignwiki.org/add-sf-link.pl') {
$name = "OSRSF House Rules Wiki: Uplinked Intelligence";
$wiki = 'UplinkedIntelligence';
} else {
ReportError('Cannot determine wiki!', '500 INTERNAL SERVER ERROR');
}
@@ -164,7 +168,7 @@ sub is_duplicate {
" already links to the URL you submitted:",
GetUrl($link->[0], $link->[1]));
return 1;
}
}
}
return 0;
}
@@ -288,11 +292,13 @@ sub print_end_of_page {
sub main {
$ConfigFile = "$DataDir/config"; # read the global config file
$ModuleDir = "$DataDir/modules"; # global modules
$DataDir = "$DataDir/$wiki"; # but link to the local pages
Init(); # read config file (no modules!)
Init(); # read config file
$ScriptName = $site; # undo setting in the config file
$FullUrl = $site; #
InitPageVariables(); # call again: $ScriptName was wrong
$HomePage = 'HomePage'; # $HomePage must not be translated
binmode(STDOUT,':utf8');
$q->charset('utf8');
if ($q->path_info eq '/source') {

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

@@ -32,7 +32,9 @@ sub bbCodeRule {
my $bbcode = $1;
my $tag = lc($2);
my $option = $3; # sanitize?
if ($tag eq 'b') {
if ($tag eq 'br') {
return $q->br(); }
elsif ($tag eq 'b') {
return AddHtmlEnvironment('b'); }
elsif ($tag eq 'i') {
return AddHtmlEnvironment('i'); }

2627
modules/common-markdown.pl Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -217,7 +217,7 @@ sub PrintPageContentCrossbar {
# If the crossbar div is placed immediately after the content div, place it
# immediately before the content div.
if (not ($html =~ s~(<div class="content browse">)$crossbar_pattern~$2$1~)) {
if (not ($html =~ s~(<div class="content browse" lang="[a-z]*">)$crossbar_pattern~$2$1~)) {
# Otherwise, if the crossbar div is placed immediately before the end of the
# content div, place it immediately after the end of the content div.
$html =~

View File

@@ -0,0 +1,37 @@
#! /usr/bin/perl
# Copyright (C) 2019 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 strict;
use v5.10;
AddModuleDescription('definition-lists.pl', 'Definition Lists Extension');
our ($q, $bol, @MyRules, @HtmlStack, $Fragment);
push(@MyRules, \&DefinitionListsRule);
# term
# : definition
sub DefinitionListsRule {
if ($bol and /\G(?:\s*\n)*(\S.*)\n[ \t]*:[ \t]*/cg) {
return OpenHtmlEnvironment('dl', 1) . "<dt>$1</dt>" . AddHtmlEnvironment('dd');
} elsif (InElement('dd') and /\G(?:\s*\n)+(\S.*)\n[ \t]*:[ \t]*/cg) {
return OpenHtmlEnvironment('dl', 1) . "<dt>$1</dt>" . AddHtmlEnvironment('dd');
} elsif (InElement('dd') and /\G(\s*\n)+[ \t]*:[ \t]*/cg) {
return OpenHtmlEnvironment('dl', 1) . AddHtmlEnvironment('dd');
}
return;
}

View File

@@ -35,6 +35,8 @@ push(@MyInitVariables, \&GotobarInit);
sub GotobarInit {
$GotobarName = FreeToNormal($GotobarName); # spaces to underscores
$AdminPages{$GotobarName} = 1;
my $id = GetId();
my $encoded = UrlEncode($id);
if ($IndexHash{$GotobarName}) {
OpenPage($GotobarName);
return if PageMarkedForDeletion();
@@ -42,8 +44,11 @@ sub GotobarInit {
# links for unsuspecting users.
@UserGotoBarPages = ();
$UserGotoBar = '';
my $text = $Page{text};
$text =~ s/\$\$id\b/$encoded/g;
$text =~ s/\$id\b/$id/g;
my $count = 0;
while ($Page{text} =~ m/($LinkPattern|\[\[$FreeLinkPattern\]\]|\[\[$FreeLinkPattern\|([^\]]+)\]\]|\[$InterLinkPattern\s+([^\]]+?)\]|\[$FullUrlPattern[|[:space:]]([^\]]+?)\])/g) {
while ($text =~ m/($LinkPattern|\[\[$FreeLinkPattern\]\]|\[\[$FreeLinkPattern\|([^\]]+)\]\]|\[$InterLinkPattern\s+([^\]]+?)\]|\[$FullUrlPattern[|[:space:]]([^\]]+?)\])/g) {
my $page = $2||$3||$4||$6||$8;
my $text = $5||$7||$9;
$UserGotoBar .= ' ' if $UserGotoBar;
@@ -57,10 +62,10 @@ sub GotobarInit {
# is the list of recent changes.
$count++;
if ($count == 1) {
unless ($GotobarSetHome) {$HomePage = FreeToNormal($page)};
} elsif ($count == 2) {
unless ($GotobarSetRC) {$RCName = FreeToNormal($page);}
}
unless ($GotobarSetHome) {$HomePage = FreeToNormal($page)};
} elsif ($count == 2) {
unless ($GotobarSetRC) {$RCName = FreeToNormal($page);}
}
}
}
}

48
modules/grep-filtered.pl Normal file
View File

@@ -0,0 +1,48 @@
# Copyright (C) 2020 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2020 Daniel MacKay <daniel@bonmot.ca>
#
# 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 strict;
use v5.10;
AddModuleDescription('grep-filtered.pl', 'Grep Filtered');
our ($PageDir);
our ($UseGrep);
$UseGrep = 1;
*OldGrepFiltered = \&Filtered;
*Filtered = \&NewGrepFiltered;
sub NewGrepFiltered {
my ($string, @pages) = @_;
my @pages = OldGrepFiltered(@_);
my $regexp = SearchRegexp($string);
return @pages unless GetParam('grep', $UseGrep) and $regexp;
my @result = grep(/$regexp/i, @pages); # search parameter for page titles
my %found = map {$_ => 1} @result;
$regexp =~ s/\\n(\)*)$/\$$1/g; # sometimes \n can be replaced with $
$regexp =~ s/([?+{|()])/\\$1/g; # basic regular expressions from man grep
# if we know of any remaining grep incompatibilities we should
# return @pages here!
$regexp = quotemeta($regexp);
open(F, '-|:encoding(UTF-8)', "find $PageDir -type f -print0 | xargs -0 -n10 -P4 grep --ignore-case -l '$regexp'") ;
while (<F>) {
my ($pageName) = m/.*\/(.*)\.pg$/ ;
push(@result, $pageName) if not $found{$pageName};
} close(F);
return sort @result;
}

View File

@@ -29,7 +29,7 @@ push(@MyRules, \&ImageSupportRule);
sub ImageSupportRule {
my $result = undef;
if (m!\G\[\[image((/[a-z]+)*)( external)?:\s*([^]|]+?)\s*(\|[^]|]+?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*\]\](\{([^}]+)\})?!cg) {
if (m!\G\[\[image((/[-a-z]+)*)( external)?:\s*([^]|]+?)\s*(\|[^]|]+?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*(\|[^]|]*?)?\s*\]\](\{([^}]+)\})?!cg) {
my $oldpos = pos;
my $class = 'image' . $1;
my $external = $3;

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 .= ";$_=" . UrlEncode($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,27 @@ 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 $@;
# If this is a minor edit, let's keep everything as it is, but show the date
# of the last major change, if possible. This is important for blogs that
# get added to a Planet. A minor change doesn't mean that the page needs to
# go to the front of the Planet.
if ($Page{minor} and $Page{lastmajor}) {
my %major = GetKeptRevision($Page{lastmajor});
$Page{ts} = $major{ts} if $major{ts};
}
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 +106,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

@@ -1,4 +1,4 @@
# Copyright (C) 20092015 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20092020 Alex Schroeder <alex@gnu.org>
# Copyright (C) 2015 Aleks-Daniel Jakimenko <alex.jakimenko@gmail.com>
#
# This program is free software; you can redistribute it and/or modify it under
@@ -230,6 +230,10 @@ sub MailMenu {
ScriptLink('action=subscriptionlist',
T('All mail subscriptions'),
'subscriptionlist')) if UserIsAdmin();
push(@$menuref,
ScriptLink('action=subscribers',
T('All mail subscribers'),
'subscribers')) if UserIsAdmin();
}
=head1 Your subscriptions
@@ -283,9 +287,9 @@ sub MailSubscription {
=head1 Administrator Access
The subscriptionlist action will show you the subscription database,
if you're an administrator. It's a plain text file of the data, which
you can use for debugging and scripting purposes.
The C<subscriptionlist> action will show you the subscription database, if
you're an administrator. With the C<raw> parameter set it's a plain text file of
the data, which you can use for debugging and scripting purposes.
=cut
@@ -327,6 +331,41 @@ sub MailLink {
. join(';', map { "pages=$_" } @pages), $str);
}
=pod
The C<subscribers> action lists each unique email address for easier mass
unsubscribing of email addresses after a wave of wiki spam.
=cut
$Action{subscribers} = \&DoMailSubscribers;
sub DoMailSubscribers {
UserIsAdminOrError();
my $raw = GetParam('raw', 0);
if ($raw) {
print GetHttpHeader('text/plain');
} else {
print GetHeader('', T('Subscriptions')),
$q->start_div({-class=>'content subscribtionlist'}),
$q->p(T('Mail addresses are linked to unsubscription links.')),
'<ul>';
}
my %authors;
require DB_File;
tie my %h, "DB_File", encode_utf8($MailFile);
for my $author (sort grep /\@/, map { UrlDecode($_) } keys %h) {
if ($raw) {
print "$author\n";
} else {
print $q->li(ScriptLink("action=unsubscribe;who=$author", $author));
}
}
print '</ul></div>' unless $raw;
PrintFooter() unless $raw;
untie %h;
}
=head1 Subscription
The subscribe action will subscribe you to pages. The mail parameter
@@ -402,13 +441,13 @@ sub MailSubscribe {
=head1 Unsubscription
The unsubscribe action will unsubscribe you from pages. The mail
parameter contains the mail address to use and defaults to the value
store in your cookie. Multiple pages parameters contain the pages to
unsubscribe.
The unsubscribe action will unsubscribe you from pages. The mail parameter
contains the mail address to use and defaults to the value store in your cookie.
Multiple pages parameters contain the pages to unsubscribe. Without naming
pages, you will be unsubscribed from all pages.
The who parameter overrides the mail parameter and is used for
administrator unsubscription from the subscriptionlist action.
The who parameter overrides the mail parameter and is used for administrator
unsubscription from the subscriptionlist action.
=cut
@@ -416,21 +455,20 @@ $Action{unsubscribe} = \&DoMailUnsubscribe;
sub DoMailUnsubscribe {
my $mail = GetParam('who', GetParam('mail', ''));
return DoMailSubscriptions(@_) unless $mail;
local $CGI::LIST_CONTEXT_WARN = 0;
my @pages = $q->param('pages');
return DoMailSubscriptions(@_) unless $mail;
my @real = ();
foreach my $id (@pages) {
push @real, $id if $IndexHash{$id};
}
MailUnsubscribe($mail, @real);
MailUnsubscribe($mail, @pages);
# MailUnsubscribe will set a parameter and must run before printing
# the header.
print GetHeader('', T('Subscriptions')),
$q->start_div({-class=>'content unsubscribe'});
print $q->p(Ts('Unsubscribed %s from the following pages:', $mail));
print $q->ul($q->li([map { GetPageLink($_) } @real]));
print $q->p(T('The remaining pages do not exist.')) if $#real < $#pages;
$q->start_div({-class=>'content unsubscribe'});
if (@pages) {
print $q->p(Ts('Unsubscribed %s from the following pages:', $mail));
print $q->ul($q->li([map { GetPageLink($_) } @pages]));
} else {
print $q->p(Ts('Unsubscribed %s from all pages.', $mail));
}
print $q->p(ScriptLink('action=subscriptions', T('Your mail subscriptions'),
'subscriptions') . '.');
print $q->end_div();
@@ -439,10 +477,11 @@ sub DoMailUnsubscribe {
sub MailUnsubscribe {
my ($mail, @pages) = @_;
return unless $mail and @pages;
return unless $mail;
require DB_File;
tie my %h, "DB_File", encode_utf8($MailFile);
my %subscriptions = map {$_=>1} split(/$FS/, UrlDecode($h{UrlEncode($mail)}));
@pages = keys %subscriptions unless @pages;
foreach my $id (@pages) {
delete $subscriptions{$id};
# take care of reverse lookup

View File

@@ -0,0 +1,108 @@
#! /usr/bin/perl
# 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/>.
use strict;
use v5.10;
use utf8;
AddModuleDescription('markdown-converter.pl', 'Markdown Convert');
our (%Action, @MyAdminCode, $q, $OpenPageName, %Page, @MyRules);
push(@MyAdminCode, \&MarkdownConvertMenu);
sub MarkdownConvertMenu {
my ($id, $menuref, $restref) = @_;
my $name = $id;
$name =~ s/_/ /g;
if ($id) {
push(@$menuref, ScriptLink('action=convert;id=' . $id, Ts('Help convert %s to Markdown', $name), 'convert'));
}
push(@$menuref, ScriptLink('action=conversion-candidates', Ts('List all non-Markdown pages'), 'convert'));
}
$Action{convert} = \&MarkdownConvert;
# some text that doesn't start and end with a space, or just one non-space
sub MarkdownConvertString {
my $c = shift;
return qr"([^\\$c \n][^\\$c\n]*[^\\$c \n]|[^\\$c \n])";
}
sub MarkdownConvert {
my $id = GetParam('id', '');
ValidIdOrDie($id);
print GetHeader('', Ts('Converting %s', $id), '');
$_ = GetPageContent($id);
s/^\{\{\{((?:.*\n)+?)\}\}\}$/```$1```/gm;
s/\{\{\{(.+?)\}\}\}/`$1`/gm;
my $s = MarkdownConvertString('*');
s/\*$s\*/**$1**/g;
# avoid URL schemas like http://example.org
$s = MarkdownConvertString('/');
s#(?<!:/)/$s/#*$1*#g;
s#(?<!:)//$s//#*$1*#g;
s/^# /1. /gm;
s/##(.*?)##/`$1`/g;
s/^(=+) *(.*?) ?=*$/'#' x length($1) . ' ' . $2/gme;
s!\[(https?://\S+) (.*?)\]![$2]($1)!g;
return DoEdit($id, "#MARKDOWN\n" . $_, 1); # preview
}
$Action{'conversion-candidates'} = \&MarkdownConversionCandidates;
sub MarkdownConversionCandidates {
# from Search
print GetHeader('', Ts('Candidates for Conversion to Markdown'));
print $q->start_div({-class=>'content'});
print $q->start_ol();
# from SearchTitleAndBody
my $regex = qr'^(?!#MARKDOWN)';
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
OpenPage($id); # this opens a page twice if it is not uploaded, but that's ok
if ($Page{text} =~ /$regex/) {
my $action = 'action=convert;id=' . UrlEncode($id);
my $name = NormalToFree($id);
print $q->li(GetPageLink($id, $name) . ' '
. ScriptLink($action, Ts('Help convert %s to Markdown', $name)));
}
}
}
print $q->end_ol();
print $q->end_div();
PrintFooter();
}
push(@MyRules, \&MarkdownConvertRule);
sub MarkdownConvertRule {
if (pos == 0 and /\G#MARKDOWN\n/gc) {
return '';
}
return;
}

View File

@@ -1,5 +1,5 @@
#! /usr/bin/perl
# Copyright (C) 20142017 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20142019 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
@@ -18,7 +18,7 @@ use v5.10;
AddModuleDescription('markdown-rule.pl', 'Markdown Rule Extension');
our ($q, $bol, %RuleOrder, @MyRules, $UrlProtocols, $FullUrlPattern, @HtmlStack);
our ($q, $bol, %RuleOrder, @MyRules, $UrlProtocols, $FullUrlPattern, @HtmlStack, $Fragment);
push(@MyRules, \&MarkdownRule);
# Since we want this package to be a simple add-on, we try and avoid
@@ -31,6 +31,7 @@ $RuleOrder{\&MarkdownRule} = 200;
# https://help.github.com/articles/github-flavored-markdown
sub MarkdownRule {
my $alignment;
# \escape
if (m/\G\\([-#>*`=])/cg) {
return $1;
@@ -50,9 +51,28 @@ sub MarkdownRule {
}
# > blockquote
# with continuation
elsif ($bol and m/\G&gt;/cg) {
return CloseHtmlEnvironments()
. AddHtmlEnvironment('blockquote');
elsif ($bol and m/\G((?:&gt;.*\n?)+)/cg) {
Clean(CloseHtmlEnvironments());
Dirty($1);
my $text = $1;
my ($oldpos, $old_) = ((pos), $_);
print '<blockquote>';
$text =~ s/^&gt; ?//gm;
ApplyRules($text, 1, 1, undef, 'p'); # local links, anchors, no revision, start with p
print '</blockquote>';
Clean(AddHtmlEnvironment('p')); # if dirty block is looked at later, this will disappear
($_, pos) = ($old_, $oldpos); # restore \G (assignment order matters!)
}
# """ = blockquote, too
elsif ($bol and m/\G("""[ \t]*\n(.*?)\n"""[ \t]*(?:\n|$))/cgs) {
Clean(CloseHtmlEnvironments());
Dirty($1);
my ($oldpos, $old_) = ((pos), $_);
print '<blockquote>';
ApplyRules($2, 1, 1, undef, 'p'); # local links, anchors, no revision, start with p
print '</blockquote>';
Clean(AddHtmlEnvironment('p')); # if dirty block is looked at later, this will disappear
($_, pos) = ($old_, $oldpos); # restore \G (assignment order matters!)
}
# ``` = code
elsif ($bol and m/\G```[ \t]*\n(.*?)\n```[ \t]*(\n|$)/cgs) {
@@ -123,34 +143,47 @@ sub MarkdownRule {
# beginning of a table
elsif ($bol and !InElement('table') and m/\G\|/cg) {
# warn pos . " beginning of a table";
$alignment = 'style="text-align: right"' if m/\G([ \t]+)/cg;
$alignment = 'style="text-align: center"' if $alignment and m/\G(?=[^|]+[ \t]+\|)/cg;
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
return OpenHtmlEnvironment('table',1)
. AddHtmlEnvironment('tr')
. AddHtmlEnvironment('th');
. AddHtmlEnvironment('th', $alignment);
}
# end of a row and beginning of a new row
elsif (InElement('table') and m/\G\|?\n\|/cg) {
# warn pos . " end of a row and beginning of a new row";
$alignment = 'style="text-align: right"' if m/\G([ \t]+)/cg;
$alignment = 'style="text-align: center"' if $alignment and m/\G(?=[^|]+[ \t]+\|)/cg;
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
return CloseHtmlEnvironment('tr')
. AddHtmlEnvironment('tr')
. AddHtmlEnvironment('td');
. AddHtmlEnvironment('td', $alignment);
}
# otherwise the table ends
elsif (InElement('table') and m/\G\|?(\n|$)/cg) {
# warn pos . " otherwise the table ends";
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
return CloseHtmlEnvironment('table')
. AddHtmlEnvironment('p');
}
# continuation of the first row
elsif (InElement('th') and m/\G\|/cg) {
# warn pos . " continuation of the first row";
$alignment = 'style="text-align: right"' if m/\G([ \t]+)/cg;
$alignment = 'style="text-align: center"' if $alignment and m/\G(?=[^|]+[ \t]+\|)/cg;
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
return CloseHtmlEnvironment('th')
. AddHtmlEnvironment('th');
. AddHtmlEnvironment('th', $alignment);
}
# continuation of other rows
elsif (InElement('td') and m/\G\|/cg) {
# warn pos . " continuation of other rows";
$alignment = 'style="text-align: right"' if m/\G([ \t]+)/cg;
$alignment = 'style="text-align: center"' if $alignment and m/\G(?=[^|]+[ \t]+\|)/cg;
$Fragment =~ s/[ \t]+$//; # cleanup trailing whitespace if previous column was centered
return CloseHtmlEnvironment('td')
. AddHtmlEnvironment('td');
. AddHtmlEnvironment('td', $alignment);
}
# whitespace indentation = code
elsif ($bol and m/\G(\s*\n)*( .+)\n?/cg) {
@@ -160,8 +193,8 @@ sub MarkdownRule {
}
return OpenHtmlEnvironment('pre',1) . $str; # always level 1
}
# [an example](http://example.com/ "Title")
elsif (m/\G\[(.+?)\]\($FullUrlPattern(\s+"(.+?)")?\)/cg) {
# link: [an example](http://example.com/ "Title")
elsif (m/\G\[((?:[^]\n]+\n?)+)\]\($FullUrlPattern(\s+"(.+?)")?\)/cg) {
my ($text, $url, $title) = ($1, $2, $4);
$url =~ /^($UrlProtocols)/;
my %params;

View File

@@ -312,9 +312,7 @@ resolved to the same target (the local page), which is unexpected.
# IndexOptions must be set in MyInitVariables for translations to
# work.
push(@MyInitVariables, sub {
push(@IndexOptions, ['near', T('Include near pages'), 0,
\&ListNearPages]);
});
push(@IndexOptions, ['near', T('Include near pages'), 0, \&ListNearPages])});
sub ListNearPages {
my %pages = %NearSource;

View File

@@ -1,4 +1,6 @@
# Copyright (C) 2005 Flavio Poletti <flavio@polettix.it>
# Copyright (C) 2005 Flavio Poletti <flavio@polettix.it>
# Copyright (C) 2014-2015 Alex Jakimenko <alex.jakimenko@gmail.com>
# Copyright (C) 2014-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
@@ -48,7 +50,7 @@ AddModuleDescription('olocalmap.pl');
push(@MyInitVariables, \&InitLocalMap);
sub InitLocalMap {
my $id = GetCurrentPageName();
my $id = GetId();
my $action = lc(GetParam('action', ''));
AllPagesList(); # Build %IndexHash

View File

@@ -178,6 +178,7 @@ sub NewPermanentAnchorsDeletePage {
my $status = OldPermanentAnchorsDeletePage(@_);
return $status if $status; # this would be the error message
DeletePermanentAnchors(@_); # the only parameter is $id
return ''; # no errors
}
*OldPermanentAnchorsSave = \&Save;
@@ -261,12 +262,15 @@ Some user interface changes are required as well.
=item *
Allow the page index to list permanent anchors or not by setting
C<@IndexOptions>.
C<@IndexOptions>. Note that we need to delay setting this option until we're
sure that translations have loaded correctly, which is why we're setting
C<@IndexOptions> as part of running C<@MyInitVariables>.
=cut
push(@IndexOptions, ['permanentanchors', T('Include permanent anchors'),
1, sub { keys %PermanentAnchors }]);
push(@MyInitVariables, sub {
push(@IndexOptions, ['permanentanchors', T('Include permanent anchors'),
1, sub { keys %PermanentAnchors }])});
=item *

View File

@@ -1,4 +1,5 @@
# Copyright (C) 2004 Brock Wilcox <awwaiid@thelackthereof.org>
# Copyright (C) 2019 Alex Schroeder <alex@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,152 +17,124 @@
use strict;
use v5.10;
use LWP::UserAgent; # This one will one day be eliminated! Hopefully!
# Need these to do pingback
use RPC::XML;
use LWP::UserAgent;
use RPC::XML::Parser;
use RPC::XML;
AddModuleDescription('pingback-server.pl', 'Pingback Server Extension');
our ($CommentsPrefix);
# Specification: http://www.hixie.ch/specs/pingback/pingback
# XML-RPC errors: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
*OldPingbackServerGetHtmlHeader = \&GetHtmlHeader;
*GetHtmlHeader = \&NewPingbackServerGetHtmlHeader;
our ($CommentsPrefix, $q, $HtmlHeaders, %Action, $QuestionaskerSecretKey,
@MyInitVariables, %IndexHash);
# Add the <link ...> to the header
sub NewPingbackServerGetHtmlHeader {
my ($title, $id) = @_;
my $header = OldPingbackServerGetHtmlHeader($title,$id);
my $pingbackLink =
'<link rel="pingback" '
. 'href="http://thelackthereof.org/wiki.pl?action=pingback;id='
. $id . '">';
$header =~ s/<head>/<head>$pingbackLink/;
return $header;
push(@MyInitVariables, \&PingbackServerAddLink);
sub PingbackServerAddLink {
SetParam('action', 'pingback') if $q->path_info =~ m|/pingback\b|;
my $id = GetId();
return unless $id;
return if $id =~ /^$CommentsPrefix/;
my $link = '<link rel="alternate" type="application/wiki" href="'
. ScriptUrl('pingback/' . UrlEncode($id)) . '" />';
$HtmlHeaders .= $link unless index($HtmlHeaders, /$link/) != -1;
}
*OldPingbackServerInitRequest = \&InitRequest;
*InitRequest = \&NewPingbackServerInitRequest;
sub NewPingbackServerInitRequest {
if($ENV{'QUERY_STRING'} =~ /action=pingback;id=(.*)/) {
my $id = $1;
DoPingbackServer($id);
exit 0;
} else {
return OldPingbackServerInitRequest(@_);
}
}
$Action{pingback} = \&DoPingbackServer;
sub DoPingbackServer {
my $id = FreeToNormal(shift);
if ($ENV{'REQUEST_METHOD'} ne 'POST') {
result('405 Method Not Allowed', -32300,
'Only XML-RPC POST requests recognised.', 'Allow: POST');
# some sanity checks for the request
if ($q->request_method() ne 'POST') {
ReportError(T('Only XML-RPC POST requests recognised'), '405 METHOD NOT ALLOWED');
}
if ($q->content_type() ne 'text/xml') {
ReportError(T('Only XML-RPC POST requests recognised'), '415 UNSUPPORTED MEDIA TYPE');
}
if ($ENV{'CONTENT_TYPE'} ne 'text/xml') {
result('415 Unsupported Media Type', -32300,
'Only XML-RPC POST requests recognised.');
# some sanity checks for the target page name
if (not $id) {
PingbackServerFault('400 NO ID', 33, "No page specified");
}
my $error = ValidId($id);
if ($error) {
PingbackServerFault('400 INVALID ID', 33, "Invalid page name: $id");
}
local $/ = undef;
my $input = <STDIN>;
# check the IP number for bans
my $rule = UserIsBanned();
if ($rule) {
PingbackServerFault('403 FORBIDDEN', 49, "Your IP number is blocked");
}
# parse it
# check that the target page exists
AllPagesList();
if (not $IndexHash{$id}) {
PingbackServerFault('404 NOT FOUND', 32, "Page does not exist: $id");
}
# parse the remote procedure call
my $data = $q->param('POSTDATA');
my $parser = RPC::XML::Parser->new();
my $request = $parser->parse($input);
my $request = $parser->parse($data);
if (not ref($request)) {
result('400 Bad Request', -32700, $request);
PingbackServerFault('400 NO DATA', -32700, "Could not parse XML-RPC");
}
# handle it
# sanity check the function and argument number
my $name = $request->name;
my $arguments = $request->args;
if ($name ne 'pingback.ping') {
result('501 Not Implemented', -32601, "Method $name not supported");
PingbackServerFault('501 NOT IMPLEMENTED', -32601, "Method $name not supported");
}
if (@$arguments != 2) {
result('400 Bad Request', -32602,
"Wrong number of arguments (arguments must be in the form 'from', 'to')");
PingbackServerFault('400 WRONG NUMBER OF ARGS', -32602, "Wrong number of arguments");
}
# extract the two arguments
my $source = $arguments->[0]->value;
my $target = $arguments->[1]->value;
# verify that the source isn't banned
$rule = BannedContent($source);
if ($rule) {
PingbackServerFault('403 FORBIDDEN', 49, "The URL is blocked");
}
# TODO: Since we are _inside_ the wiki seems like we shouldn't have to use LWP
# So comment out all the LWP stuff once the DoPost thingie works
# DoPost($id);
# verify that the pingback is legit
my $ua = LWP::UserAgent->new;
$ua->agent("OddmusePingbackServer/0.1 ");
# Create a request
my $req = HTTP::Request->new(POST => 'http://thelackthereof.org/wiki.pl');
$req->content_type('application/x-www-form-urlencoded');
$req->content("title=$CommentsPrefix$id"
. "&summary=new%20comment"
. "&aftertext=Pingback:%20$source"
. "&save=save"
. "&username=pingback");
my $res = $ua->request($req);
my $out = '';
# Check the outcome of the response
if ($res->is_success) {
$out = $res->content;
} else {
$out = $res->status_line . "\n";
my $response = $ua->get($source);
if (not $response->is_success) {
PingbackServerFault('400 NO SOURCE', 16, "Cannot retrieve $source");
}
my $self = ScriptUrl(UrlEncode($id));
if ($response->decoded_content !~ /$self/) {
PingbackServerFault('403 FORBIDDEN', "$source does not link to $self");
}
$id = $CommentsPrefix . $id;
if (GetPageContent($id) =~ /$source/) {
PingbackServerFault('400 ALREADY REGISTERED', 48, "$source has already been registered");
}
result('200 OK', 0, "Oddmuse PingbackServer! $id OK");
# post a comment without redirect at the end
SetParam('aftertext', 'Pingback: ' . $source);
SetParam('summary', 'Pingback');
SetParam('username', T('Anonymous'));
SetParam($QuestionaskerSecretKey, 1) if $QuestionaskerSecretKey;
local *ReBrowsePage = sub {};
DoPost($id);
# response
my $message = "Oddmuse PingbackServer! $id OK";
my $response = RPC::XML::response->new(RPC::XML::string->new($message));
print GetHttpHeader('text/xml', 'nocache', '200 OK'), $response->as_string, "\n\n";
}
sub result {
my($status, $error, $data, $extra) = @_;
my $response;
if ($error) {
$response = RPC::XML::response->new(
RPC::XML::fault->new($error, $data));
} else {
$response = RPC::XML::response->new(RPC::XML::string->new($data));
}
print "Status: $status\n";
if (defined($extra)) {
print "$extra\n";
}
print "Content-Type: text/xml\n\n";
print $response->as_string;
exit;
sub PingbackServerFault {
my($status, $error, $data) = @_;
my $fault = RPC::XML::response->new(RPC::XML::fault->new($error, $data));
print GetHttpHeader('text/xml', 'nocache', $status), $fault->as_string, "\n\n";
exit 2;
}
=pod
# This doesn't work... but might be a basis for an in-wiki update system
sub DoPost {
my $id = FreeToNormal(shift);
my $source = shift;
ValidIdOrDie($id);
# Lock before getting old page to prevent races
RequestLockOrError(); # fatal
OpenPage($id);
my $string = $Page{text};
my $comment = "Pingback: $source";
$comment =~ s/\r//g; # Remove "\r"-s (0x0d) from the string
$comment =~ s/\s+$//g; # Remove whitespace at the end
$string .= "----\n" if $string and $string ne "\n";
$string .= $comment . "\n\n-- Pingback"
. ' ' . TimeToText(time) . "\n\n";
my $summary = "new pingback"
$Page{summary} = $summary;
$Page{username} = $user;
$Page{text} = $string;
SavePage();
ReleaseLock();
}
=cut

View File

@@ -171,7 +171,7 @@ sub ReCaptchaGetQuestion {
# if (defined $ReCaptchaTabIndex) { $recaptcha_options{tabindex} = $ReCaptchaTabIndex; }
eval "use Captcha::reCAPTCHA";
my $captcha_html = Captcha::reCAPTCHA->new()->get_html(
my $captcha_html = Captcha::reCAPTCHA->new()->get_html_v2(
$ReCaptchaPublicKey, undef, $ENV{'HTTPS'} eq 'on', undef);
my $submit_html = $need_button ? $q->submit(-value=> T('Go!')) : '';
my $options_html = '
@@ -239,11 +239,12 @@ sub NewReCaptchaDoPost {
sub ReCaptchaCheckAnswer {
eval "use Captcha::reCAPTCHA";
my $result = Captcha::reCAPTCHA->new()->check_answer(
my $answer = GetParam('g-recaptcha-response');
return 0 unless $answer;
my $result = Captcha::reCAPTCHA->new()->check_answer_v2(
$ReCaptchaPrivateKey,
$q->remote_addr(),
GetParam('recaptcha_challenge_field'),
GetParam('recaptcha_response_field')
$answer,
$q->remote_addr()
);
return $result->{is_valid};
}

70
modules/rename-pages.pl Normal file
View File

@@ -0,0 +1,70 @@
# Copyright (C) 2019 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 strict;
use v5.10;
our ($q, %Page, %Action, %IndexHash, @MyAdminCode);
AddModuleDescription('rename-pages.pl', 'Rename Pages Extension');
$Action{'rename-page'} = \&RenamePage;
sub RenamePage {
my $id = shift;
my $to = GetParam('to', '');
# check target
ValidIdOrDie($to);
OpenPage($to);
ReportError(T('Target page already exists.'), '400 BAD REQUEST')
if $IndexHash{$to} and not PageMarkedForDeletion();
# check the source
ValidIdOrDie($id);
OpenPage($id);
ReportError(T('Source page does not exist.'), '400 BAD REQUEST')
if not $IndexHash{$id} or PageMarkedForDeletion();
{
# prevent posting from browsing the target right away
local *ReBrowsePage = sub {};
# renaming is a minor change
SetParam('recent_edit', 'on');
# copy text
SetParam('text', $Page{text});
SetParam('summary', Ts('Copied from %s', FreeToNormal($id)));
DoPost($to);
# create redirect
SetParam('text', "#REDIRECT [[$to]]");
SetParam('summary', Ts('Moved to %s', FreeToNormal($to)));
DoPost($id);
}
# and now that we're done, go to the target
ReBrowsePage($to);
}
push(@MyAdminCode, \&RenamePageMenu);
sub RenamePageMenu {
my ($id, $menuref, $restref) = @_;
my $name = FreeToNormal($id);
if ($id) {
push(@$menuref, GetFormStart()
. $q->label({-for=>'to'}, Ts('Rename %s to:', $name) . ' ')
. GetHiddenValue('action', 'rename-page')
. GetHiddenValue('id', $id)
. $q->textfield(-name=>'to', -size=>20)
. ' '
. $q->submit('Do it'));
}
}

View File

@@ -62,16 +62,26 @@ Example:
$TagFeedIcon = 'http://www.example.org/pics/rss.png';
=head2 $TagCloudSize
The number of most used tags when looking at the tag cloud. The
default is 50.
Example:
$TagCloudSize = 20;
=cut
our ($q, $Now, %Action, %Page, $FreeLinkPattern, @MyInitVariables, @MyRules, @MyAdminCode, $DataDir, $ScriptName);
our ($TagUrl, $TagFeed, $TagFeedIcon, $TagFile);
our ($TagUrl, $TagFeed, $TagFeedIcon, $TagFile, $TagCloudSize);
push(@MyInitVariables, \&TagsInit);
sub TagsInit {
$TagUrl = ScriptUrl('action=rc;rcfilteronly=tag:%s') unless $TagUrl;
$TagFeed = ScriptUrl('action=rss;rcfilteronly=tag:%s') unless $TagFeed;
$TagCloudSize = 50 unless $TagCloudSize;
$TagFile = "$DataDir/tag.db";
}
@@ -239,6 +249,11 @@ sub TagFind {
return @result;
}
sub TagsTerms {
my $string = shift;
return grep(/./, $string =~ /\"([^\"]+)\"|(\S+)/g);
}
*OldTagFiltered = \&Filtered;
*Filtered = \&NewTagFiltered;
@@ -246,7 +261,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 +275,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 +295,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
@@ -298,27 +315,16 @@ $Action{tagcloud} = \&TagCloud;
sub TagCloud {
print GetHeader('', T('Tag Cloud'), ''),
$q->start_div({-class=>'content cloud'}) . '<p>';
$q->start_div({-class=>'content cloud'});
require HTML::TagCloud;
my $cloud = HTML::TagCloud->new;
# open the DB file
my %h = TagReadHash();
my $max = 0;
my $min = 0;
my %count = ();
foreach my $tag (grep !/^_/, keys %h) {
$count{$tag} = @{$h{$tag}};
$max = $count{$tag} if $count{$tag} > $max;
$min = $count{$tag} if not $min or $count{$tag} < $min;
$cloud->add(NormalToFree($tag), "$ScriptName?search=tag:" . UrlEncode($tag), scalar @{$h{$tag}});
}
foreach my $tag (sort keys %count) {
my $n = $count{$tag};
print $q->a({-href => "$ScriptName?search=tag:" . UrlEncode($tag),
-title => $n,
-style => 'font-size: '
. int(80+120*($max == $min ? 1 : ($n-$min)/($max-$min)))
. '%;',
}, NormalToFree($tag)), ' ... ';
}
print '</p></div>';
print $cloud->html_and_css($TagCloudSize);
print '</div>';
PrintFooter();
}
@@ -418,13 +424,12 @@ sub TagsMenu {
my ($id, $menuref, $restref) = @_;
push(@$menuref,
ScriptLink('action=reindex', T('Rebuild tag index'), 'reindex')
. ', ' . ScriptLink('action=taglist', T('list tags'), 'taglist')
. ', ' . ScriptLink('action=tagcloud', T('tag cloud'), 'tagcloud'));
}
=head1 COPYRIGHT AND LICENSE
Copyright (C) 20052015 Alex Schroeder <alex@gnu.org>
Copyright (C) 20052019 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

View File

@@ -229,8 +229,8 @@ Important pages:
Páginas importantes:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Para marcar uma página para exclusão, coloque <strong>%s</strong> na primeira linha
from %s
de %s
Anonymous
Anônimo
redirected from %s
redirecionado de %s
%s:
@@ -469,8 +469,6 @@ The changes conflict. Please check the page again.
As alterações são conflitantes. Por favor, verifique essa página novamente.
Please check whether you overwrote those changes.
Por favor, verifique se você sobrescreveu aquelas alterações.
Anonymous
Anônimo
Cannot delete the index file %s.
Não foi possível excluir o arquivo de índice %s.
Please check the directory permissions.
@@ -505,6 +503,8 @@ Note: This error is normal if no changes have been made.
Observação: Esse erro é normal se nenhuma alteração foi feita.
Moving %s log entries.
Movendo %s entradas de log.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Marcar ou Remover o bloqueio global de edição
Edit lock created.
@@ -1236,6 +1236,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -229,8 +229,8 @@ Important pages:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
от %s
Anonymous
Анонимен
redirected from %s
Препратка от %s
%s:
@@ -469,8 +469,6 @@ The changes conflict. Please check the page again.
Промените се застъпват. Моля провери страницата още веднъж.
Please check whether you overwrote those changes.
Моля провери, дали няма конфликт между промените.
Anonymous
Анонимен
Cannot delete the index file %s.
Please check the directory permissions.
@@ -505,6 +503,8 @@ Note: This error is normal if no changes have been made.
Забележка: Тази грешка е нормална, ако не са направени промени.
Moving %s log entries.
Преместване на %s лог-записа.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Налагане или отмяна на глобална защита от редакции.
Edit lock created.
@@ -1236,6 +1236,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -228,8 +228,8 @@ Important pages:
Pàgines importants:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Per marcar una pàgina per esborrar, escriure <strong>%s</strong> en la primera línia.
from %s
des de %s
Anonymous
Anònim
redirected from %s
redirigit des de %s
%s:
@@ -468,8 +468,6 @@ The changes conflict. Please check the page again.
Els canvis entren en conflicte. Si us plau, comprova la pàgina un altre cop.
Please check whether you overwrote those changes.
Si us plau, comprova si sobreescrius aquests canvis.
Anonymous
Anònim
Cannot delete the index file %s.
No es pot esborrar el arxiu índex de %s.
Please check the directory permissions.
@@ -504,6 +502,8 @@ Note: This error is normal if no changes have been made.
Nota: Aquest error és normal si no s'han fet canvis.
Moving %s log entries.
Movent %s entrades del registre.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
(Des)activar bloqueig global de edició
Edit lock created.
@@ -1235,6 +1235,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -229,8 +229,8 @@ Important pages:
重要頁面:
To mark a page for deletion, put <strong>%s</strong> on the first line.
在該頁首行加入 <strong>%s</strong> 可將頁面標記為刪除
from %s
%s
Anonymous
匿名者
redirected from %s
%s 轉址
%s:
@@ -469,8 +469,6 @@ The changes conflict. Please check the page again.
你的修改和他人發生衝突請再次確認
Please check whether you overwrote those changes.
請確認一下是否你要覆蓋這些修改
Anonymous
匿名者
Cannot delete the index file %s.
無法刪除索引檔 %s
Please check the directory permissions.
@@ -505,6 +503,8 @@ Note: This error is normal if no changes have been made.
如果還沒有做過任何修改的話則不用理會這個錯誤訊息
Moving %s log entries.
移除了 %s 個記錄項目
Removing IP numbers from %s log entries.
Set or Remove global edit lock
設定或移除整個網站的編輯鎖定
Edit lock created.
@@ -1236,6 +1236,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -245,8 +245,8 @@ Important pages:
重要页面
To mark a page for deletion, put <strong>%s</strong> on the first line.
在首行加入 <strong>%s</strong>以将页面标记为删除
from %s
%s
Anonymous
匿名者
redirected from %s
%s重定向
%s:
@@ -485,8 +485,6 @@ The changes conflict. Please check the page again.
您的修改和他人发生冲突请再次确认
Please check whether you overwrote those changes.
请您确认一下是否要覆盖这些修改
Anonymous
匿名者
Cannot delete the index file %s.
无法删除索引档%s
Please check the directory permissions.
@@ -521,6 +519,8 @@ Note: This error is normal if no changes have been made.
如果还没有做过任何修改请不用理会这个错误讯息
Moving %s log entries.
移除了%s个日志项目
Removing IP numbers from %s log entries.
Set or Remove global edit lock
设定或移除整个网站的编辑锁定
Edit lock created.
@@ -1252,6 +1252,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -235,8 +235,8 @@ Important pages:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
vanaf %s
Anonymous
Anoniem
redirected from %s
doorverwezen vanaf %s
%s:
@@ -475,8 +475,6 @@ The changes conflict. Please check the page again.
De wijzigingen zijn strijdig met elkaar. Controleer de pagina a.u.b. opnieuw.
Please check whether you overwrote those changes.
Controleer a.u.b. of u die wijzigingen overschreven heeft.
Anonymous
Anoniem
Cannot delete the index file %s.
Please check the directory permissions.
@@ -511,6 +509,8 @@ Note: This error is normal if no changes have been made.
NB: Deze foutmelding is gebruikelijk als er geen wijzigingen zijn gemaakt.
Moving %s log entries.
Bezig met verplaatsen van %s items.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Ver-/ontgrendelen van de gehele site
Edit lock created.
@@ -1242,6 +1242,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,8 +226,8 @@ Important pages:
Tähtsad leheküljed:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
Anonymous
Anonüümne
redirected from %s
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Please check whether you overwrote those changes.
Anonymous
Anonüümne
Cannot delete the index file %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Moving %s log entries.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,8 +226,8 @@ Important pages:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
%s:stä
Anonymous
Nimetön
redirected from %s
uudelleenohjattu sivulta %s
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Muutokset ovat ristiriidassa. Tarkista sivu uudelleen.
Please check whether you overwrote those changes.
Tarkista, ylikirjoititko joitakin noista muutoksista.
Anonymous
Nimetön
Cannot delete the index file %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Huom: Tämä virhe on normaali, jos muutoksia ei ole tehty.
Moving %s log entries.
Siirretään %s kirjausta.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Aseta tai poista sivuston muokkauslukitus
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -232,8 +232,8 @@ Important pages:
Pages importantes :
To mark a page for deletion, put <strong>%s</strong> on the first line.
Pour marquer une page comme étant à supprimer, ajoutez <strong>%s</strong> à la première ligne
from %s
depuis %s
Anonymous
Anonyme
redirected from %s
redirigé(e) à partir de %s
%s:
@@ -472,8 +472,6 @@ The changes conflict. Please check the page again.
Conflit sur les modifications. SVP vérifiez la page à nouveau.
Please check whether you overwrote those changes.
SVP vérifiez si vous avez écrasé ces modifications.
Anonymous
Anonyme
Cannot delete the index file %s.
Impossible de supprimer le fichier index %s.
Please check the directory permissions.
@@ -508,6 +506,8 @@ Note: This error is normal if no changes have been made.
Remarque : Cette erreur est normale si aucune modification na été effectuée.
Moving %s log entries.
Déplace %s entrées du log.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Positionne ou Retire le verrou global dédition
Edit lock created.
@@ -1239,6 +1239,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,8 +226,8 @@ Important pages:
Wichtige Seiten:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Um eine Seite zu löschen, setzt man <strong>%s</strong> auf die erste Zeile.
from %s
von %s
Anonymous
Unbekannt
redirected from %s
Umgeleitet von %s
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Die Änderungen beeinträchtigen sich gegenseitig. Bitte überprüfen Sie die Seite nochmals.
Please check whether you overwrote those changes.
Bitte überprüfen Sie, ob diese Änderungen überschrieben worden sind.
Anonymous
Unbekannt
Cannot delete the index file %s.
Die Index Datei %s kann nicht gelöscht werden.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
HINWEIS: Dieser Fehler ist normal, wenn keine Änderungen durchgeführt wurden.
Moving %s log entries.
Verschiebe %s Log-Einträge.
Removing IP numbers from %s log entries.
IP Nummbern wurden as %s Log Einträgen entfernt.
Set or Remove global edit lock
Erzeugen oder Aufheben der globalen Bearbeitungssperre
Edit lock created.
@@ -1203,8 +1203,8 @@ All mail subscriptions
Alle Email Abonnements
Subscriptions
Abonnements
Email:
Email:
E-Mail:
Show
Zeigen
Subscriptions for %s:
@@ -1234,6 +1234,17 @@ Es wurden keine Email Adressen gefundne, die zu migrieren wären.
Migrated %s rows.
%s Zeilen wurden migriert.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
%s zu Markdown convertieren
List all non-Markdown pages
Liste aller Seiten, die noch nicht zu Markdown konvertiert wurden
Converting %s
%s konvertieren
Candidates for Conversion to Markdown
Kandidaten für die Konvertierung zu Mardown
################################################################################
# modules/module-bisect.pl
################################################################################
Bisect modules

View File

@@ -226,8 +226,8 @@ Important pages:
Σημαντικές σελίδες:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Για να σημειωθεί μια σελίδα προς διαγραφή, εισάγετε το κείμενο <strong>%s</strong> στην πρώτη γραμμή της σελίδας.
from %s
από %s
Anonymous
Ανώνυμος
redirected from %s
προώθηση από το %s
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Οι αλλαγές έρχονται σε σύγκρουση. Παρακαλώ δείτε τη σελίδα πάλι.
Please check whether you overwrote those changes.
Παρακαλώ ελέγξτε ότι δεν σβήσατε κάποιες από τις αλλαγές.
Anonymous
Ανώνυμος
Cannot delete the index file %s.
Σφάλμα διαγραφής του αρχείου %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Σημείωση: Αυτό το σφάλμα δεν είναι σημαντικό αν δεν έχουν γίνει αλλαγές.
Moving %s log entries.
Μετακίνηση %s εγγραφών καταγραφής.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Κλείδωμα ή Ξεκλείδωμα του κεντρικού κλειδώματος εκδότη
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -228,8 +228,8 @@ Important pages:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
מ %s
Anonymous
אנונימי
redirected from %s
הוכוון מחדש מ %s
%s:
@@ -468,8 +468,6 @@ The changes conflict. Please check the page again.
Please check whether you overwrote those changes.
Anonymous
אנונימי
Cannot delete the index file %s.
Please check the directory permissions.
@@ -504,6 +502,8 @@ Note: This error is normal if no changes have been made.
הערה: שגיאה זו היא רגילה אם לא בוצעו שינויים.
Moving %s log entries.
מזיז %s ערכי יומן.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
קבע או הסר נעילת עריכה גלובלית
Edit lock created.
@@ -1235,6 +1235,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,8 +226,8 @@ Important pages:
Pagine importanti
To mark a page for deletion, put <strong>%s</strong> on the first line.
Per impostare una pagine per la cancellazione, inserire <strong>%s</strong> sulla prima riga.
from %s
da %s
Anonymous
Anonimo
redirected from %s
rediretto da %s
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Le modifiche sono in conflitto. Prego ricontrollare la pagina.
Please check whether you overwrote those changes.
Prego controllate se avete sovrascritto quelle modifiche.
Anonymous
Anonimo
Cannot delete the index file %s.
Impossibile cancellare il file di indice %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Nota: questo errore è normale se non sono stati apportati cambiamenti.
Moving %s log entries.
Spostamento di %s elementi di log.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Imposta o rilascia il blocco globale di modifica
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,7 +226,7 @@ Important pages:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
Anonymous
redirected from %s
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Please check whether you overwrote those changes.
Anonymous
Cannot delete the index file %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Moving %s log entries.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,8 +226,8 @@ Important pages:
중요한 페이지들:
To mark a page for deletion, put <strong>%s</strong> on the first line.
특정 페이지를 삭제 요청페이지로 하려면, <strong>%s</strong> 첫줄에 입력하세요.
from %s
%s 부터
Anonymous
익명
redirected from %s
%s 에서 재전송됨
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
페이지들이 충돌합니다. 페이지를 다시 확인하세요.
Please check whether you overwrote those changes.
변경사항들에 덮어쓰기 하지 않았는지 확인하세요.
Anonymous
익명
Cannot delete the index file %s.
%s 인덱스 파일을 삭제 없습니다.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
주의: 변경이 전혀 없었다면 오류는 정상적인 것입니다.
Moving %s log entries.
%s 로그 엔트리를 옭김.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
전역 편집 잠금을 설정하거나 제거
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,7 +226,7 @@ Important pages:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
Anonymous
redirected from %s
@@ -296,6 +296,8 @@ Delete
Filter:
Summary:
Last edit
revision %s
@@ -310,8 +312,6 @@ later minor edits
No diff available.
Summary:
Old revision:
Changed:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Please check whether you overwrote those changes.
Anonymous
Cannot delete the index file %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Moving %s log entries.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################
@@ -1398,6 +1409,11 @@ Click to search for references to this permanent anchor
Include permanent anchors
################################################################################
# modules/pingback-server.pl
################################################################################
Only XML-RPC POST requests recognised
################################################################################
# modules/portrait-support.pl
################################################################################
@@ -1462,6 +1478,17 @@ Referrers
################################################################################
All Referrers
################################################################################
# modules/rename-pages.pl
################################################################################
Target page already exists.
Source page does not exist.
Copied from %s
Moved to %s
################################################################################
# modules/search-list.pl
################################################################################
@@ -1528,8 +1555,6 @@ Rebuilding index not done.
Rebuild tag index
list tags
tag cloud
################################################################################
@@ -1686,5 +1711,34 @@ Edit %s.
################################################################################
Tags:
################################################################################
# modules/webmention.pl
################################################################################
Webmention requires a POST request
Webmention requires x-www-form-urlencoded requests
Webmention must mention a specific page
Webmention must mention a valid page
Your IP number is blocked: %s
Webmention must mention an existing page
Webmention must mention source
Webmention must mention target
The URL is blocked: %s
Webmention source cannot be verified: %1 returns %2 %3
Webmention source does not link to %s
Webmention for %s already exists
Webmention OK!
#
END_OF_TRANSLATION

View File

@@ -228,8 +228,8 @@ Important pages:
Ważne strony:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Aby zaznaczyć stronę do usunięcia, wpisz <strong>%s</strong> w pierwszej linii.
from %s
z %s
Anonymous
Anonimowy
redirected from %s
przekierowano z %s
%s:
@@ -468,8 +468,6 @@ The changes conflict. Please check the page again.
Wystąpił konflikt zmian. Proszę sprawdzić stronę ponownie.
Please check whether you overwrote those changes.
Proszę sprawdzić, czy nadpisałeś te zmiany.
Anonymous
Anonimowy
Cannot delete the index file %s.
Nie można skasować pliku indeksu %s.
Please check the directory permissions.
@@ -504,6 +502,8 @@ Note: This error is normal if no changes have been made.
Uwaga: Ten błąd jest normalny, jeśli nie było żadnych zmian.
Moving %s log entries.
Przenoszenie %s wpisów logów.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Ustaw lub zwolnij globalną blokadę edycji
Edit lock created.
@@ -1235,6 +1235,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -231,8 +231,8 @@ Important pages:
Páginas importantes:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Para marcar uma página a ser apagada, ponha <strong>%s</strong> na primeira linha.
from %s
de %s
Anonymous
Anónimo
redirected from %s
redireccionado de %s
%s:
@@ -471,8 +471,6 @@ The changes conflict. Please check the page again.
Conflito entre alterações. Por favor reverifique esta página.
Please check whether you overwrote those changes.
Por favor verifique se sobrescreveu essas alterações.
Anonymous
Anónimo
Cannot delete the index file %s.
Impossível apagar o ficheiro de índice %s.
Please check the directory permissions.
@@ -507,6 +505,8 @@ Note: This error is normal if no changes have been made.
NOTA: Este erro é normal se nenhuma alteração foi feita.
Moving %s log entries.
Movendo entradas de log %s.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Marque ou Remova bloqueio global de edição
Edit lock created.
@@ -1238,6 +1238,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,8 +226,8 @@ Important pages:
Pagini importante:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
de la %s
Anonymous
Anonim
redirected from %s
redirecţionat de la %s
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Please check whether you overwrote those changes.
Anonymous
Anonim
Cannot delete the index file %s.
Nu am putut şterge fişierul index %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Moving %s log entries.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -232,8 +232,8 @@ Important pages:
Служебные страницы:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Чтобы пометить любую страницу к удалению, поместите <strong>%s</strong> первой строкой страницы.
from %s
с %s
Anonymous
Аноним
redirected from %s
перенаправлено с %s
%s:
@@ -472,8 +472,6 @@ The changes conflict. Please check the page again.
Изменения конфликтуют. Проверьте страницу снова.
Please check whether you overwrote those changes.
Пожалуйста удостоверьтесь, что вы не перезаписали чужие изменения.
Anonymous
Аноним
Cannot delete the index file %s.
Не могу удалить индекс-файл %s.
Please check the directory permissions.
@@ -508,6 +506,8 @@ Note: This error is normal if no changes have been made.
Примечание: Эта ошибка - нормально, если не было сделано изменений.
Moving %s log entries.
Перемещаю %s лог записей.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Установка или снятие глобальной блокировки на редактирование
Edit lock created.
@@ -1239,6 +1239,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -228,8 +228,8 @@ Important pages:
To mark a page for deletion, put <strong>%s</strong> on the first line.
from %s
од %s
Anonymous
Анонимони
redirected from %s
преусмерено са %s
%s:
@@ -468,8 +468,6 @@ The changes conflict. Please check the page again.
Измене су у сукобу. Проверите страну поново.
Please check whether you overwrote those changes.
Проверите да нисте пребрисали ове те измене.
Anonymous
Анонимони
Cannot delete the index file %s.
Please check the directory permissions.
@@ -504,6 +502,8 @@ Note: This error is normal if no changes have been made.
Пажња: Ова грешка је уобичајена уколико ништа нисте изменили.
Moving %s log entries.
Премештам %s дневничких ставки.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Постави или уклони глобалну браву за уређивање
Edit lock created.
@@ -1235,6 +1235,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -230,8 +230,8 @@ Important pages:
Páginas importantes:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Para marcar una página para su eliminación, escribir <strong>%s</strong> en la primera línea.
from %s
desde %s
Anonymous
Anónimo
redirected from %s
redirigido desde %s
%s:
@@ -470,8 +470,6 @@ The changes conflict. Please check the page again.
Los cambios entran en conflicto. Por favor, comprueba la página otra vez.
Please check whether you overwrote those changes.
Por favor, comprueba si sobreescribes esos cambios.
Anonymous
Anónimo
Cannot delete the index file %s.
No se puede borrar el archivo índice %s.
Please check the directory permissions.
@@ -506,6 +504,8 @@ Note: This error is normal if no changes have been made.
Nota: Este error es normal si no se han hecho cambios.
Moving %s log entries.
Moviendo %s entradas del registro.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Establecer o quitar bloqueo global de edición
Edit lock created.
@@ -1237,6 +1237,17 @@ No non-migrated email addresses found, migration not necessary.
No se encontraron direcciones de correo no migradas, la migración no es necesaria.
Migrated %s rows.
Migrado %s filas.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -230,8 +230,8 @@ Important pages:
Viktiga sidor:
To mark a page for deletion, put <strong>%s</strong> on the first line.
För att markera en sida för radering, skriv <strong>%s</strong> första raden.
from %s
från %s
Anonymous
Anonym
redirected from %s
omdirigerad från %s
%s:
@@ -470,8 +470,6 @@ The changes conflict. Please check the page again.
Ändringarna står i konflikt. Kontrollera sidan igen.
Please check whether you overwrote those changes.
Kontrollera om du skrev över de ändringarna.
Anonymous
Anonym
Cannot delete the index file %s.
Kan inte radera index-filen %s.
Please check the directory permissions.
@@ -506,6 +504,8 @@ Note: This error is normal if no changes have been made.
Observera: Det här felet är normalt om inga ändringar gjorts.
Moving %s log entries.
Flyttar %s loggnotering.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Slå eller av globalt redigeringslås
Edit lock created.
@@ -1237,6 +1237,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

View File

@@ -226,8 +226,8 @@ Important pages:
Важливі сторінки:
To mark a page for deletion, put <strong>%s</strong> on the first line.
Для того, щоб помітити сторінку для видалення, вставте <strong>%s</strong> на першу строчку.
from %s
від %s
Anonymous
Анонім
redirected from %s
перенаправлено з %s
%s:
@@ -466,8 +466,6 @@ The changes conflict. Please check the page again.
Зміни конфліктують. Будь ласка, перевірте цю сторінку ще раз.
Please check whether you overwrote those changes.
Будь ласка, перевірте, чи перезаписали ви ці зміни.
Anonymous
Анонім
Cannot delete the index file %s.
Не можу видалити файл з покажчиком %s.
Please check the directory permissions.
@@ -502,6 +500,8 @@ Note: This error is normal if no changes have been made.
Примітка: Ця помилка припустима, якщо не було зроблено жодних змін.
Moving %s log entries.
Removing IP numbers from %s log entries.
Set or Remove global edit lock
Встановити або Видалити глобальний блок редагування
Edit lock created.
@@ -1233,6 +1233,17 @@ No non-migrated email addresses found, migration not necessary.
Migrated %s rows.
################################################################################
# modules/markdown-converter.pl
################################################################################
Help convert %s to Markdown
List all non-Markdown pages
Converting %s
Candidates for Conversion to Markdown
################################################################################
# modules/module-bisect.pl
################################################################################

227
modules/webmention.pl Normal file
View File

@@ -0,0 +1,227 @@
# Copyright (C) 2004 Brock Wilcox <awwaiid@thelackthereof.org>
# Copyright (C) 2019 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 strict;
use v5.10;
use LWP::UserAgent;
use Modern::Perl;
use XML::LibXML;
AddModuleDescription('webmention.pl', 'Webmention Server Extension');
# Specification: https://www.w3.org/TR/webmention/
our ($CommentsPrefix, $q, $HtmlHeaders, %Action, $QuestionaskerSecretKey,
@MyInitVariables, %IndexHash, $BannedContent, $UsePathInfo, $HomePage,
$Message, @MyAdminCode, $FullUrlPattern);
push(@MyInitVariables, \&WebmentionServerAddLink, \&WebmentionAddAction);
# Add webmentions metadata to our pages
sub WebmentionServerAddLink {
$Message .= T('Webmention module requires $CommentsPrefix to be set') unless $CommentsPrefix;
# only allow linking to reasonable pages: no URL parameters!
my @params = $q->param;
return unless GetParam('action', 'browse') eq 'browse';
return if GetParam('revision');
my $id = GetId() || $HomePage;
return if $id =~ /^$CommentsPrefix/;
my $link = '<link rel="webmention" type="application/wiki" href="'
. ScriptUrl('webmention/' . UrlEncode($id)) . '" />';
$HtmlHeaders .= $link unless $HtmlHeaders =~ /rel="webmention"/;
}
sub WebmentionAddAction {
SetParam('action', 'webmention') if $q->path_info =~ m|/webmention\b|;
}
# Process incoming webmentions
$Action{webmention} = \&DoWebmentionServer;
sub DoWebmentionServer {
my $id = FreeToNormal(shift);
# some sanity checks for the request
if ($q->request_method() ne 'POST') {
ReportError(T('Webmention requires a POST request'), '400 BAD REQUEST');
}
if ($q->content_type() ne 'application/x-www-form-urlencoded') {
ReportError(T('Webmention requires x-www-form-urlencoded requests'), '400 BAD REQUEST');
}
# some sanity checks for the target page name
if (not $id) {
ReportError(T('Webmention must mention a specific page'), '400 BAD REQUEST');
}
my $error = ValidId($id);
if ($error) {
ReportError(T('Webmention must mention a valid page'), '400 BAD REQUEST');
}
# check the IP number for bans
my $rule = UserIsBanned();
if ($rule) {
ReportError(Ts('Your IP number is blocked: %s', $rule), '403 FORBIDDEN');
}
# check that the target page exists
AllPagesList();
if (not $IndexHash{$id}) {
ReportError(T('Webmention must mention an existing page'), '404 NOT FOUND');
}
# verify parameters
my $source = GetParam('source', undef) or ReportError(T('Webmention must mention source'), '400 BAD REQUEST');
my $target = GetParam('target', undef) or ReportError(T('Webmention must mention target'), '400 BAD REQUEST');
# verify that the source isn't banned
$rule = BannedContent($source);
if ($rule) {
ReportError(Ts('The URL is blocked: %s', $rule), '403 FORBIDDEN');
}
# verify that the webmention is legit
my $ua = LWP::UserAgent->new(agent => 'Oddmuse Webmention Server/0.1');
my $response = $ua->get($source);
if (not $response->is_success) {
ReportError(Tss('Webmention source cannot be verified: %1 returns %2 %3',
$source, $response->code, $response->message), '400 BAD REQUEST');
}
my $self = ScriptUrl(UrlEncode($id));
if ($response->decoded_content !~ /$self/) {
ReportError(Ts('Webmention source does not link to %s', $self), '400 BAD REQUEST');
}
$id = $CommentsPrefix . $id;
if (GetPageContent($id) =~ /$source/) {
ReportError(Ts('Webmention for %s already exists', $source), '400 BAD REQUEST');
}
# try to determine a name and a link
my ($username, $homepage);
my $parser = XML::LibXML->new(recover => 2);
my $dom = $parser->load_html(string => $response->decoded_content);
my @nodes = $dom->findnodes('//*[@rel="author"]');
if (@nodes) {
my $node = shift @nodes;
$username = $node->textContent;
$homepage = $node->getAttribute('href');
}
# post a comment without redirect at the end
SetParam('aftertext', 'Webmention: ' . $source);
SetParam('summary', 'Webmention');
SetParam('username', $username || T('Anonymous'));
SetParam('homepage', $homepage);
SetParam($QuestionaskerSecretKey, 1) if $QuestionaskerSecretKey;
local *ReBrowsePage = sub {};
DoPost($id);
# response
print GetHeader('', T('Webmention OK!'));
print $q->start_div({-class=>'content webmention'}),
$q->p(GetPageLink($BannedContent)),
$q->end_div;
PrintFooter();
}
# Allow user to webmention other sites
push(@MyAdminCode, \&WebmentionMenu);
sub WebmentionMenu {
my ($id, $menuref, $restref) = @_;
if ($id) {
push(@$menuref, ScriptLink('action=webmentions;id=' . $id, T('Add webmentions'), 'webmentions'));
}
}
$Action{webmentions} = \&DoWebmentionMenu;
sub DoWebmentionMenu {
my $id = GetId();
ValidIdOrDie($id);
print GetHeader('', Ts('Webmentioning others from %s', NormalToFree($id)), '');
my $text = GetPageContent($id);
my @urls = $text =~ /$FullUrlPattern/g;
if (@urls) {
print GetFormStart();
print GetHiddenValue('action', 'webmentioning');
print GetHiddenValue('from', UrlEncode($id));
print '<p>';
print $q->checkbox_group('to', \@urls, undef, 'true');
print '</p>';
print $q->submit('go', T('Webmention!'));
} else {
print $q->p(T('No links found.'));
}
PrintFooter();
}
$Action{webmentioning} = \&DoWebmention;
sub DoWebmention {
my $id = GetParam('from');
ValidIdOrDie($id);
my $from = ScriptUrl($id);
my @to = $q->multi_param('to');
ReportError('Missing target') unless @to;
print GetHeader('', Ts('Webmentioning somebody from %s', NormalToFree($id)), '');
for my $to (@to) {
Webmention($from, $to);
}
PrintFooter();
}
sub Webmention {
my ($from, $to) = @_;
ReportError('Target must be an URL', '400 BAD REQUEST', 0, $q->p($to)) unless $to =~ /$FullUrlPattern/;
my $ua = LWP::UserAgent->new(agent => "Oddmuse Webmention Client/0.1");
print $q->p(Ts('Contacting %s', $to));
my $response = $ua->get($to);
if (!$response->is_success) {
print $q->p(Ts('Target reports an error: %s', $response->status_line));
return;
}
print $q->p("Parsing response");
my $data = $response->decoded_content;
my $parser = XML::LibXML->new(recover => 2);
my $dom = $parser->load_html(string => $data);
my $webmention = $dom->findvalue('//link[@rel="webmention"]/@href');
if (!$webmention) {
print $q->p(T('No Webmention URL found'));
return;
}
print $q->p("Webmention URL is $webmention");
$response = $ua->post($webmention, { source => $from, target => $to });
my $message = $response->code . " " . $response->message;
if ($response->is_success) {
print $q->p(Ts("Success: %s", $message));
} else {
print $q->p(Ts("Failure: %s", $message));
$dom = $parser->load_html(string => $response->decoded_content());
for my $node ($dom->getElementsByTagName('script')) { $node->parentNode->removeChild($node) };
for my $node ($dom->getElementsByTagName('style')) { $node->parentNode->removeChild($node) };
print $q->p($dom->textContent);
}
}

59
modules/wordcount.pl Normal file
View File

@@ -0,0 +1,59 @@
# Copyright (C) 2005 Robin V. Stacey (robin@greywulf.net)
#
# 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 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
# This module adds a wordcount to the bottom of edit boxes. The javascript code is munged from
# Richard Livsey's Textarea Tools page: http://livsey.org/experiments/textareatools/
# Though I've stripped it down to it's barest necessities
use strict;
use v5.10;
our (@MyInitVariables, $HtmlHeaders);
AddModuleDescription('wordcount.pl', 'Word Count Extension');
push(@MyInitVariables, \&WordcountAddScript);
sub WordcountAddScript {
$HtmlHeaders .= "<script type='text/javascript'>
function addEvent(obj, evType, fn) {
if (obj.addEventListener) {
obj.addEventListener(evType, fn, true);
return true;
} else if (obj.attachEvent) {
var r = obj.attachEvent('on'+evType, fn);
return r;
} else { return false; }
}
addEvent(window, 'load', function() {
document.getElementById('textWordCount').innerHTML = numWords(document.getElementById('text').value);
document.getElementById('text').onkeyup = function() {
document.getElementById('textWordCount').innerHTML = numWords(document.getElementById('text').value);
}
});
function numWords(string) {
string = string + ' ';
string = string.replace(/^[^A-Za-z0-9]+/gi, '');
string = string.replace(/[^A-Za-z0-9]+/gi, ' ');
var items = string.split(' ');
return items.length -1;
}
</script>";
}

94
scripts/ip-to-regexp.pl Normal file
View File

@@ -0,0 +1,94 @@
use Modern::Perl;
use Net::Whois::Parser qw/parse_whois/;
sub main {
my $ip = shift(@ARGV);
die "Provide an IP number as argument.\n" unless $ip;
print get_regexp_ip(get_range($ip)), "\n";
}
sub get_range {
my $ip = shift;
my $response = parse_whois(domain => $ip);
my $re = '(?:[0-9]{1,3}\.){3}[0-9]{1,3}';
my ($start, $end) = $response->{inetnum} =~ /($re) *- *($re)/;
return $start, $end;
}
sub get_groups {
my ($from, $to) = @_;
my @groups;
if ($from < 10) {
my $to = $to >= 10 ? 9 : $to;
push(@groups, [$from, $to]);
$from = $to + 1;
}
while ($from < $to) {
my $to = int($from/100) < int($to/100) ? $from + 99 - $from % 100 : $to;
if ($from % 10) {
push(@groups, [$from, $from + 9 - $from % 10]);
$from += 10 - $from % 10;
}
if (int($from/10) < int($to/10)) {
if ($to % 10 == 9) {
push(@groups, [$from, $to]);
$from = 1 + $to;
} else {
push(@groups, [$from, $to - 1 - $to % 10]);
$from = $to - $to % 10;
}
} else {
push(@groups, [$from - $from % 10, $to]);
last;
}
if ($to % 10 != 9) {
push(@groups, [$from, $to]);
$from = 1 + $to; # jump from 99 to 100
}
}
return \@groups;
}
sub get_regexp_range {
my @chars;
for my $group (@{get_groups(@_)}) {
my ($from, $to) = @$group;
my $char;
for (my $i = length($from); $i >= 1; $i--) {
if (substr($from, - $i, 1) eq substr($to, - $i, 1)) {
$char .= substr($from, - $i, 1);
} else {
$char .= '[' . substr($from, - $i, 1) . '-' . substr($to, - $i, 1). ']';
}
}
push(@chars, $char);
}
return join('|', @chars);
}
sub get_regexp_ip {
my ($from, $to) = @_;
my @start = split(/\./, $from);
my @end = split(/\./, $to);
my $regexp = "^";
for my $i (0 .. 3) {
if ($start[$i] eq $end[$i]) {
$regexp .= $start[$i];
} elsif ($start[$i] eq '0' and $end[$i] eq '255') {
last;
} elsif ($start[$i + 1] > 0) {
$regexp .= '(' . $start[$i] . '\.('
. get_regexp_range($start[$i + 1], '255') . ')|'
. get_regexp_range($start[$i] + 1, $end[$i + 1]) . ')';
$regexp .= '\.';
last;
} else {
$regexp .= '(' . get_regexp_range($start[$i], $end[$i]) . ')$';
last;
}
$regexp .= '\.' if $i < 3;
}
return $regexp;
}
main();

71
scripts/pingback-client.pl Executable file
View File

@@ -0,0 +1,71 @@
#! /usr/bin/perl -w
# Copyright (C) 2013 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/>.
=head1 Pingback from one site to another
If you link from page A on your site to page B on some other site, you can
invoke this script with the command-line arguments A and B. In theory, this will
create a link back from B to A, letting them and all their visitors know that
you wrote something in response.
=cut
use Modern::Perl;
use RPC::XML;
use RPC::XML::Client;
use XML::LibXML;
use LWP::UserAgent;
use Data::Dumper;
if (@ARGV != 2) {
die "Usage: pingback-client FROM TO\n";
}
my ($from, $to) = @ARGV;
my $ua = LWP::UserAgent->new;
$ua->agent("OddmusePingbackClient/0.1");
print "Getting $to\n";
my $response = $ua->get($to);
if (!$response->is_success) {
die $response->status_line;
}
print "Parsing $to\n";
my $data = $response->decoded_content;
my $parser = XML::LibXML->new(recover => 2);
my $dom = $parser->load_html(string => $data);
my $pingback = $dom->findvalue('//link[@rel="pingback"]/@href');
if (!$pingback) {
die "Pingback URL not found in $to\n";
}
print "Pingback URL is $pingback\n";
my $request = RPC::XML::request->new(
'pingback.ping', $from, $to);
my $client = RPC::XML::Client->new($pingback);
$response = $client->send_request($request);
if (!ref($response)) {
die $response;
}
print Dumper($response->value);

View File

@@ -1,5 +1,5 @@
#! /usr/bin/perl
# Copyright (C) 2010 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20102019 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
@@ -22,6 +22,8 @@ use MIME::Entity;
use File::Temp;
use File::Basename;
use File::Path;
use Net::SMTP;
use Authen::SASL qw(Perl);
# This script can be invoked as follows:
# perl rc2mail.pl -r http://localhost/cgi-bin/wiki \
@@ -36,7 +38,8 @@ use File::Path;
# gets http://localhost/cgi-bin/wiki?action=rss;days=1;full=1;short=0
# And http://localhost/cgi-bin/wiki?action=subscriptionlist;raw=1;pwd=foo
# -m user:password@mailhost for sending email using SMTP Auth. Without this
# information, the script will send mail to localhost.
# information, the script will send mail to localhost. The host can end
# in a port number, e.g. "kensanata:*secret*@smtp.migadu.com:587"
# -f email address to use as the sender.
# -t timestamp file; it's last modified date is used to determine when the
# the last run was and an appropriate URL is used. Instead of days=1 it
@@ -137,7 +140,7 @@ sub send_files {
$sent += @subscribers;
send_file($id, $title, $item, @subscribers);
}
print "$sent messages sent\n" if $sent;
print "$sent messages sent\n" if $sent and not $quiet;
}
sub send_file {
@@ -171,45 +174,30 @@ sub send_mail {
Type=> "text/html");
if ($host) {
print "Sending $title to $subscriber using ${user}\@${host}\n" if $verbose;
eval {
require Net::SMTP::TLS;
my $smtp = Net::SMTP::TLS->new($host,
User => $user,
Password => $password);
$smtp->mail($from);
$smtp->to($subscriber);
my $smtp = Net::SMTP->new($host, Debug => $debug);
$smtp->starttls();
# the following requires Authen::SASL!
$smtp->auth($user, $password);
$smtp->mail($from);
if ($smtp->to($subscriber)) {
$smtp->data;
$smtp->datasend($mail->stringify);
$smtp->dataend;
$smtp->quit;
};
if ($@) {
require Net::SMTP::SSL;
my $smtp = Net::SMTP::SSL->new($host, Port => 465);
$smtp->auth($user, $password);
$smtp->mail($from);
$smtp->to($subscriber);
$smtp->data;
$smtp->datasend($mail->stringify);
$smtp->dataend;
$smtp->quit;
}
} else {
my @recipients = $mail->smtpsend();
if (@recipients) {
print "Sent $title to ", join(', ', @recipients), "\n" unless $quiet;
} else {
print "Failed to send $title to $subscriber\n" unless $quiet;
warn "Error: ", $smtp->message();
}
$smtp->quit;
}
}
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

@@ -1,24 +1,41 @@
#!/usr/bin/env perl
use Modern::Perl;
use Mojolicious::Lite;
use Mojo::Cache;
use Archive::Tar;
use File::Basename;
use Sort::Versions;
use Encode qw(decode_utf8);
my $dir = "/home/alex/oddmuse.org/releases";
my $cache = Mojo::Cache->new(max_keys => 50);
get '/' => sub {
my $c = shift;
my @tarballs = sort map {
sub tarballs {
my @tarballs = reverse sort versioncmp map {
my ($name, $path, $suffix) = fileparse($_, '.tar.gz');
$name;
} <$dir/*.tar.gz>;
$c->render(template => 'index', tarballs => \@tarballs);
return \@tarballs;
}
sub tarball {
my $tarball = shift;
if ($tarball eq 'latest') {
my $tarballs = tarballs();
$tarball = shift @$tarballs;
}
return $tarball;
}
get '/' => sub {
my $c = shift;
my $tarballs = tarballs();
unshift @$tarballs, 'latest';
$c->render(template => 'index', tarballs => $tarballs);
} => 'main';
get '/#tarball' => sub {
my $c = shift;
my $tarball = $c->param('tarball');
my $tarball = tarball $c->param('tarball');
my $files = $cache->get($tarball);
if (not $files) {
$c->app->log->info("Reading $tarball.tar.gz");
@@ -36,7 +53,7 @@ get '/#tarball' => sub {
get '/#tarball/#file' => sub {
my $c = shift;
my $tarball = $c->param('tarball');
my $tarball = tarball $c->param('tarball');
my $file = $c->param('file');
my $text = $cache->get("$tarball/$file");
if (not $text) {

View File

@@ -30,7 +30,7 @@ if (@ARGV == 1 and $ARGV[0] eq '--help') {
sub AddModuleDescription { print $_[0], ' ' };
our %Translate;
my @files = <modules/translations/*-utf8.pl>;
my @files = <./modules/translations/*-utf8.pl>;
for (@files) {
if (@ARGV) { # some specific modules
my $files = join ' ', map { quotemeta } @ARGV; # quick and dirty

84
scripts/webmention.pl Normal file
View File

@@ -0,0 +1,84 @@
#! /usr/bin/perl -w
# Copyright (C) 2019 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/>.
=head1 Webmention from one site to another
If you link from page A on your site to page B on some other site, you can
invoke this script with the command-line arguments A and B. In theory, this will
create a link back from B to A, letting them and all their visitors know that
you wrote something in response.
=cut
use Modern::Perl;
use XML::LibXML;
use LWP::UserAgent;
use Data::Dumper;
if (@ARGV != 2) {
die "Usage: webmention FROM TO\n";
}
my $parser = XML::LibXML->new(recover => 2);
my ($from, $to) = @ARGV;
my $ua = LWP::UserAgent->new(agent => "Oddmuse Webmention Client/0.1");
print "Getting $from\n";
my $response = $ua->get($from);
if (!$response->is_success) {
die $response->status_line;
}
print "Parsing $from\n";
my ($username, $homepage);
my $dom = $parser->load_html(string => $response->decoded_content);
my @nodes = $dom->findnodes('//*[@rel="author"]');
if (@nodes) {
my $node = shift @nodes;
$username = $node->textContent;
$homepage = $node->getAttribute('href');
}
print "Webmention from " . join(", ", $username, $homepage) . "\n"
if $username or $homepage;
print "Getting $to\n";
$response = $ua->get($to);
if (!$response->is_success) {
die $response->status_line;
}
print "Parsing $to\n";
$dom = $parser->load_html(string => $response->decoded_content);
my $webmention = $dom->findvalue('//link[@rel="webmention"]/@href');
if (!$webmention) {
die "Webmention URL not found in $to\n";
}
print "Webmention URL is $webmention\n";
$response = $ua->post($webmention, { source => $from, target => $to });
my $message = $response->code . " " . $response->message . "\n";
if ($response->is_success) {
print $message;
} else {
die $message;
}

View File

@@ -3,3 +3,66 @@ Extra Files
Some of the modules requires extra files -- graphics, templates, and
so on.
Gopher Server
-------------
Example usage:
Change your working directory to the root of the Oddmuse repository
(the parent directory of this directory).
Set the environment variable `WikiDataDir` to `test-data`:
```
export WikiDataDir=test-data
```
Test that the simple web server works by running `stuff/server.pl`.
This should start the web server on `http://localhost:8080/`. Visit
the link using your web browser and edit `HomePage`.
You should see a `test-data` directory containing the new page.
Now start the gopher server on port 7070 by running
`stuff/gopher-server.pl --port=7070`. If you don't provide an explicit
port a random port is used and you'll need to read the server output
to determine the actual port. That's why we're setting the port
ourselves. Remember that using ports below 1024 require special
privileges. Don't use them unless you know what you're doing.
Test the gopher server by simulating a request using `echo HomePage |
nc localhost 7070`. You should get back the content of the page you
wrote.
Let's test encryption. Create a self-signed certificate and a private
key. If you use the following command, you can leave all the fields
empty except for the common name. The common name you provide must
match the server name you are using. In our case, that would be
`localhost`.
```
openssl req -new -x509 -days 365 -nodes -out \
gopher-server-cert.pem -keyout gopher-server-key.pem
```
Start the gopher server on port 7443 using this information with
`stuff/gopher-server.pl --port=7443
--wiki_key_file=gopher-server-key.pem
--wiki_cert_file=gopher-server-cert.pem`.
If you test this by simulating an unencrypted request using `echo
HomePage | nc localhost 7443`, you shouldn't get any output. Use `echo
HomePage | gnutls-cli --no-ca-verification localhost:7443` and you
should get back your page. Actually, you have the certificate right
there so you might as well provide it: `echo HomePage | gnutls-cli
--x509cafile=gopher-server-cert.pem localhost:7443`
What you'd expect to see is a lot of cryptography output by
`gnutls-cli` and at the very end the content of the page. If you're
seeing `Fatal error: Error in the pull function` instead, then perhaps
the timing of things is a bit off. Introducing a short wait fixed this
for me. `(sleep 1;echo HomePage) | gnutls-cli
--x509cafile=gopher-server-cert.pem localhost:7443`
Good luck!

777
stuff/gemini-server.pl Executable file
View File

@@ -0,0 +1,777 @@
#!/usr/bin/env perl
# Copyright (C) 20172020 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/>.
package OddMuse;
use strict;
use 5.26.0;
use base qw(Net::Server::Fork); # any personality will do
use List::Util qw(first);
use MIME::Base64;
use Text::Wrap;
use Socket;
our($RunCGI, $DataDir, %IndexHash, @IndexList, $IndexFile, $TagFile, $q,
%Page, $OpenPageName, $MaxPost, $ShowEdits, %Locks, $CommentsPattern,
$CommentsPrefix, $EditAllowed, $NoEditFile, $SiteName, $ScriptName,
$Now, %RecentVisitors, $SurgeProtectionTime, $SurgeProtectionViews,
$SurgeProtection);
# Sadly, we need this information before doing anything else
my %args = (proto => 'ssl');
for (grep(/--wiki_(key|cert)_file=/, @ARGV)) {
$args{SSL_cert_file} = $1 if /--wiki_cert_file=(.*)/;
$args{SSL_key_file} = $1 if /--wiki_key_file=(.*)/;
}
if (not $args{SSL_cert_file} or not $args{SSL_key_file}) {
die "I must have both --wiki_key_file and --wiki_cert_file\n";
} else {
OddMuse->run(%args);
}
sub options {
my $self = shift;
my $prop = $self->{'server'};
my $template = shift;
# setup options in the parent classes
$self->SUPER::options($template);
# add a single value option
$prop->{wiki} ||= undef;
$template->{wiki} = \$prop->{wiki};
$prop->{wiki_dir} ||= undef;
$template->{wiki_dir} = \$prop->{wiki_dir};
$prop->{wiki_pages} ||= [];
$template->{wiki_pages} = $prop->{wiki_pages};
# $prop->{wiki_pem_file} ||= undef;
# $template->{wiki_pem_file} = $prop->{wiki_pem_file};
}
sub post_configure_hook {
my $self = shift;
$self->write_help if $ARGV[0] eq '--help';
$DataDir = $self->{server}->{wiki_dir} || $ENV{WikiDataDir} || '/tmp/oddmuse';
$self->log(3, "PID $$");
$self->log(3, "Host " . ("@{$self->{server}->{host}}" || "*"));
$self->log(3, "Port @{$self->{server}->{port}}");
# Note: if you use sudo to run gemini-server.pl, these options might not work!
$self->log(4, "--wikir_dir says $self->{server}->{wiki_dir}\n");
$self->log(4, "\$WikiDataDir says $ENV{WikiDataDir}\n");
$self->log(3, "Wiki data dir is $DataDir\n");
$RunCGI = 0;
my $wiki = $self->{server}->{wiki} || "./wiki.pl";
$self->log(1, "Running $wiki\n");
unless (my $return = do $wiki) {
$self->log(1, "couldn't parse wiki library $wiki: $@") if $@;
$self->log(1, "couldn't do wiki library $wiki: $!") unless defined $return;
$self->log(1, "couldn't run wiki library $wiki") unless $return;
}
# make sure search is sorted newest first because NewTagFiltered resorts
*OldGeminiFiltered = \&Filtered;
*Filtered = \&NewGeminiFiltered;
*ReportError = sub {
my ($error, $status, $log, @html) = @_;
$self->print_error("Error: $error");
map { ReleaseLockDir($_); } keys %Locks;
exit 2;
};
}
my $usage = << 'EOT';
This server serves a wiki as a gemini site.
It implements Net::Server and thus all the options available to
Net::Server are also available here. Additional options are available:
wiki - this is the path to the Oddmuse script
wiki_dir - this is the path to the Oddmuse data directory
wiki_pages - this is a page to show on the entry menu
wiki_cert_file - the filename containing a certificate in PEM format
wiki_key_file - the filename containing a private key in PEM format
For many of the options, more information can be had in the Net::Server
documentation. This is important if you want to daemonize the server. You'll
need to use --pid_file so that you can stop it using a script, --setsid to
daemonize it, --log_file to write keep logs, and you'll need to set the user or
group using --user or --group such that the server has write access to the data
directory.
For testing purposes, you can start with the following:
--port=1965
The port to listen to, defaults to a random port.
--log_level=4
The log level to use, defaults to 2.
--wiki_dir=/var/oddmuse
The wiki directory, defaults to the value of the "WikiDataDir" environment
variable or "/tmp/oddmuse".
--wiki_lib=/home/alex/src/oddmuse/wiki.pl
The Oddmuse main script, defaults to "./wiki.pl".
--wiki_pages=SiteMap
This adds a page to the main index. Can be used multiple times.
--help
Prints this message.
Example invocation:
/home/alex/src/oddmuse/stuff/gemini-server.pl \
--port=1965 \
--wiki=/home/alex/src/oddmuse/wiki.pl \
--pid_file=/tmp/oddmuse/gemini.pid \
--wiki_dir=/tmp/oddmuse \
--wiki_pages=Homepage \
--wiki_pages=Gemini
Run the script and test it:
echo | nc localhost 7070
lynx gemini://localhost:7070
If you want to use SSL, you need to provide PEM files containing certificate and
private key. To create self-signed files, for example:
openssl req -new -x509 -days 365 -nodes -out \
gemini-server-cert.pem -keyout gemini-server-key.pem
Make sure the common name you provide matches your domain name!
Note that parameters should not contain spaces. Thus:
/home/alex/src/oddmuse/stuff/gemini-server.pl \
--port=1965 \
--log_level=3 \
--wiki=/home/alex/src/oddmuse/wiki.pl \
--wiki_dir=/home/alex/alexschroeder
EOT
run();
sub NewGeminiFiltered {
my @pages = OldGeminiFiltered(@_);
@pages = sort newest_first @pages;
return @pages;
}
sub success {
my $self = shift;
my $type = shift || 'text/gemini; charset=UTF-8';
print "20 $type\r\n"
}
sub normal_to_free {
my $title = shift;
$title =~ s/_/ /g;
return $title;
}
sub free_to_normal {
my $title = shift;
$title =~ s/^ +//g;
$title =~ s/ +$//g;
$title =~ s/ +/_/g;
return $title;
}
sub host {
my $self = shift;
return $self->{server}->{host}->[0]
|| $self->{server}->{sockaddr};
}
sub port {
my $self = shift;
return $self->{server}->{port}->[0]
|| $self->{server}->{sockport};
}
sub base {
my $self = shift;
my $host = $self->host();
my $port = $self->port();
return "gemini://$host:$port/";
}
sub link {
my $self = shift;
my $id = shift;
return $self->base() . UrlEncode($id);
}
sub print_link {
my $self = shift;
my $title = shift;
my $id = shift;
my $url = $self->link($id);
print "=> $url $title\r\n";
}
sub serve_main_menu {
my $self = shift;
$self->log(3, "Serving main menu");
$self->success();
say "Welcome to the Gemini version of this wiki.";
say "";
say "Blog:";
my @pages = sort { $b cmp $a } grep(/^\d\d\d\d-\d\d-\d\d/, @IndexList);
# we should check for pages marked for deletion!
for my $id (@pages[0..9]) {
$self->print_link(normal_to_free($id), free_to_normal($id));
}
$self->print_link("More...", "do/more");
say "";
for my $id (@{$self->{server}->{wiki_pages}}) {
$self->print_link(normal_to_free($id), free_to_normal($id));
}
$self->print_link("Recent Changes", "do/rc");
$self->print_link("Index of all pages", "do/index");
if ($TagFile) {
$self->print_link("Index of all tags", "do/tags");
}
}
sub serve_archive {
my $self = shift;
$self->success();
$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_link(normal_to_free($id), free_to_normal($id));
}
}
sub serve_index {
my $self = shift;
$self->success();
$self->log(3, "Serving index of all pages");
for my $id (sort newest_first @IndexList) {
$self->print_link(normal_to_free($id), free_to_normal($id));
}
}
sub serve_match {
my $self = shift;
my $match = shift;
$self->log(3, "Serving pages matching " . UrlEncode($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" . normal_to_free($id), free_to_normal($id) . "/menu");
}
}
sub serve_search {
my $self = shift;
my $str = shift;
$self->log(3, "Serving search result for " . UrlEncode($str));
$self->print_info("Use regular expressions separated by spaces.");
SearchTitleAndBody($str, sub {
my $id = shift;
$self->print_menu("1" . normal_to_free($id), free_to_normal($id) . "/menu");
});
}
sub serve_tags {
my $self = shift;
$self->success();
$self->log(3, "Serving tag cloud");
# open the DB file
my %h = TagReadHash();
my %count = ();
foreach my $tag (grep !/^_/, keys %h) {
$count{$tag} = @{$h{$tag}};
}
foreach my $id (sort { $count{$b} <=> $count{$a} } keys %count) {
$self->print_link(normal_to_free($id), free_to_normal($id) . "/tag");
}
}
sub serve_rc {
my $self = shift;
my $showedit = $ShowEdits = shift;
$self->log(3, "Serving recent changes"
. ($showedit ? " including minor changes" : ""));
$self->success();
say "Recent Changes";
if ($showedit) {
$self->print_link("Skip minor edits", "do/rc");
} else {
$self->print_link("Show minor edits", "do/rc/showedits");
}
$self->print_link("Show RSS", "do/rss");
ProcessRcLines(
sub {
my $date = shift;
say "";
say "$date";
say "";
},
sub {
my($id, $ts, $author_host, $username, $summary, $minor, $revision,
$languages, $cluster, $last) = @_;
$self->print_link(normal_to_free($id), free_to_normal($id));
for my $line (split(/\n/, wrap(' ', ' ', $summary))) {
say $line;
}
});
}
sub serve_rss {
my $self = shift;
$self->log(3, "Serving Gemini RSS");
$self->success("application/rss+xml");
my $rss = GetRcRss();
# $rss =~ s!$ScriptName\?action=rss!${gemini}1do/rss!g;
# $rss =~ s!$ScriptName\?action=history;id=([^[:space:]<]*)!${gemini}1$1/history!g;
# $rss =~ s!$ScriptName/([^[:space:]<]*)!${gemini}0$1!g;
$rss =~ s!<wiki:diff>.*</wiki:diff>\n!!g;
print $rss;
}
sub serve_page_comment_link {
my $self = shift;
my $id = shift;
my $revision = shift;
if (not $revision and $CommentsPattern) {
if ($id =~ /$CommentsPattern/) {
my $original = $1;
# sometimes we are on a comment page and cannot derive the original
$self->print_menu("1" . "Back to the original page",
"$original/menu") if $original;
$self->print_menu("w" . "Add a comment", free_to_normal($id) . "/append/text");
} else {
my $comments = free_to_normal($CommentsPrefix . $id);
$self->print_menu("1" . "Comments on this page", "$comments/menu");
}
}
}
sub serve_page_history_link {
my $self = shift;
my $id = shift;
my $revision = shift;
if (not $revision) {
$self->print_menu("1" . "Page History", free_to_normal($id) . "/history");
}
}
sub serve_file_page_menu {
my $self = shift;
my $id = shift;
my $type = shift;
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 . normal_to_free($id)
. ($revision ? "/$revision" : ""), free_to_normal($id));
$self->serve_page_comment_link($id, $revision);
$self->serve_page_history_link($id, $revision);
}
sub serve_text_page_menu {
my $self = shift;
my $id = shift;
my $page = shift;
my $revision = shift;
$self->log(3, "Serving text page menu for $id"
. ($revision ? "/$revision" : ""));
$self->print_info("The text of this page:");
$self->print_menu("0" . normal_to_free($id),
free_to_normal($id) . ($revision ? "/$revision" : ""));
$self->print_menu("h" . normal_to_free($id),
free_to_normal($id) . ($revision ? "/$revision" : "") . "/html");
$self->print_menu("w" . "Replace " . normal_to_free($id),
free_to_normal($id) . "/write/text");
$self->serve_page_comment_link($id, $revision);
$self->serve_page_history_link($id, $revision);
my $first = 1;
while ($page->{text} =~ /
\[\[ (?<title>[^\]|]*) (?:\|(?<text>[^\]]*))? \]\]
| \[ (?<url>https?:\/\/\S+) \s+ (?<text>[^\]]*) \]
| (?<url>https?:\/\/\S+)
| \[ (?<text>[^\]]*) \] \( (?<url>https?:\/\/\S+) \)
| \[ geminis?:\/\/ (?<hostname>[^:\/]*) (?::(?<port>\d+))?
(?:\/(?<type>\d)? (?<selector>\S+))? \]
| \[ geminis?:\/\/ (?<hostname>[^:\/]*) (?::(?<port>\d+))?
(?:\/(?<type>\d)? (?<selector>\S+))?
\s+ (?<text>[^\]]+) \]
| \[ (?<text>[^\]]+) \]
\( geminis?:\/\/ (?<hostname>[^:\/]*) (?::(?<port>\d+))?
(?:\/(?<type>\d)? (?<selector>\S+))? \)
/xg) {
# remember $type can be "0" and thus "false" -- use // and defined instead!
my ($title, $text, $url, $hostname,
$port, $type, $selector)
= ($+{title}, $+{text}, $+{url}, $+{hostname},
$+{port}||70, $+{type}//1, $+{selector});
$title =~ s/\n/ /g;
$text =~ s/\n/ /g;
if ($first) {
$self->print_info("");
$self->print_info("Links leaving " . normal_to_free($id) . ":");
$first = 0;
}
if ($hostname and $text) {
$self->print_text(join("\t", $type . $text, $selector, $hostname, $port) . "\r\n");
} elsif ($hostname and $selector) {
$self->print_text(join("\t", "$type$hostname:$port/$type$selector", $selector, $hostname, $port) . "\r\n");
} elsif ($hostname) {
$self->print_text(join("\t", "1$hostname:$port", $selector, $hostname, $port) . "\r\n");
} elsif ($url and $text) {
$self->print_menu("h$text", "URL:" . $url, undef, undef, 1);
} elsif ($url) {
$self->print_menu("h$url", "URL:" . $url, undef, undef, 1);
} elsif ($title and substr($title, 0, 4) eq 'tag:') {
$self->print_menu("1" . ($text||substr($title, 4)),
free_to_normal(substr($title, 4)) . "/tag");
} elsif ($title =~ s!^image[/a-z]* external:!pics/!) {
$self->print_menu("I" . $text||$title, $title); # do not normalize space
} elsif ($title) {
$title =~ s!^image[/a-z]*:!!i;
$self->print_menu("1" . ($text||$title), free_to_normal($title) . "/menu");
}
}
$first = 1;
while ($page->{text} =~ /\[https?:\/\/gemini\.floodgap\.com\/gemini\/gw\?a=gemini%3a%2f%2f(.*?)(?:%3a(\d+))?%2f(.)(\S+)\s+([^\]]+)\]/gi) {
my ($hostname, $port, $type, $selector, $text) = ($1, $2||"70", $3, $4, $5);
if ($first) {
$self->print_info("");
$self->print_info("Gemini links (via Floodgap):");
$first = 0;
}
$selector =~ s/%([0-9a-f][0-9a-f])/chr(hex($1))/eig; # url unescape
$self->print_text(join("\t", $type . $text, $selector, $hostname, $port)
. "\r\n");
}
if ($page->{text} =~ m/<journal search tag:(\S+)>\s*/) {
my $tag = $1;
$self->print_info("");
$self->serve_tag_list($tag);
}
}
sub serve_page_history {
my $self = shift;
my $id = shift;
$self->log(3, "Serving history of " . UrlEncode($id));
OpenPage($id);
$self->print_menu("1" . normal_to_free($id) . " (current)", free_to_normal($id) . "/menu");
$self->print_info(CalcTime($Page{ts})
. " by " . GetAuthor($Page{username})
. ($Page{summary} ? ": $Page{summary}" : "")
. ($Page{minor} ? " (minor)" : ""));
foreach my $revision (GetKeepRevisions($OpenPageName)) {
my $keep = GetKeptRevision($revision);
$self->print_menu("1" . normal_to_free($id) . " ($keep->{revision})",
free_to_normal($id) . "/$keep->{revision}/menu");
$self->print_info(CalcTime($keep->{ts})
. " by " . GetAuthor($keep->{username})
. ($keep->{summary} ? ": $keep->{summary}" : "")
. ($keep->{minor} ? " (minor)" : ""));
}
}
sub get_page {
my $id = shift;
my $revision = shift;
my $page;
if ($revision) {
$OpenPageName = $id;
$page = GetKeptRevision($revision);
} else {
OpenPage($id);
$page = \%Page;
}
return $page;
}
sub serve_page_menu {
my $self = shift;
my $id = shift;
my $revision = shift;
my $page = get_page($id, $revision);
if (my ($type) = TextIsFile($page->{text})) {
$self->serve_file_page_menu($id, $type, $revision);
} else {
$self->serve_text_page_menu($id, $page, $revision);
}
}
sub serve_file_page {
my $self = shift;
my $id = shift;
my $type = shift;
my $page = shift;
$self->log(3, "Serving $id as $type file");
my ($encoded) = $page->{text} =~ /^[^\n]*\n(.*)/s;
my $data = decode_base64($encoded);
$self->success($type);
binmode(STDOUT, ":raw");
print($data);
}
sub serve_text_page {
my $self = shift;
my $id = shift;
my $page = shift;
my $text = $page->{text};
$text =~ s/\[\[tag:([^]]+)\]\]/'#' . join('_', split(' ', $1))/mge;
$self->log(3, "Serving " . UrlEncode($id) . " as " . length($text)
. " bytes of text");
$self->success('text/markdown; charset=UTF-8');
print $text;
}
sub serve_page {
my $self = shift;
my $id = shift;
my $revision = shift;
my $page = get_page($id, $revision);
if (my ($type) = TextIsFile($page->{text})) {
$self->serve_file_page($id, $type, $page);
} else {
$self->serve_text_page($id, $page);
}
}
sub serve_page_html {
my $self = shift;
my $id = shift;
my $revision = shift;
my $page = get_page($id, $revision);
$self->log(3, "Serving " . UrlEncode($id) . " as HTML");
my $title = normal_to_free($id);
print GetHtmlHeader(Ts('%s:', $SiteName) . ' ' . UnWiki($title), $id);
print GetHeaderDiv($id, $title);
print $q->start_div({-class=>'wrapper'});
if ($revision) {
# no locking of the file, no updating of the cache
PrintWikiToHTML($page->{text});
} else {
PrintPageHtml();
}
PrintFooter($id, $revision);
}
sub serve_redirect {
my $self = shift;
my $url = shift;
print qq{<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta http-equiv="refresh" content="0; url=$url">
<title>Redirection</title>
</head>
<body>
If you are not redirected automatically, follow this <a href='$url'>link</a>.
</body>
</html>
};
}
sub newest_first {
my ($A, $B) = ($a, $b);
if ($A =~ /^\d\d\d\d-\d\d-\d\d/ and $B =~ /^\d\d\d\d-\d\d-\d\d/) {
return $B cmp $A;
}
$A cmp $B;
}
sub serve_tag_list {
my $self = shift;
my $tag = shift;
print("Search result for tag $tag:\r\n");
for my $id (sort newest_first TagFind($tag)) {
$self->print_link(normal_to_free($id), free_to_normal($id));
}
}
sub serve_tag {
my $self = shift;
my $tag = shift;
$self->success();
$self->log(3, "Serving tag " . UrlEncode($tag));
if ($IndexHash{$tag}) {
print("This page is about the tag $tag.\r\n");
$self->print_link(normal_to_free($tag), free_to_normal($tag));
print("\r\n");
}
$self->serve_tag_list($tag);
}
sub read_text {
my $self = shift;
my $buf;
while (1) {
my $line = <STDIN>;
if (length($line) == 0) {
sleep(1); # wait for input
next;
}
last if $line =~ /^.\r?\n/m;
$buf .= $line;
if (length($buf) > $MaxPost) {
$buf = substr($buf, 0, $MaxPost);
last;
}
}
$self->log(4, "Received " . length($buf) . " bytes (max is $MaxPost)");
utf8::decode($buf);
$self->log(4, "Received " . length($buf) . " characters");
return $buf;
}
sub allow_deny_hook {
my $self = shift;
my $client = shift;
# clear cookie, read config file
$q = undef;
{
local $SIG{__WARN__} = sub {}; # sooooorryy!! 😭
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 ($ts and ($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
} else {
RefreshIndex();
}
eval {
local $SIG{'ALRM'} = sub {
$self->log(1, "Timeout!");
die "Timed Out!\n";
};
alarm(10); # timeout
my $port = $self->port();
my $base = $self->base();
my $url = <STDIN>; # no loop
$url =~ s/\s+$//g; # no trailing whitespace
$url =~ s!^([^/:]+://[^/:]+)(/.*|)$!$1:$port$2!; # add port
$url .= '/' if $url =~ m!^[^/]+://[^/]+$!; # add missing trailing slash
my $selector = $url;
$selector =~ s/^$base//;
$selector = UrlDecode($selector);
$self->log(3, "Looking at $url");
if ($url !~ "^gemini://") {
$self->log(3, "Cannot serve $url");
print "53 This server only serves the gemini schema\r\n";
} elsif ($url !~ "^$base") {
$self->log(3, "Cannot serve $url");
print "53 This server only serves $base\r\n";
} elsif (not $selector) {
$self->serve_main_menu();
} elsif ($selector eq "do/more") {
$self->serve_archive();
} elsif ($selector eq "do/index") {
$self->serve_index();
# } elsif (substr($url, 0, 9) eq "do/match\t") {
# $self->serve_match(substr($url, 9));
# } elsif (substr($url, 0, 10) eq "do/search\t") {
# $self->serve_search(substr($url, 10));
} elsif ($selector eq "do/tags") {
$self->serve_tags();
} elsif ($selector eq "do/rc") {
$self->serve_rc(0);
} elsif ($selector eq "do/rss") {
$self->serve_rss(0);
} elsif ($selector eq "do/rc/showedits") {
$self->serve_rc(1);
# } elsif ($url eq "do/new") {
# my $data = $self->read_text();
# $self->write_text_page(undef, $data);
# } elsif ($url =~ m!^([^/]*)/(\d+)/menu$!) {
# $self->serve_page_menu($1, $2);
# } elsif (substr($url, -5) eq '/menu') {
# $self->serve_page_menu(substr($url, 0, -5));
} elsif ($selector =~ m!^([^/]*)/tag$!) {
$self->serve_tag($1);
# } elsif ($url =~ m!^([^/]*)(?:/(\d+))?/html!) {
# $self->serve_page_html($1, $2);
# } elsif ($url =~ m!^([^/]*)/history$!) {
# $self->serve_page_history($1);
# } elsif ($url =~ m!^([^/]*)/write/text$!) {
# my $data = $self->read_text();
# $self->write_text_page($1, $data);
# } elsif ($url =~ m!^([^/]*)/append/text$!) {
# my $data = $self->read_text();
# $self->append_text_page($1, $data);
# } elsif ($url =~ m!^([^/]*)(?:/([a-z]+/[-a-z]+))?/write/file(?:\t(\d+))?$!) {
# my $data = $self->read_file($3);
# $self->write_file_page($1, $data, $2);
} elsif ($selector =~ m!^([^/]*)(?:/(\d+))?$!) {
$self->log(3, "Serve page $selector");
$self->serve_page($1, $2);
} else {
$self->log(3, "Unknown $selector");
print "40 " . (ValidId($url) || 'Cause unknown') . "\r\n";
}
$self->log(4, "Done");
}
}

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env perl
# Copyright (C) 20172018 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20172019 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
@@ -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);
$CommentsPrefix, $EditAllowed, $NoEditFile, $SiteName, $ScriptName,
$Now, %RecentVisitors, $SurgeProtectionTime, $SurgeProtectionViews,
$SurgeProtection);
my $external_image_path = '/home/alex/alexschroeder.ch/pics/';
@@ -75,10 +78,14 @@ sub post_configure_hook {
$self->write_help if $ARGV[0] eq '--help';
$DataDir = $self->{server}->{wiki_dir} || $ENV{WikiDataDir} || '/tmp/oddmuse';
$self->log(3, "PID $$");
$self->log(3, "Host " . ("@{$self->{server}->{host}}" || "*"));
$self->log(3, "Port @{$self->{server}->{port}}");
# Note: if you use sudo to run gopher-server.pl, these options might not work!
$self->log(4, "--wikir_dir says $self->{server}->{wiki_dir}\n");
$self->log(4, "\$WikiDataDir says $ENV{WikiDataDir}\n");
$self->log(3, "Wiki data dir is $DataDir\n");
$RunCGI = 0;
@@ -93,6 +100,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';
@@ -112,7 +125,7 @@ wiki_key_file - the filename containing a private key in PEM format
For many of the options, more information can be had in the Net::Server
documentation. This is important if you want to daemonize the server. You'll
need to use --pid_file so that you can stop it using a script, --setsid to
daemonize it, --log_file to write keep logs, and you'll net to set the user or
daemonize it, --log_file to write keep logs, and you'll need to set the user or
group using --user or --group such that the server has write access to the data
directory.
@@ -177,6 +190,20 @@ sub NewGopherFiltered {
return @pages;
}
sub normal_to_free {
my $title = shift;
$title =~ s/_/ /g;
return $title;
}
sub free_to_normal {
my $title = shift;
$title =~ s/^ +//g;
$title =~ s/ +$//g;
$title =~ s/ +/_/g;
return $title;
}
sub print_text {
my $self = shift;
my $text = shift;
@@ -221,18 +248,19 @@ sub serve_main_menu {
$self->print_info("Phlog:");
my @pages = sort { $b cmp $a } grep(/^\d\d\d\d-\d\d-\d\d/, @IndexList);
# we should check for pages marked for deletion!
for my $id (@pages[0..9]) {
$self->print_menu("1" . NormalToFree($id), "$id/menu");
$self->print_menu("1" . normal_to_free($id), free_to_normal($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), free_to_normal($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/" . free_to_normal($id));
}
$self->print_menu("1" . "Recent Changes", "do/rc");
@@ -255,7 +283,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), free_to_normal($id) . "/menu");
}
}
@@ -263,7 +291,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), free_to_normal($id) . "/menu");
}
}
@@ -274,7 +302,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), free_to_normal($id) . "/menu");
}
}
@@ -285,7 +313,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), free_to_normal($id) . "/menu");
});
}
@@ -299,7 +327,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), free_to_normal($id) . "/tag");
}
}
@@ -326,7 +354,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), free_to_normal($id) . "/menu");
for my $line (split(/\n/, wrap(' ', ' ', $summary))) {
$self->print_info($line);
}
@@ -379,9 +407,9 @@ sub serve_page_comment_link {
# sometimes we are on a comment page and cannot derive the original
$self->print_menu("1" . "Back to the original page",
"$original/menu") if $original;
$self->print_menu("w" . "Add a comment", "$id/append/text");
$self->print_menu("w" . "Add a comment", free_to_normal($id) . "/append/text");
} else {
my $comments = $CommentsPrefix . $id;
my $comments = free_to_normal($CommentsPrefix . $id);
$self->print_menu("1" . "Comments on this page", "$comments/menu");
}
}
@@ -392,7 +420,7 @@ sub serve_page_history_link {
my $id = shift;
my $revision = shift;
if (not $revision) {
$self->print_menu("1" . "Page History", "$id/history");
$self->print_menu("1" . "Page History", free_to_normal($id) . "/history");
}
}
@@ -403,8 +431,8 @@ 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)
. ($revision ? "/$revision" : ""), $id);
$self->print_menu($code . normal_to_free($id)
. ($revision ? "/$revision" : ""), free_to_normal($id));
$self->serve_page_comment_link($id, $revision);
$self->serve_page_history_link($id, $revision);
}
@@ -414,41 +442,65 @@ sub serve_text_page_menu {
my $id = shift;
my $page = shift;
my $revision = shift;
$self->log(3, "Serving text page menu for " . UrlEncode($id)
$self->log(3, "Serving text page menu for $id"
. ($revision ? "/$revision" : ""));
$self->print_info("The text of this page:");
$self->print_menu("0" . NormalToFree($id),
$id . ($revision ? "/$revision" : ""));
$self->print_menu("h" . NormalToFree($id),
$id . ($revision ? "/$revision" : "") . "/html");
$self->print_menu("w" . "Replace " . NormalToFree($id),
$id . "/write/text");
$self->print_menu("0" . normal_to_free($id),
free_to_normal($id) . ($revision ? "/$revision" : ""));
$self->print_menu("h" . normal_to_free($id),
free_to_normal($id) . ($revision ? "/$revision" : "") . "/html");
$self->print_menu("w" . "Replace " . normal_to_free($id),
free_to_normal($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>[^\]]*) \]
| (?<url>https?:\/\/\S+)
| \[ (?<text>[^\]]*) \] \( (?<url>https?:\/\/\S+) \)
| \[ gophers?:\/\/ (?<hostname>[^:\/]*) (?::(?<port>\d+))?
(?:\/(?<type>\d)? (?<selector>\S+))? \]
| \[ gophers?:\/\/ (?<hostname>[^:\/]*) (?::(?<port>\d+))?
(?:\/(?<type>\d)? (?<selector>\S+))?
\s+ (?<text>[^\]]+) \]
| \[ (?<text>[^\]]+) \]
\( gophers?:\/\/ (?<hostname>[^:\/]*) (?::(?<port>\d+))?
(?:\/(?<type>\d)? (?<selector>\S+))? \)
/xg) {
# remember $type can be "0" and thus "false" -- use // and defined instead!
my ($title, $text, $url, $hostname,
$port, $type, $selector)
= ($+{title}, $+{text}, $+{url}, $+{hostname},
$+{port}||70, $+{type}//1, $+{selector});
$title =~ s/\n/ /g;
$text =~ s/\n/ /g;
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) {
if ($hostname and $text) {
$self->print_text(join("\t", $type . $text, $selector, $hostname, $port) . "\r\n");
} elsif ($url) {
} elsif ($hostname and $selector) {
$self->print_text(join("\t", "$type$hostname:$port/$type$selector", $selector, $hostname, $port) . "\r\n");
} elsif ($hostname) {
$self->print_text(join("\t", "1$hostname:$port", $selector, $hostname, $port) . "\r\n");
} elsif ($url and $text) {
$self->print_menu("h$text", "URL:" . $url, undef, undef, 1);
} elsif ($url) {
$self->print_menu("h$url", "URL:" . $url, undef, undef, 1);
} elsif ($title and substr($title, 0, 4) eq 'tag:') {
$self->print_menu("1" . ($text||substr($title, 4)),
substr($title, 4) . "/tag");
free_to_normal(substr($title, 4)) . "/tag");
} elsif ($title =~ s!^image[/a-z]* external:!pics/!) {
$self->print_menu("I" . $text||$title, $title);
$self->print_menu("I" . $text||$title, $title); # do not normalize space
} 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), free_to_normal($title) . "/menu");
}
}
@@ -464,7 +516,7 @@ sub serve_text_page_menu {
$self->print_text(join("\t", $type . $text, $selector, $hostname, $port)
. "\r\n");
}
if ($page->{text} =~ m/<journal search tag:(\S+)>\s*/) {
my $tag = $1;
$self->print_info("");
@@ -478,18 +530,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)", free_to_normal($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})",
"$id/$keep->{revision}/menu");
$self->print_menu("1" . normal_to_free($id) . " ($keep->{revision})",
free_to_normal($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)" : ""));
}
@@ -544,9 +596,10 @@ sub serve_text_page {
my $id = shift;
my $page = shift;
my $text = $page->{text};
$text =~ s/^\./../mg;
$text =~ s/\[\[tag:([^]]+)\]\]/'#' . join('_', split(' ', $1))/mge;
$self->log(3, "Serving " . UrlEncode($id) . " as " . length($text)
. " bytes of text");
$text =~ s/^\./../mg;
$self->print_text($text);
}
@@ -570,7 +623,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 +684,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), free_to_normal($id) . "/menu");
}
}
@@ -641,7 +694,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), free_to_normal($tag) . "/menu");
$self->print_info("");
}
$self->serve_tag_list($tag);
@@ -679,7 +732,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), free_to_normal($id) . "/menu");
}
sub write_page_error {
@@ -795,13 +848,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

4
stuff/hypnotoad.pl Normal file
View File

@@ -0,0 +1,4 @@
use Mojo::Server::Hypnotoad;
warn "Use hypnotoad -s stuff/hypnotoad.pl to stop the server\n";
my $hypnotoad = Mojo::Server::Hypnotoad->new;
$hypnotoad->run('stuff/mojolicious-app.pl');

17
stuff/mojolicious-app.pl Normal file
View File

@@ -0,0 +1,17 @@
# From the root directory, run one of the following:
# 1. stuff/mojolicious-app.pl daemon -l http://localhost:8080
# 2. stuff/hypnotoad.pl
# 3. stuff/toadfarm.pl start
use Mojolicious::Lite;
plugin CGI => {
support_semicolon_in_query_string => 1,
};
plugin CGI => {
route => '/',
script => 'wiki.pl',
};
app->start;

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) {

29
stuff/server.pl Normal file → Executable file
View File

@@ -1,4 +1,4 @@
#!/bin/env perl
#!/usr/bin/env perl
# Copyright (C) 2015 Alex Schroeder <alex@gnu.org>
# This program is free software: you can redistribute it and/or modify it under
@@ -13,6 +13,23 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
# What is this?
# =============
#
# This is a script that will server a wiki using a web server written in Perl,
# without a fancy framework like Mojolicious. Instead, it uses
# HTTP::Server::Simple::CGI.
#
# A simple usecase would be that you have had a wiki running years ago but then
# you forgot all about it and your Apache config no longer works and who knows
# how the system Perl is doing. So check out the data dir and notice that the
# files belong to a user called _www... And so you run the following:
#
# sudo -u _www perl stuff/server.pl ./wiki.pl 3000 \
# /Users/alex/WebServer/Oddmuse
#
# Your old wiki is served on localhost:3000 for you to examine.
my $wiki = $ARGV[0] || './wiki.pl';
my $port = $ARGV[1] || 8080;
my $dir = $ARGV[2];
@@ -33,6 +50,10 @@ $ENV{WikiDataDir} = $dir if $dir;
package OddMuse;
$q = shift;
# The equivalent of use CGI qw(-utf8) because it didn't work as part of
# cgi_init.
$CGI::PARAM_UTF8++;
# NPH, or "no-parsed-header", scripts bypass the server completely by
# sending the complete HTTP header directly to the browser.
$q->nph(1);
@@ -44,10 +65,10 @@ $ENV{WikiDataDir} = $dir if $dir;
die <<'EOT' unless -f $wiki;
Usage: perl server.pl [WIKI [PORT [DIR]]]
Example: perl server.pl wiki.pl 8080 ~/src/oddmuse/test-data
Example: perl server.pl ./wiki.pl 8080 ~/src/oddmuse/test-data
You may provide the Oddmuse wiki script on the command line. If you do not
provide it, WIKI will default to 'wiki.pl'.
provide it, WIKI will default to './wiki.pl'.
You may provide a port number on the command line. If you do not provide it,
PORT will default to 8080.
@@ -67,7 +88,7 @@ echo <<EOF > "$WikiDataDir/config"
$AdminPass = 'foo';
$ScriptName = 'http://localhost/';
EOF
perl stuff/server.pl wiki.pl &
perl stuff/server.pl ./wiki.pl &
SERVER=$!
sleep 1
w3m http://localhost:8080/

8
stuff/toadfarm.pl Normal file
View File

@@ -0,0 +1,8 @@
use Toadfarm -init;
mount "stuff/mojolicious-app.pl" => {
"Host" => qr{^localhost:8080$},
mount_point => '/',
};
start;

View File

@@ -1,4 +1,4 @@
# Copyright (C) 20062015 Alex Schroeder <alex@gnu.org>
# Copyright (C) 20062019 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
@@ -26,9 +26,6 @@ add_module('atom.pl');
start_server();
# Give the child time to start
sleep 1;
# Check whether the child is up and running
my $ua = LWP::UserAgent->new;
my $response = $ua->get("$ScriptName?action=version");
@@ -141,5 +138,6 @@ sub trim {
s/\s+$//g;
return $_;
}
ok(trim($result->content->body) eq ("<p>" . trim($content) . '</p>'), 'verify content');
my $re = "<p>" . trim($content) . '</p>';
like($result->content->body, qr/$re/, 'verify content');
ok($result->author->name eq $username, 'verify author');

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!"]',);

File diff suppressed because it is too large Load Diff

49
t/common-markdown.t Normal file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env perl
# 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;
use JSON;
use utf8;
add_module('common-markdown.pl');
sub load_tests {
my $spec = "t/common-markdown-spec-0.28.json";
open(my $fh, "<", $spec)
or die "Cannot open $spec: $!";
local $/ = undef;
return decode_json <$fh>;
}
sub normalize {
my $html = shift;
$html =~ s/\n$//s;
return $html;
}
my $tests = load_tests();
for my $test (@$tests) {
my $name = $test->{example} . ". (" . $test->{section} . ")";
my $input = $test->{markdown};
my $output = apply_rules($input, 'p');
my $correct = normalize($test->{html});
is($output, $correct, $name);
}
done_testing();

View File

@@ -100,7 +100,7 @@ bla
quotemeta(qq{<div class="crossbar"><p>bla</p><h2>mu</h2>}),
quotemeta(qq{<p>bla </p><div class="toc"><h2>$TocHeaderText</h2>}),
quotemeta(qq{<ol><li><a href="#${TocAnchorPrefix}1">two</a><ol><li><a href="#${TocAnchorPrefix}2">three</a></li></ol></li><li><a href="#${TocAnchorPrefix}3">one</a></li></ol>}),
quotemeta(qq{one</a></li></ol></div></div><div class="content browse"><p>bla}));
quotemeta(qq{one</a></li></ol></div></div><div class="content browse" lang="en"><p>bla}));
quotemeta(qq{<h2 id="${TocAnchorPrefix}1">two</h2>}),
quotemeta(qq{<h2 id="${TocAnchorPrefix}3">one</h2>}),

Some files were not shown because too many files have changed in this diff Show More