Wiki controller handles all the wiki action. Redirect /index to /wiki to have an easily-editable front page.
--- /dev/null
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('',-1,FALSE,FALSE,FALSE);
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('',11,TRUE,TRUE,FALSE);
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('',1,TRUE,TRUE,TRUE);
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('',3,TRUE,TRUE,TRUE);
+
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('Info',-1,FALSE,FALSE,FALSE);
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('Info',1,TRUE,TRUE,TRUE);
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('Info',3,TRUE,TRUE,TRUE);
+
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('Members',2,TRUE,TRUE,FALSE);
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('Members',1,TRUE,TRUE,TRUE);
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('Members',3,TRUE,TRUE,TRUE);
+
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('HC',1,TRUE,TRUE,TRUE);
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('HC',3,TRUE,TRUE,TRUE);
+
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('Tech',-1,FALSE,FALSE,FALSE);
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('Tech',2,TRUE,TRUE,FALSE);
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('Tech',1,TRUE,TRUE,TRUE);
+INSERT INTO wiki_namespace_access (namespace,gid,edit,post,moderate) VALUES ('Tech',3,TRUE,TRUE,TRUE);
--- /dev/null
+CREATE TABLE wiki_namespaces (
+ namespace VARCHAR(16) PRIMARY KEY
+);
+
+CREATE TABLE wiki_pages (
+ wpid SERIAL PRIMARY KEY,
+ name VARCHAR(255) NOT NULL,
+ namespace TExt NOT NULL REFERENCES wiki_namespaces(namespace) DEFAULT '',
+ textsearch tsvector NOT NULL DEFAULT to_tsvector(''),
+ UNIQUE(namespace,name)
+);
+
+CREATE INDEX wiki_pages_textsearch_index ON wiki_pages USING gin (textsearch);
+
+CREATE TABLE wiki_page_revisions (
+ wpid INTEGER REFERENCES wiki_pages(wpid),
+ wprev SERIAL NOT NULL PRIMARY KEY,
+ parent INTEGER REFERENCES wiki_page_revisions(wprev),
+ text TEXT NOT NULL,
+ comment TEXT NOT NULL,
+ time TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+ uid INTEGER REFERENCES users(uid)
+);
+
+ALTER TABLE wiki_pages ADD COLUMN wprev INTEGER REFERENCES wiki_page_revisions(wprev);
+
+CREATE OR REPLACE FUNCTION update_wiki_page() RETURNS trigger
+AS $$
+DECLARE
+ rec RECORD;
+BEGIN
+ SELECT setweight(to_tsvector(wpr.text), 'D') AS ts
+ INTO STRICT rec
+ FROM wiki_page_revisions wpr
+ WHERE NEW.wprev = wpr.wprev;
+ NEW.textsearch := rec.ts
+ || setweight(to_tsvector(NEW.namespace || ':' || NEW.name), 'A');
+ return NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER update_wiki_page
+ BEFORE UPDATE ON wiki_pages
+ FOR EACH ROW
+ EXECUTE PROCEDURE update_wiki_page();
+
+CREATE TABLE wiki_namespace_access (
+ namespace TEXT NOT NULL REFERENCES wiki_namespaces(namespace),
+ gid INTEGER NOT NULL REFERENCES groups(gid),
+ edit BOOL NOT NULL DEFAULT FALSE,
+ post BOOL NOT NULL DEFAULT FALSE,
+ moderate BOOL NOT NULL DEFAULT FALSE,
+ PRIMARY KEY(gid,namespace)
+);
+
+CREATE TABLE wiki_page_access (
+ wpid INTEGER NOT NULL REFERENCES wiki_pages(wpid),
+ uid INTEGER NOT NULL REFERENCES users(uid),
+ edit BOOL NOT NULL DEFAULT FALSE,
+ moderate BOOL NOT NULL DEFAULT FALSE,
+ PRIMARY KEY(uid,wpid)
+);
+
+INSERT INTO wiki_namespaces VALUES ('');
+INSERT INTO wiki_namespaces VALUES ('Members');
+INSERT INTO wiki_namespaces VALUES ('HC');
+INSERT INTO wiki_namespaces VALUES ('Tech');
+INSERT INTO wiki_namespaces VALUES ('Info');
+
+INSERT INTO wiki_pages (name,namespace) VALUES('Main','Info');
+INSERT INTO wiki_page_revisions (wpid,text,comment,uid) VALUES(1,'Welcome to the main page!', 'First revision', 1);
+UPDATE wiki_pages set wprev = 1 WHERE wpid = 1;
+
sub index : Local Path Args(0) {
my ( $self, $c ) = @_;
+
+ $c->res->redirect($c->uri_for('/wiki'));
}
sub default : Path {
--- /dev/null
+package NDWeb::Controller::Wiki;
+
+use strict;
+use warnings;
+use parent 'Catalyst::Controller';
+
+use Text::MediawikiFormat prefix => '/wiki/';
+
+=head1 NAME
+
+NDWeb::Controller::Wiki - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub auto : Priate {
+ my ( $self, $c ) = @_;
+
+ $c->stash(wikiformat => \&wikiformat);
+}
+
+sub index :Path :Args(0) {
+ my ( $self, $c ) = @_;
+
+ push @{$c->req->captures}, ('Info','Main');
+ $c->forward('page');
+ $c->stash(template => 'wiki/page.tt2');
+}
+
+sub page : LocalRegex(^(?:([A-Z]\w*)(?::|%3A))?([A-Z]\w*)$) {
+ my ( $self, $c ) = @_;
+ my $dbh = $c->model;
+
+ $c->forward('findPage');
+ $c->acl_access_denied('test',$c->action,'No edit access for this page')
+ if defined $c->stash->{page}->{view} && !$c->stash->{page}->{view};
+ $c->forward('loadText');
+
+ unless ($c->stash->{page}->{wpid}){
+ $c->stash->{page}->{namespace} = $c->req->captures->[0];
+ $c->stash->{page}->{name} = $c->req->captures->[1];
+ $c->stash->{page}->{fullname} = ($c->stash->{page}->{namespace} ? $c->stash->{page}->{namespace}.':' : '')
+ . $c->stash->{page}->{name};
+ $c->stash->{page}->{post} = $dbh->selectrow_array(q{SELECT post
+ FROM wiki_namespace_access
+ WHERE namespace = COALESCE($1,'') AND post AND gid IN (SELECT groups($2))
+ },undef,$c->stash->{page}->{namespace}, $c->stash->{UID});
+ }
+ $c->stash(title => $c->stash->{page}->{fullname});
+}
+
+sub edit : LocalRegex(^edit/(?:([A-Z]\w*)(?::|%3A))?([A-Z]\w*)$) {
+ my ( $self, $c ) = @_;
+ my $dbh = $c->model;
+
+ $c->forward('findPage');
+ $c->acl_access_denied('test',$c->action,'No edit access for this page')
+ if defined $c->stash->{page}->{edit} && !$c->stash->{page}->{edit};
+ $c->forward('loadText');
+ $c->forward('findNamespaces');
+
+ unless ($c->stash->{page}->{wpid}){
+ $c->acl_access_denied('test',$c->action,'No edit access for this page')
+ unless @{$c->stash->{namespaces}};
+ $c->stash->{page}->{namespace} = $c->req->captures->[0];
+ $c->stash->{page}->{name} = $c->req->captures->[1];
+ }
+}
+
+sub history : LocalRegex(^history/(?:([A-Z]\w*)(?::|%3A))?([A-Z]\w*)$) {
+ my ( $self, $c ) = @_;
+ my $dbh = $c->model;
+
+ $c->forward('findPage');
+
+ my $query = $dbh->prepare(q{SELECT wprev,time,username,comment
+ FROM wiki_page_revisions JOIN users u USING (uid)
+ WHERE wpid = $1
+ ORDER BY time DESC
+ });
+ $query->execute($c->stash->{page}->{wpid});
+ $c->stash(revisions => $query->fetchall_arrayref({}) );
+ $c->stash(title => 'History for ' . $c->stash->{page}->{fullname});
+}
+
+sub postedit : Local {
+ my ( $self, $c, $p ) = @_;
+ my $dbh = $c->model;
+
+ eval {
+ $dbh->begin_work;
+
+ my $wpid = $c->req->param('wpid');
+ if ( $wpid eq 'new'){
+ my $namespace = $dbh->selectrow_array(q{SELECT namespace
+ FROM wiki_namespace_access
+ WHERE namespace = $1 AND post AND gid IN (SELECT groups($2))
+ },undef,$c->req->param('namespace'), $c->stash->{UID});
+
+ my $query = $dbh->prepare(q{INSERT INTO wiki_pages (namespace,name) VALUES($1,$2) RETURNING wpid});
+ $query->execute($namespace,$c->req->param('name'));
+ $wpid = $query->fetchrow;
+ }
+ $c->forward('findPage',[$wpid]);
+ $c->acl_access_denied('test',$c->action,'No edit access for this page')
+ if defined $c->stash->{page}->{edit} && !$c->stash->{page}->{edit};
+
+ my $query = $dbh->prepare(q{INSERT INTO wiki_page_revisions
+ (wpid,parent,text,comment,uid) VALUES($1,$2,$3,$4,$5)
+ RETURNING wprev
+ });
+ $c->req->params->{parent}||= undef;
+ $query->execute($wpid,$c->req->param('parent'),$c->req->param('text')
+ ,$c->req->param('comment'),$c->stash->{UID});
+ my $rev = $query->fetchrow;
+ $dbh->do(q{UPDATE wiki_pages SET wprev = $1 WHERE wpid = $2}
+ ,undef,$rev,$wpid);
+
+ $dbh->commit;
+ $c->res->redirect($c->uri_for($c->stash->{page}->{fullname}));
+ return;
+ } if ($c->req->param('cmd') eq 'Submit');
+
+ if ($@){
+ if ($@ =~ /duplicate key value violates unique constraint "wiki_pages_namespace_key"/){
+ $c->stash(error => "Page does already exist");
+ }elsif ($@ =~ /value too long for type character varying\(255\)/){
+ $c->stash(error => 'The name is too long, keep it to max 255 characters');
+ }else{
+ $c->stash(error => $@);
+ }
+ $dbh->rollback;
+ }
+
+ $c->forward('findPage') if $p;
+ $c->forward('findNamespaces');
+
+ $c->stash->{page}->{namespace} = $c->req->param('namespace');
+ $c->stash->{page}->{name} = $c->req->param('name');
+
+ $c->stash(text => $c->req->param('text'));
+ $c->stash(template => 'wiki/edit.tt2');
+}
+
+sub findPage : Private {
+ my ( $self, $c, $p ) = @_;
+ my $dbh = $c->model;
+
+ my @arguments = ($c->stash->{UID});
+ my $where;
+ if ($p){
+ $where = q{AND wpid = $2};
+ push @arguments, $p;
+ }else{
+ $where = q{AND (namespace = COALESCE($2,'') AND name = $3)};
+ push @arguments, @{$c->req->captures};
+ }
+
+ my $query = q{SELECT wpid,namespace,name,wprev
+ ,(CASE WHEN namespace <> '' THEN namespace || ':' ELSE '' END) || name AS fullname
+ ,bool_or(COALESCE(wpa.edit,wna.edit)) AS edit
+ ,bool_or(wna.post) AS post
+ ,bool_or(wpa.moderate OR wna.moderate) AS moderate
+ ,bool_or(wpa.wpid IS NOT NULL OR wna.namespace IS NOT NULL) AS view
+ FROM wiki_pages wp
+ LEFT OUTER JOIN (SELECT * FROM wiki_namespace_access
+ WHERE gid IN (SELECT groups($1))) wna USING (namespace)
+ LEFT OUTER JOIN (SELECT * FROM wiki_page_access
+ WHERE uid = $1) wpa USING (wpid)
+ WHERE TRUE
+ } . $where . q{ GROUP BY wpid,namespace,name,wprev};
+ $query = $dbh->prepare($query);
+ $query->execute(@arguments);
+
+ my $page = $query->fetchrow_hashref;
+ $c->stash(page => $page);
+}
+
+sub loadText : Private {
+ my ( $self, $c, $p ) = @_;
+ my $dbh = $c->model;
+
+ my $text = $dbh->selectrow_array(q{SELECT text
+ FROM wiki_page_revisions WHERE wprev = $1
+ },undef,$c->stash->{page}->{wprev});
+ $c->stash(text => $text);
+}
+
+sub findNamespaces : Private {
+ my ( $self, $c, $p ) = @_;
+ my $dbh = $c->model;
+
+ my $query = $dbh->prepare(q{SELECT namespace FROM wiki_namespaces
+ WHERE namespace IN (SELECT namespace FROM wiki_namespace_access WHERE post AND gid IN (SELECT groups($1)))
+ ORDER BY namespace
+ });
+ $query->execute($c->stash->{UID});
+ $c->stash(namespaces => $query->fetchall_arrayref({}) );
+}
+
+
+=head1 AUTHOR
+
+Michael Andreen (harv@ruin.nu)
+
+=head1 LICENSE
+
+GPL 2.0, or later
+
+=cut
+
+1;
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
- <title>[% site.title %]: [% template.title %]</title>
+ <title>[% site.title %]: [% title or template.title %]</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link rel="stylesheet" type="text/css" href="/static/default.css">
<link rel="stylesheet" type="text/css" href="/static/css/[% user.css or "black" %].css">
<ul class="linkbar">
- <li><a href="[% c.check_user_roles("member_menu") ? '/members' : '/index' %]">Main page</a></li>
+ [% IF c.check_user_roles("member_menu") %]<li><a href="/members">Main page</a></li>[% END %]
+ <li><a href="/wiki">Wiki</a></li>
<li><a href="/forum">Forum</a></li>
<li><a href="/forum/search">Forum search</a></li>
[% IF user %]
--- /dev/null
+[% META title = 'Edit wiki page' %]
+<form action="[% c.uri_for('postedit',page.wpid) %]" method="post">
+[% IF page.wpid %]
+ <p><input type="radio" name="wpid" value="[% page.wpid %]" checked>Use old name: [% page.fullname %]
+[% END %]
+[% IF namespaces.size > 0 %]
+ <p><input type="radio" name="wpid" value="new"[% UNLESS page.wpid %] checked[%END%]>Save as new: <select name="namespace">
+ [% FOR n IN namespaces %]
+ <option value="[% n.namespace %]" [%IF n.namespace == page.namespace %]selected[%END%]>[% n.namespace %]</option>
+ [% END %]
+ </select> <input type="text" name="name" value="[% page.name %]"></p>
+[% END %]
+ <div><input type="hidden" name="parent" value="[% page.wprev %]"></div>
+ <p><textarea name="text" cols="80" rows="40">[% text | html %]</textarea></p>
+ <p>Comment: <input style="width: 40em" type="text" name="comment"></p>
+ <p><input type="submit" name="cmd" value="Preview"><input type="submit" name="cmd" value="Submit"></p>
+</form>
+
+<hr>
+
+[% wikiformat(text) %]
+
--- /dev/null
+[% SET name = page.namespace ? "$page.namespace:" : '' %]
+[% name = "$name$page.name" %]
+<h1><a href="[% c.uri_for(name) %]">[% name %]</a></h1>
+<hr>
+
+<table>
+<tr>
+ <th>Time</th><th>User</th><th>Comment</th>
+</tr>
+[% FOR r IN revisions %]
+<tr>
+ <td>[% r.time %]</td>
+ <td>[% r.username %]</td>
+ <td>[% r.comment %]</td>
+</tr>
+[% END %]
+</table>
+
+<hr>
+<a href="[% c.uri_for('edit',name) %]">Edit this page</a>
--- /dev/null
+<h1>[% page.fullname %]</h1>
+<hr>
+
+[% IF page.wpid %]
+[% wikiformat(text) %]
+
+<hr>
+[% IF page.edit %]<a href="[% c.uri_for('edit',page.fullname) %]">Edit this page</a>[% END %]
+<a href="[% c.uri_for('history',page.fullname) %]">View page history</a>
+[% ELSE %]
+
+<p>This page doesn't exist[% IF page.post %], <a href="[% c.uri_for('edit',page.fullname) %]">create it</a>[% END %].</p>
+
+[% END %]
--- /dev/null
+use strict;
+use warnings;
+use Test::More tests => 3;
+
+BEGIN { use_ok 'Catalyst::Test', 'NDWeb' }
+BEGIN { use_ok 'NDWeb::Controller::Wiki' }
+
+ok( request('/wiki')->is_success, 'Request should succeed' );
+
+