diff --git a/docs/upgrades/templates-7.1.1/default-wiki-master.tmpl b/docs/upgrades/templates-7.1.1/default-wiki-master.tmpl
new file mode 100644
index 000000000..fb7bcccd9
--- /dev/null
+++ b/docs/upgrades/templates-7.1.1/default-wiki-master.tmpl
@@ -0,0 +1,16 @@
+#WikiMasterTmpl00000001
+#create
+#namespace:WikiMaster
+#url:default-wiki-master
+#title:Default Wiki Master
+#menuTitle:Default Wiki Master
+
+
+
+
+Other actions
+
diff --git a/docs/upgrades/templates-7.1.1/default-wiki-page-edit.tmpl b/docs/upgrades/templates-7.1.1/default-wiki-page-edit.tmpl
new file mode 100644
index 000000000..77c18fbca
--- /dev/null
+++ b/docs/upgrades/templates-7.1.1/default-wiki-page-edit.tmpl
@@ -0,0 +1,12 @@
+#WikiPageEditTmpl000001
+#create
+#namespace:WikiPage_edit
+#url:default-wiki-page-edit
+#title:Default Wiki Page Edit
+#menuTitle:Default Wiki Page Edit
+
+
+Title:
+Content:
+
+
diff --git a/docs/upgrades/templates-7.1.1/default-wiki-page-history.tmpl b/docs/upgrades/templates-7.1.1/default-wiki-page-history.tmpl
new file mode 100644
index 000000000..985be58a5
--- /dev/null
+++ b/docs/upgrades/templates-7.1.1/default-wiki-page-history.tmpl
@@ -0,0 +1,15 @@
+#WikiPHTmpl000000000001
+#create
+#namespace:WikiPage_pageHistory
+#url:default-wiki-page-history
+#title:Default Page History
+#menuTitle:Default Page History
+
+
diff --git a/docs/upgrades/templates-7.1.1/default-wiki-page-list.tmpl b/docs/upgrades/templates-7.1.1/default-wiki-page-list.tmpl
new file mode 100644
index 000000000..c512d2932
--- /dev/null
+++ b/docs/upgrades/templates-7.1.1/default-wiki-page-list.tmpl
@@ -0,0 +1,13 @@
+#WikiPLTmpl000000000001
+#create
+#namespace:WikiMaster_pageList
+#url:default-wiki-page-list
+#title:Default Page List
+#menuTitle:Default Page List
+
+
+
diff --git a/docs/upgrades/templates-7.1.1/default-wiki-page.tmpl b/docs/upgrades/templates-7.1.1/default-wiki-page.tmpl
new file mode 100644
index 000000000..06d2b2d35
--- /dev/null
+++ b/docs/upgrades/templates-7.1.1/default-wiki-page.tmpl
@@ -0,0 +1,25 @@
+#WikiPageTmpl0000000001
+#create
+#namespace:WikiPage
+#url:default-wiki-page
+#title:Default Wiki Page
+#menuTitle:Default Wiki Page
+
+
+This page
+
+
+
+ - (Protected page)
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/upgrades/templates-7.1.1/default-wiki-recent-changes.tmpl b/docs/upgrades/templates-7.1.1/default-wiki-recent-changes.tmpl
new file mode 100644
index 000000000..d4b9f1098
--- /dev/null
+++ b/docs/upgrades/templates-7.1.1/default-wiki-recent-changes.tmpl
@@ -0,0 +1,11 @@
+#WikiRCTmpl000000000001
+#create
+#namespace:WikiMaster_recentChanges
+#url:default-wiki-recent-changes
+#title:Default Recent Changes
+#menuTitle:Default Recent Changes
+
+
diff --git a/docs/upgrades/upgrade_7.1.0-7.1.1.pl b/docs/upgrades/upgrade_7.1.0-7.1.1.pl
index ee1f33b0d..dcfcbd1bc 100644
--- a/docs/upgrades/upgrade_7.1.0-7.1.1.pl
+++ b/docs/upgrades/upgrade_7.1.0-7.1.1.pl
@@ -21,6 +21,7 @@ my $quiet; # this line required
my $session = start(); # this line required
fixSurvey();
+addWikiAssets($session);
finish($session); # this line required
@@ -31,7 +32,73 @@ sub fixSurvey {
$session->db->write("alter table Survey_questionResponse change response response text");
}
+sub addWikiAssets {
+ my $session = shift;
+ print "\tAdding wiki assets.\n" unless $quiet;
+ $session->db->write($_) for(<<'EOT',
+ CREATE TABLE `WikiMaster` (
+ `assetId` varchar(22) character set utf8 collate utf8_bin NOT NULL,
+ `revisionDate` bigint(20) NOT NULL,
+ `groupToEditPages` varchar(22) character set utf8 collate utf8_bin NOT NULL,
+ `groupToAdminister` varchar(22) character set utf8 collate utf8_bin NOT NULL,
+ `richEditor` varchar(22) character set utf8 collate utf8_bin NOT NULL
+ default 'PBrichedit000000000002',
+ `defaultPage` varchar(22) character set utf8 collate utf8_bin NULL,
+ `masterTemplateId` varchar(22) character set utf8 collate utf8_bin NOT NULL
+ default 'WikiMasterTmpl00000001',
+ `pageTemplateId` varchar(22) character set utf8 collate utf8_bin NOT NULL
+ default 'WikiPageTmpl0000000001',
+ `pageEditTemplateId` varchar(22) character set utf8 collate utf8_bin NOT NULL
+ default 'WikiPageEditTmpl000001',
+ `recentChangesTemplateId` varchar(22) character set utf8 collate utf8_bin NOT NULL
+ default 'WikiRCTmpl000000000001',
+ `pageHistoryTemplateId` varchar(22) character set utf8 collate utf8_bin NOT NULL
+ default 'WikiPHTmpl000000000001',
+ `pageListTemplateId` varchar(22) character set utf8 collate utf8_bin NOT NULL
+ default 'WikiPLTmpl000000000001',
+ PRIMARY KEY (`assetId`, `revisionDate`)
+ ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+EOT
+ <<'EOT',
+ CREATE TABLE `WikiPage` (
+ `assetId` varchar(22) character set utf8 collate utf8_bin NOT NULL,
+ `revisionDate` bigint(20) NOT NULL,
+ `content` mediumtext,
+ `storageId` varchar(22) character set utf8 collate utf8_bin NULL,
+ `views` bigint(20) NOT NULL default 0,
+ PRIMARY KEY (`assetId`, `revisionDate`)
+ ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+EOT
+ <<'EOT',
+ CREATE TABLE `WikiMaster_titleIndex` (
+ `assetId` varchar(22) character set utf8 collate utf8_bin NOT NULL,
+ `pageId` varchar(22) character set utf8 collate utf8_bin NOT NULL,
+ `title` varchar(255) NOT NULL,
+ PRIMARY KEY (`assetId`, `pageId`)
+ );
+EOT
+ # Don't want protection to be versioned, so put it in a
+ # separate table.
+ <<'EOT',
+ CREATE TABLE `WikiPage_protected` (
+ `assetId` varchar(22) character set utf8 collate utf8_bin NOT NULL,
+ PRIMARY KEY (`assetId`)
+ );
+EOT
+ <<'EOT',
+ CREATE TABLE `WikiPage_extraHistory` (
+ `assetId` varchar(22) character set utf8 collate utf8_bin NOT NULL,
+ `userId` varchar(22) character set utf8 collate utf8_bin NOT NULL,
+ `dateStamp` bigint(20) NOT NULL,
+ `actionTaken` varchar(255) NOT NULL default ''
+ );
+EOT
+ );
+
+ my $config = $session->config;
+ $config->addToArray('assets', 'WebGUI::Asset::Wobject::WikiMaster');
+}
# ---- DO NOT EDIT BELOW THIS LINE ----
diff --git a/lib/WebGUI/Asset/WikiPage.pm b/lib/WebGUI/Asset/WikiPage.pm
new file mode 100644
index 000000000..03c09629b
--- /dev/null
+++ b/lib/WebGUI/Asset/WikiPage.pm
@@ -0,0 +1,350 @@
+package WebGUI::Asset::WikiPage;
+
+# -------------------------------------------------------------------
+# WebGUI is Copyright 2001-2006 Plain Black Corporation.
+# -------------------------------------------------------------------
+# Please read the legal notices (docs/legal.txt) and the license
+# (docs/license.txt) that came with this distribution before using
+# this software.
+# -------------------------------------------------------------------
+# http://www.plainblack.com info@plainblack.com
+# -------------------------------------------------------------------
+
+use strict;
+use Tie::IxHash;
+use WebGUI::International;
+use WebGUI::Utility;
+use base 'WebGUI::Asset';
+
+#-------------------------------------------------------------------
+sub definition {
+ my $class = shift;
+ my $session = shift;
+ my $definition = shift;
+ my $i18n = WebGUI::International->new($session, "Asset_WikiPage");
+
+ my %properties;
+ tie %properties, 'Tie::IxHash';
+ %properties =
+ (
+ storageId => { fieldType => 'image',
+ defaultValue => undef },
+ content => { fieldType => "HTMLArea",
+ defaultValue => undef },
+ );
+
+ push @$definition,
+ {
+ assetName => $i18n->get('assetName'),
+ icon => 'wikiPage.gif',
+ autoGenerateForms => 1,
+ tableName => 'WikiPage',
+ className => 'WebGUI::Asset::WikiPage',
+ properties => \%properties,
+ };
+
+ return $class->SUPER::definition($session, $definition);
+}
+
+
+#-------------------------------------------------------------------
+sub getWiki {
+ my $self = shift;
+ my $parent = $self->getParent;
+ return undef unless defined $parent and $parent->isa('WebGUI::Asset::Wobject::WikiMaster');
+ return $parent;
+}
+
+#-------------------------------------------------------------------
+# BUGGO: how to handle this?
+sub duplicate {
+ my $self = shift;
+ my $newAsset = $self->SUPER::duplicate(@_);
+ return $newAsset;
+}
+
+
+#-------------------------------------------------------------------
+# TODO
+sub indexContent {
+ my $self = shift;
+ my $indexer = $self->SUPER::indexContent;
+}
+
+#-------------------------------------------------------------------
+sub purge {
+ my $self = shift;
+ $self->getWiki->updateTitleIndex([$self], from => 'purge');
+ $self->session->db->write("DELETE FROM WikiPage_protected WHERE assetId = ?", [$self->getId]);
+ $self->session->db->write("DELETE FROM WikiPage_extraHistory WHERE assetId = ?", [$self->getId]);
+ return $self->SUPER::purge;
+}
+
+#-------------------------------------------------------------------
+sub purgeRevision {
+ my $self = shift;
+ $self->getWiki->updateTitleIndex([$self], from => 'purgeRevision');
+ return $self->SUPER::purgeRevision;
+}
+
+#-------------------------------------------------------------------
+sub preparePageTemplate {
+ my $self = shift;
+ return $self->{_pageTemplate} if $self->{_pageTemplate};
+ $self->{_pageTemplate} =
+ WebGUI::Asset::Template->new($self->session, $self->getWiki->get('pageTemplateId'));
+ $self->{_pageTemplate}->prepare;
+ return $self->{_pageTemplate};
+}
+
+sub processPageTemplate {
+ my $self = shift;
+ my $content = shift;
+ my $func = shift || $self->session->form->process('func');
+ my $var = {};
+ my $template = $self->preparePageTemplate;
+
+ $var->{content} = $content;
+ $var->{nonexistentPage} = $self->{_nonexistent};
+ $var->{canEdit} = $self->canEdit;
+ $var->{couldEdit} = $self->couldEdit;
+ $var->{canProtect} = $self->canProtect;
+ $var->{isProtected} = $self->isProtected;
+ $var->{inEdit} = isIn($func, qw/edit add/);
+ $var->{inView} = isIn($func, qw/view/) || !defined $func;
+ $var->{inHistory} = isIn($func, qw/pageHistory/);
+ $self->_addFuncTemplateVars($var);
+
+ return $self->processTemplate($var, undef, $template);
+}
+
+sub prepareView {
+ my $self = shift;
+ $self->SUPER::prepareView;
+ $self->preparePageTemplate;
+}
+
+# Buggo, semi-duplication with WikiMaster; move this into a common utility routine somewhere
+sub _addFuncTemplateVars {
+ my $self = shift;
+ my $var = shift;
+ my @funcs = @_;
+ my $i18n = WebGUI::International->new($self->session, 'Asset_WikiPage');
+ my %specialFuncs = ();
+ my $revision = $self->session->form->process('revision');
+ my $revisionSuffix = defined($revision)? ";revision=$revision" : '';
+ @funcs = (qw/view edit pageHistory protect unprotect/) unless @funcs;
+
+ foreach my $func (@funcs) {
+ $var->{$func.'.url'} = $self->getUrl($specialFuncs{$func}
+ || "func=$func$revisionSuffix");
+ $var->{$func.'.text'} = $i18n->get("func $func link text");
+ }
+}
+
+sub view {
+ my $self = shift;
+ my $var = {};
+ my $title = $self->get('title');
+ my $content = $self->getWiki->autolinkHtml($self->get('content'));
+ return $self->getWiki->processMasterTemplate($title, $self->processPageTemplate($content, 'view'));
+}
+
+sub www_view {
+ my $self = shift;
+ return $self->session->privilege->noAccess unless $self->canView;
+ $self->update({ views => $self->get('views')+1 });
+ # TODO: This should probably exist, as the CS has one.
+# $self->session->http->setCacheControl($self->getWiki->get('visitorCacheTimeout'))
+# if ($self->session->user->userId eq '1');
+ $self->session->http->sendHeader;
+ $self->prepareView;
+ return $self->getWiki->processStyle($self->view);
+}
+
+#-------------------------------------------------------------------
+sub isProtected {
+ my $self = shift;
+ return $self->{_isProtected} if exists $self->{_isProtected};
+ ($self->{_isProtected}) = $self->session->db->quickArray("SELECT COUNT(assetId) FROM WikiPage_protected WHERE assetId = ?", [$self->getId]);
+ return $self->{_isProtected};
+}
+
+sub couldEdit {
+ my $self = shift;
+ my $userId = shift || $self->session->user->userId;
+ return 0 if $self->{_nonexistent};
+ return 0 unless $self->getWiki->canEditPages($userId);
+ return 1;
+}
+
+sub canEdit {
+ my $self = shift;
+ my $userId = shift || $self->session->user->userId;
+ return 0 if $self->isProtected and not $self->getWiki->canAdminister($userId);
+ return $self->couldEdit($userId);
+}
+
+sub canProtect {
+ my $self = shift;
+ my $userId = shift || $self->session->user->userId;
+ return 0 if $self->{_nonexistent};
+ return $self->getWiki->canAdminister($userId);
+}
+
+sub processPropertiesFromFormPost {
+ my $self = shift;
+ my $ret = $self->SUPER::processPropertiesFromFormPost(@_);
+ $self->update({ groupIdView => $self->getWiki->get('groupIdView'),
+ groupIdEdit => $self->getWiki->get('groupIdEdit') });
+ $self->getWiki->updateTitleIndex([$self], from => 'edit');
+ return $ret;
+}
+
+sub www_edit {
+ my $self = shift;
+ return $self->session->privilege->insufficient unless $self->canEdit;
+
+ my $template = WebGUI::Asset::Template->new($self->session, $self->getWiki->get('pageEditTemplateId'));
+ my $var = {};
+ my $newPage = 0;
+ $template->prepare;
+
+ if ($self->session->form->process('func') eq 'add') {
+ # New page.
+ $newPage = 1;
+ $var->{'form.header'} = join '',
+ (WebGUI::Form::formHeader($self->session,
+ { action => $self->getWiki->getUrl('func=addPageSave') }),
+ WebGUI::Form::hidden($self->session, { name => 'class', value => ref $self }));
+ } else {
+ # Editing a page.
+ $newPage = 0;
+ $var->{'form.header'} = join '',
+ (WebGUI::Form::formHeader($self->session,
+ { action => $self->getUrl('func=editSave') }));
+ }
+
+ $var->{'form.title'} = WebGUI::Form::text
+ ($self->session, { name => 'title', maxlength => 255,
+ size => 40, value => $self->get('title') });
+ $var->{'form.content'} = WebGUI::Form::HTMLArea
+ ($self->session, { name => 'content', richEditId => $self->getWiki->get('richEditor'),
+ value => $self->get('content') });
+ $var->{'form.submit'} = WebGUI::Form::submit
+ ($self->session, { value => 'Save' });
+ $var->{'form.footer'} = WebGUI::Form::formFooter($self->session);
+ $self->_addFuncTemplateVars($var);
+
+ my $title = "Editing ".(defined($self->get('title'))? $self->get('title') : 'new page');
+
+ return $self->getWiki->processStyle($self->getWiki->processMasterTemplate($title, $self->processPageTemplate($self->processTemplate($var, undef, $template), 'edit')));
+}
+
+sub www_editSave {
+ my $self = shift;
+ return $self->session->privilege->insufficient unless $self->canEdit;
+
+ # TODO: refactor: duplication with A::W::Matrix::www_editListingSave
+ my $oldTag = WebGUI::VersionTag->getWorking($self->session, 1);
+ my $newTag = WebGUI::VersionTag->create
+ ($self->session, { name => (sprintf "%s edit of %s - %s",
+ $self->getWiki->get('title'), $self->get('title'),
+ $self->session->user->username),
+ workflowId => 'pbworkflow000000000003' });
+ $newTag->setWorking;
+
+ my $newSelf = $self->addRevision;
+ my $error = $newSelf->processPropertiesFromFormPost;
+ if (ref $error eq 'ARRAY') {
+ $self->session->stow->set('editFormErrors', $error);
+ $newTag->rollback;
+ $oldTag->setWorking if defined $oldTag;
+ return $self->www_edit;
+ }
+
+ $newSelf->updateHistory('edited');
+ $newTag->requestCommit;
+ $newTag->clearWorking;
+ $oldTag->setWorking if defined $oldTag;
+
+ return $newSelf->www_view;
+}
+
+#-------------------------------------------------------------------
+sub www_pageHistory {
+ my $self = shift;
+ my $ago = WebGUI::International->new($self->session, 'Asset')->get('ago');
+ my $i18n = WebGUI::International->new($self->session, 'Asset_WikiPage');
+
+ # Buggo. What to do about this query?
+ my @history = @{$self->session->db->buildArrayRefOfHashRefs("SELECT h.userId AS userId, u.username AS username, h.dateStamp AS dateStamp, h.actionTaken AS action FROM assetHistory AS h LEFT JOIN users AS u ON h.userId = u.userId WHERE h.assetId = ? AND h.actionTaken IN ('edited', 'trashed') UNION SELECT h.userId AS userId, u.username AS username, h.dateStamp AS dateStamp, h.actionTaken AS action FROM WikiPage_extraHistory AS h LEFT JOIN users AS u ON h.userId = u.userId WHERE h.assetId = ? ORDER BY dateStamp DESC", [$self->getId, $self->getId])};
+ my $dt = $self->session->datetime;
+ my $time = $dt->time;
+
+ foreach my $entry (@history) {
+ $entry->{date} = $dt->epochToHuman($entry->{dateStamp});
+ $entry->{dateInterval} = sprintf '%s %s %s',
+ ($dt->secondsToInterval($time - $entry->{dateStamp}), $ago);
+
+ $entry->{isDelete} = ($entry->{action} eq 'trashed');
+ $entry->{isEdit} = ($entry->{action} eq 'edited');
+ $entry->{isProtect} = ($entry->{action} eq 'protected');
+ $entry->{isUnprotect} = ($entry->{action} eq 'unprotected');
+ if ($entry->{isEdit}) {
+ $entry->{viewUrl} = $self->getUrl('func=view;revision='.$entry->{dateStamp});
+ $entry->{editUrl} = $self->getUrl('func=edit;revision='.$entry->{dateStamp});
+ }
+
+ $entry->{actionN} = $i18n->get('actionN '.$entry->{action});
+ }
+
+ if ($history[-1]{action} eq 'edited') {
+ my $entry = $history[-1];
+ $entry->{action} = 'created';
+ $entry->{isEdit} = 0;
+ $entry->{isCreate} = 1;
+ $entry->{actionN} = $i18n->get('actionN created');
+ }
+
+ my $template = WebGUI::Asset::Template->new($self->session, $self->getWiki->get('pageHistoryTemplateId'));
+ $template->prepare;
+
+ my $var = {};
+ $var->{'ph.entries'} = \@history;
+
+ return $self->getWiki->processStyle($self->getWiki->processMasterTemplate('History of "'.$self->get('title').'"', $self->processPageTemplate($self->processTemplate($var, undef, $template), 'pageHistory')));
+}
+
+#-------------------------------------------------------------------
+sub updateWikiHistory {
+ my $self = shift;
+ my $action = shift;
+ my $userId = shift || $self->session->user->userId;
+ $self->session->db->write("INSERT INTO WikiPage_extraHistory (assetId, userId, dateStamp, actionTaken) VALUES (?, ?, ?, ?)", [$self->getId, $userId, $self->session->datetime->time, $action]);
+}
+
+sub www_protect {
+ my $self = shift;
+ return $self->session->privilege->insufficient unless $self->canProtect;
+ return $self->www_view if $self->isProtected;
+
+ $self->session->db->write("DELETE FROM WikiPage_protected WHERE assetId = ?", [$self->getId]);
+ $self->session->db->write("INSERT INTO WikiPage_protected (assetId) VALUES (?)", [$self->getId]);
+ $self->{_isProtected} = 1;
+ $self->updateWikiHistory('protected');
+ return $self->www_view;
+}
+
+sub www_unprotect {
+ my $self = shift;
+ return $self->session->privilege->insufficient unless $self->canProtect;
+ return $self->www_view if !$self->isProtected;
+
+ $self->session->db->write("DELETE FROM WikiPage_protected WHERE assetId = ?", [$self->getId]);
+ $self->{_isProtected} = 0;
+ $self->updateWikiHistory('unprotected');
+ return $self->www_view;
+}
+
+1;
diff --git a/lib/WebGUI/Asset/Wobject/WikiMaster.pm b/lib/WebGUI/Asset/Wobject/WikiMaster.pm
new file mode 100644
index 000000000..2ff108fa8
--- /dev/null
+++ b/lib/WebGUI/Asset/Wobject/WikiMaster.pm
@@ -0,0 +1,389 @@
+package WebGUI::Asset::Wobject::WikiMaster;
+
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2006 Plain Black Corporation.
+#-------------------------------------------------------------------
+# Please read the legal notices (docs/legal.txt) and the license
+# (docs/license.txt) that came with this distribution before using
+# this software.
+#-------------------------------------------------------------------
+# http://www.plainblack.com info@plainblack.com
+#-------------------------------------------------------------------
+
+use strict;
+use Tie::IxHash;
+use WebGUI::International;
+use WebGUI::Utility;
+use HTML::Parser;
+use base 'WebGUI::Asset::Wobject';
+
+#-------------------------------------------------------------------
+sub definition {
+ my $class = shift;
+ my $session = shift;
+ my $definition = shift;
+ my $i18n = WebGUI::International->new($session, 'Asset_WikiMaster');
+
+ # BUGGO: duplication with Collaboration; move this into WebGUI::Asset::RichEdit
+ my $richEditorOptions = $session->db->buildHashRef("select distinct(assetData.assetId), assetData.title from asset, assetData where asset.className='WebGUI::Asset::RichEdit' and asset.assetId=assetData.assetId order by assetData.title");
+
+ my %properties;
+ tie %properties, 'Tie::IxHash';
+ %properties =
+ (
+ groupToEditPages => { fieldType => 'group',
+ defaultValue => ['7'],
+ tab => 'security',
+ hoverHelp => $i18n->get('groupToEditPages hoverHelp'),
+ label => $i18n->get('groupToEditPages label') },
+
+ groupToAdminister => { fieldType => 'group',
+ defaultValue => ['3'],
+ tab => 'security',
+ hoverHelp => $i18n->get('groupToAdminister hoverHelp'),
+ label => $i18n->get('groupToAdminister label') },
+
+ richEditor => { fieldType => 'selectBox',
+ options => $richEditorOptions,
+ defaultValue => 'PBrichedit000000000001',
+ tab => 'display',
+ hoverHelp => $i18n->get('richEditor hoverHelp'),
+ label => $i18n->get('richEditor label') },
+
+ # BUGGO: how do we get this to only be able to specify pages underneath
+ # the asset? There's no lineage option for this field type.
+ defaultPage => { fieldType => 'asset',
+ className => 'WebGUI::Asset::WikiPage',
+ tab => 'display',
+ hoverHelp => $i18n->get('defaultPage hoverHelp'),
+ label => $i18n->get('defaultPage label') },
+
+ masterTemplateId => { fieldType => 'template',
+ namespace => 'WikiMaster',
+ defaultValue => 'WikiMasterTmpl00000001',
+ tab => 'display',
+ hoverHelp => $i18n->get('masterTemplateId hoverHelp'),
+ label => $i18n->get('masterTemplateId label') },
+
+ pageTemplateId => { fieldType => 'template',
+ namespace => 'WikiPage',
+ defaultValue => 'WikiPageTmpl0000000001',
+ tab => 'display',
+ hoverHelp => $i18n->get('pageTemplateId hoverHelp'),
+ label => $i18n->get('pageTemplateId label') },
+
+ pageHistoryTemplateId => { fieldType => 'template',
+ namespace => 'WikiPage_pageHistory',
+ defaultValue => 'WikiPHTmpl000000000001',
+ tab => 'display',
+ hoverHelp => $i18n->get('pageHistoryTemplateId hoverHelp'),
+ label => $i18n->get('pageHistoryTemplateId label') },
+
+ recentChangesTemplateId => { fieldType => 'template',
+ namespace => 'WikiMaster_recentChanges',
+ defaultValue => 'WikiRCTmpl000000000001',
+ tab => 'display',
+ hoverHelp => $i18n->get('recentChangesTemplateId hoverHelp'),
+ label => $i18n->get('recentChangesTemplateId label') },
+
+ pageListTemplateId => { fieldType => 'template',
+ namespace => 'WikiMaster_pageList',
+ defaultValue => 'WikiPLTmpl000000000001',
+ tab => 'display',
+ hoverHelp => $i18n->get('pageListTemplateId hoverHelp'),
+ label => $i18n->get('pageListTemplateId label') },
+ );
+
+ push @$definition,
+ {
+ assetName => $i18n->get('assetName'),
+ icon => 'wikiMaster.gif',
+ autoGenerateForms => 1,
+ tableName => 'WikiMaster',
+ className => 'WebGUI::Asset::Wobject::WikiMaster',
+ properties => \%properties,
+ };
+
+ return $class->SUPER::definition($session, $definition);
+}
+
+#-------------------------------------------------------------------
+sub canEditPages {
+ my $self = shift;
+ my $userId = shift || $self->session->user->userId;
+ my $user = WebGUI::User->new($self->session, $userId);
+ return $self->canView($userId) && $user->isInGroup($self->get('groupToEditPages'));
+}
+
+sub canAdminister {
+ my $self = shift;
+ my $userId = shift || $self->session->user->userId;
+ my $user = WebGUI::User->new($self->session, $userId);
+ return $self->canView($userId) && $user->isInGroup($self->get('groupToAdminister'));
+}
+
+#-------------------------------------------------------------------
+sub getDefaultPage {
+ my $self = shift;
+ return $self->{_defaultPage} if $self->{_defaultPage};
+
+ my $pageId = $self->get('defaultPage');
+ if (defined $pageId) {
+ my $page = WebGUI::Asset->newByDynamicClass($self->session, $pageId);
+ if (defined $page and $page->isa('WebGUI::Asset::WikiPage') and $page->getParent->getId eq $self->getId) {
+ $self->{_defaultPage} = $page;
+ return $page;
+ }
+ }
+
+ # No valid default page. Okay, we have to synthesize it.
+ my $page = WebGUI::Asset->newByPropertyHashRef
+ ($self->session, { className => 'WebGUI::Asset::WikiPage',
+ title => $self->get('title'), content => $self->get('description') });
+ $page->{_parent} = $self;
+ $page->{_nonexistent} = 1;
+ $self->{_defaultPage} = $page;
+ return $page;
+}
+
+sub prepareMasterTemplate {
+ my $self = shift;
+ return $self->{_masterTemplate} if $self->{_masterTemplate};
+ $self->{_masterTemplate} =
+ WebGUI::Asset::Template->new($self->session, $self->get('masterTemplateId'));
+ $self->{_masterTemplate}->prepare;
+ return $self->{_masterTemplate};
+}
+
+sub processMasterTemplate {
+ my $self = shift;
+ my $title = shift;
+ my $content = shift;
+ my $var = {};
+ my $template = $self->prepareMasterTemplate;
+
+ $var->{title} = $title;
+ $var->{content} = $content;
+ $var->{displayTitle} = $self->get('displayTitle');
+ $var->{canEdit} = $self->canEditPages;
+ $self->_addFuncTemplateVars($var, qw/addPage listPages recentChanges/);
+
+ return $self->processTemplate($var, undef, $template);
+}
+
+sub prepareView {
+ my $self = shift;
+ $self->SUPER::prepareView;
+ $self->prepareMasterTemplate;
+ $self->getDefaultPage->prepareView;
+}
+
+sub _addFuncTemplateVars {
+ my $self = shift;
+ my $var = shift;
+ my @funcs = @_;
+ my $i18n = WebGUI::International->new($self->session, 'Asset_WikiMaster');
+ my %specialFuncs =
+ (addPage => 'func=add;class=WebGUI::Asset::WikiPage');
+
+ foreach my $func (@funcs) {
+ $var->{$func.'.url'} = $self->getUrl($specialFuncs{$func} || "func=$func");
+ $var->{$func.'.text'} = $i18n->get("func $func link text");
+ }
+}
+
+sub view {
+ my $self = shift;
+ return $self->getDefaultPage->view;
+}
+
+sub getContentLastModified {
+ my $self = shift;
+ return $self->getDefaultPage->getContentLastModified;
+}
+
+#-------------------------------------------------------------------
+sub purge {
+ my $self = shift;
+ $self->session->db->write('DELETE FROM WikiMaster_titleIndex WHERE assetId = ?', [$self->getId]);
+ return $self->SUPER::purge;
+}
+
+#-------------------------------------------------------------------
+sub processPropertiesFromFormPost {
+ my $self = shift;
+
+ # BUGGO: Duplication with A::W::Collaboration::processPropertiesFromFormPost
+ my $groupsChanged =
+ (($self->session->form->process('groupIdView') ne $self->get('groupIdView'))
+ or ($self->session->form->process('groupIdEdit') ne $self->get('groupIdEdit')));
+ my $ret = $self->SUPER::processPropertiesFromFormPost(@_);
+
+ if ($groupsChanged) {
+ foreach my $child (@{$self->getLineage(['children'], {returnObjects => 1})}) {
+ $child->update({ groupIdView => $self->get('groupIdView'),
+ groupIdEdit => $self->get('groupIdEdit') });
+ }
+ }
+
+ return $ret;
+}
+
+#-------------------------------------------------------------------
+sub updateTitleIndex {
+ my $self = shift;
+ my @pages = @{+shift};
+ my %opts = @_;
+ return unless @pages;
+ $self->session->db->write("DELETE FROM WikiMaster_titleIndex WHERE assetId = ? AND pageId IN (".join(', ', ('?') x @pages).")", [$self->getId, map{$_->getId} @pages]);
+
+ foreach my $page (@pages) {
+ my ($pageId, $title) = ($page->getId, $page->get('title'));
+ $self->session->db->write("INSERT INTO WikiMaster_titleIndex (assetId, pageId, title) VALUES (?, ?, ?)", [$self->getId, $pageId, $title]);
+ }
+}
+
+sub autolinkHtml {
+ my $self = shift;
+ my $html = shift;
+
+ # TODO: caching? Maybe in WikiPage.
+ my %mapping = $self->session->db->buildHash("SELECT LOWER(i.title), d.url FROM WikiMaster_titleIndex AS i INNER JOIN assetData AS d ON i.pageId = d.assetId WHERE i.assetId = ?", [$self->getId]);
+ return $html unless %mapping;
+
+ foreach my $key (keys %mapping) {
+ $mapping{$key} = WebGUI::HTML::format('/'.$mapping{$key}, 'text');
+ }
+
+ my $matchString = join('|', map{quotemeta} keys %mapping);
+ my $regexp = qr/\b($matchString)\b/i;
+
+ my @acc = ();
+ my $in_a = 0;
+ my $p = HTML::Parser->new;
+ $p->case_sensitive(1);
+ $p->marked_sections(1);
+ $p->unbroken_text(1);
+ $p->handler(start => sub { push @acc, $_[2]; if ($_[0] eq 'a' and exists $_[1]{href}) { $in_a++ } },
+ 'tagname, attr, text');
+ $p->handler(end => sub { push @acc, $_[2]; if ($_[0] eq 'a' and exists $_[1]{href}) { $in_a-- } },
+ 'tagname, attr, text');
+ $p->handler(text => sub {
+ my $text = $_[0];
+ unless ($in_a) {
+ while ($text =~ s#^(.*?)$regexp##i) {
+ push @acc, sprintf '%s%s',
+ ($1, $mapping{lc $2}, $2);
+ }
+ }
+ push @acc, $text;
+ }, 'text');
+ $p->handler(default => sub { push @acc, $_[0] }, 'text');
+ $p->parse($html);
+ $p->eof;
+ undef $p; # Just in case there might be reference loops.
+
+ return join '', @acc;
+}
+
+#-------------------------------------------------------------------
+sub www_addPageSave {
+ my $self = shift;
+ my $pageClass = $self->session->form->process('class');
+ return $self->session->privilege->insufficient unless
+ $self->canEditPages and UNIVERSAL::isa($pageClass, 'WebGUI::Asset::WikiPage');
+
+ # Refactor: duplication with A::W::Matrix::www_editListingSave
+ my $oldTag = WebGUI::VersionTag->getWorking($self->session, 1);
+ my $newTag = WebGUI::VersionTag->create
+ ($self->session, { name => (sprintf "%s create of %s - %s",
+ $self->get('title'), $self->session->form->process('title'),
+ $self->session->user->username),
+ workflowId => 'pbworkflow000000000003' });
+ $newTag->setWorking;
+
+ # Hrm. Duplication with Asset::www_editSave. How to fix that?
+ my $page = $self->addChild({ className => $pageClass });
+ $page->{_parent} = $self;
+
+ my $error = $page->processPropertiesFromFormPost;
+ if (ref $error eq 'ARRAY') {
+ $self->session->stow->set('editFormErrors', $error);
+ $page->purge;
+ return $self->www_add;
+ }
+
+ $page->updateHistory('edited');
+ $newTag->requestCommit;
+ $newTag->clearWorking;
+ $oldTag->setWorking if defined $oldTag;
+ return $page->www_view;
+}
+
+#-------------------------------------------------------------------
+sub www_listPages {
+ # TODO: template, i18n
+ my $self = shift;
+ my $i18n = WebGUI::International->new($self->session, 'Asset_WikiMaster');
+ my $title = $i18n->get('listPages title');
+ my @pages = @{$self->getLineage(['children'], {returnObjects => 1})};
+ my $var = {};
+ my $template = WebGUI::Asset::Template->new($self->session, $self->get('pageListTemplateId'));
+ $template->prepare;
+
+ $var->{'pl.entries'} = [map {
+ my $page = $_;
+ my $subvar = {};
+ $subvar->{pageUrl} = WebGUI::HTML::format($page->getUrl, 'text');
+ $subvar->{pageTitle} = WebGUI::HTML::format($page->get('title'), 'text');
+ $subvar;
+ } @pages];
+
+ return $self->processStyle($self->processMasterTemplate($title, $self->processTemplate($var, undef, $template)));
+}
+
+#-------------------------------------------------------------------
+sub www_recentChanges {
+ my $self = shift;
+ my $ago = WebGUI::International->new($self->session, 'Asset')->get('ago');
+ my $dt = $self->session->datetime;
+
+ # Buggo: hardcoded number of recent changes
+ # TODO: query should have both time limit and number limit, settable by form elements...
+ my @changes = @{$self->session->db->buildArrayRefOfHashRefs("SELECT h.userId AS userId, u.username AS username, h.dateStamp AS dateStamp, h.actionTaken AS action, d.url AS url, d.title AS title FROM assetHistory AS h LEFT JOIN users AS u ON h.userId = u.userId INNER JOIN asset AS a ON h.assetId = a.assetId INNER JOIN assetData AS d ON a.assetId = d.assetId AND h.dateStamp = d.revisionDate WHERE a.lineage LIKE CONCAT(?, '%') AND a.assetId <> ? AND h.actionTaken IN ('edited', 'trashed') ORDER BY dateStamp DESC LIMIT 0,50", [$self->get('lineage'), $self->getId])};
+
+ # Buggo, duplication with WikiPage::www_pageHistory?
+ my $time = $dt->time;
+ my @days = ();
+ foreach my $entry (@changes) {
+ $entry->{date} = $dt->epochToHuman($entry->{dateStamp}, '%z');
+ $entry->{time} = $dt->epochToHuman($entry->{dateStamp}, '%Z');
+ $entry->{dateInterval} = sprintf '%s %s %s',
+ ($dt->secondsToInterval($time - $entry->{dateStamp}), $ago);
+ $entry->{isDelete} = ($entry->{action} eq 'trashed');
+ $entry->{isEdit} = ($entry->{action} eq 'edited');
+ $entry->{viewUrl} = $entry->{url};
+
+ # TODO: actionC, and also lowercased version, and change WikiPage to comply
+ # with that also
+
+ if (!@days || $entry->{date} ne $days[-1][0]{date}) {
+ push @days, [$entry];
+ } else {
+ push @{$days[-1]}, $entry;
+ }
+ }
+
+ my $template = WebGUI::Asset::Template->new($self->session, $self->get('recentChangesTemplateId'));
+ $template->prepare;
+
+ my $var = {};
+ $var->{'rc.entries'} = \@changes;
+ $var->{'rc.days'} = [map{{'day.date' => $$_[0]{date}, 'day.entries' => $_}} @days];
+
+ my $title = "Recent changes";
+
+ return $self->processStyle($self->processMasterTemplate($title, $self->processTemplate($var, undef, $template)));
+}
+
+1;
diff --git a/lib/WebGUI/i18n/English/Asset_WikiMaster.pm b/lib/WebGUI/i18n/English/Asset_WikiMaster.pm
new file mode 100644
index 000000000..d4623c16b
--- /dev/null
+++ b/lib/WebGUI/i18n/English/Asset_WikiMaster.pm
@@ -0,0 +1,58 @@
+package WebGUI::i18n::English::Asset_WikiMaster;
+
+our $I18N =
+{
+ 'assetName' =>
+ { lastUpdated => 1160157064, message => 'Wiki' },
+
+ 'groupToEditPages hoverHelp' =>
+ { lastUpdated => 1160157064, message => q|Choose a group of users who will be able to edit pages in this wiki instance. They will not, by default, be able to delete pages or revisions, or edit protected pages.| },
+ 'groupToEditPages label' =>
+ { lastUpdated => 1160157064, message => q|Who can edit pages?| },
+
+ 'groupToAdminister hoverHelp' =>
+ { lastUpdated => 1160157064, message => q|Choose a group of users who will be able to perform administrative actions on pages in this wiki instance; such actions include deletion of pages and page revisions, and protecting and unprotecting of pages.| },
+ 'groupToAdminister label' =>
+ { lastUpdated => 1160157064, message => q|Who can administer?| },
+
+ 'richEditor hoverHelp' =>
+ { lastUpdated => 1160157064, message => q|Which rich editor to use for editing pages in this wiki instance.| },
+ 'richEditor label' =>
+ { lastUpdated => 1160157064, message => q|Rich Editor| },
+
+ 'defaultPage hoverHelp' =>
+ { lastUpdated => 1160157064, message => q|Which page to display by default when someone browses to the wiki's main URL.| },
+ 'defaultPage label' =>
+ { lastUpdated => 1160157064, message => q|Default Page| },
+
+ 'pageTemplateId hoverHelp' =>
+ { lastUpdated => 1160157064, message => q|Which template to use to display pages.| },
+ 'pageTemplateId label' =>
+ { lastUpdated => 1160157064, message => q|Page Template| },
+
+ 'recentChangesTemplateId hoverHelp' =>
+ { lastUpdated => 1160157064, message => q|Which template to use for the recent changes display.| },
+ 'recentChangesTemplateId label' =>
+ { lastUpdated => 1160157064, message => q|Recent Changes Template| },
+
+ 'pageHistoryTemplateId hoverHelp' =>
+ { lastUpdated => 1160505291, message => q|Which template to use for the page history display.| },
+ 'pageHistoryTemplateId label' =>
+ { lastUpdated => 1160505291, message => q|Page History Template| },
+
+ 'pageListTemplateId hoverHelp' =>
+ { lastUpdated => 1160417517, message => q|Which template to use for displaying lists of pages.| },
+ 'pageListTemplateId label' =>
+ { lastUpdated => 1160417517, message => q|Page List Template| },
+
+ 'func addPage link text' =>
+ { lastUpdated => 1160157064, message => q|Add a new page| },
+ 'func listPages link text' =>
+ { lastUpdated => 1160417517, message => q|List all pages| },
+ 'func recentChanges link text' =>
+ { lastUpdated => 1160768887, message => q|Show recent changes| },
+ 'listPages title' =>
+ { lastUpdated => 1160417517, message => q|List of pages| },
+};
+
+1;
diff --git a/lib/WebGUI/i18n/English/Asset_WikiPage.pm b/lib/WebGUI/i18n/English/Asset_WikiPage.pm
new file mode 100644
index 000000000..cbdf5cd38
--- /dev/null
+++ b/lib/WebGUI/i18n/English/Asset_WikiPage.pm
@@ -0,0 +1,31 @@
+package WebGUI::i18n::English::Asset_WikiPage;
+
+our $I18N =
+{
+ 'assetName' =>
+ { lastUpdated => 1160157064, message => 'Wiki Page' },
+
+ 'func edit link text' =>
+ { lastUpdated => 1160157064, message => q|Edit this page| },
+ 'func view link text' =>
+ { lastUpdated => 1160157064, message => q|View this page| },
+ 'func pageHistory link text' =>
+ { lastUpdated => 1160425002, message => q|View this page's history| },
+ 'func protect link text' =>
+ { lastUpdated => 1160425002, message => q|Protect this page| },
+ 'func unprotect link text' =>
+ { lastUpdated => 1160425002, message => q|Unprotect this page| },
+
+ 'actionN edited' =>
+ { lastUpdated => 1160505291, message => q|Edited| },
+ 'actionN trashed' =>
+ { lastUpdated => 1160505291, message => q|Deleted| },
+ 'actionN protected' =>
+ { lastUpdated => 1160505291, message => q|Protected| },
+ 'actionN unprotected' =>
+ { lastUpdated => 1160505291, message => q|Unprotected| },
+ 'actionN created' =>
+ { lastUpdated => 1160505291, message => q|Created| },
+};
+
+1;