package WebGUI::Asset::WikiPage;

# -------------------------------------------------------------------
#  WebGUI is Copyright 2001-2009 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 Class::C3;
use base qw(
    WebGUI::AssetAspect::Subscribable
    WebGUI::AssetAspect::Comments 
    WebGUI::AssetAspect::AutoSynopsis
    WebGUI::Asset
);
use Tie::IxHash;
use WebGUI::International;
use WebGUI::Utility;
use WebGUI::VersionTag;


#-------------------------------------------------------------------

=head2 addChild ( )

You can't add children to a wiki page.

=cut

sub addChild {
	return undef;
}

#-------------------------------------------------------------------

=head2 addRevision ( )

Override the default method in order to deal with attachments.

=cut

sub addRevision {
        my $self = shift;
        my $newSelf = $self->next::method(@_);
	my $now = time();
	$newSelf->update({
		isHidden => 1,
		});
        return $newSelf;
}

#-------------------------------------------------------------------

=head2 canAdd ($session)

This functions as a class or an object method.  It sets the subclassGroupId to 7
instead of the default of 12.

=cut

sub canAdd {
    my $class = shift;
    my $session = shift;
    return $class->next::method($session, undef, '7');
}

#-------------------------------------------------------------------

=head2 canEdit 

Returns true if the current user can administer the wiki containing this WikiPage, or
if the current user can edit wiki pages and is trying to add or edit pages, or the page
is not protected.

=cut

sub canEdit {
	my $self = shift;
    my $wiki = $self->getWiki;
    return undef unless defined $wiki;

	my $form      = $self->session->form;
    my $addNew    = $form->process("func"              ) eq "add";
    my $editSave  = $form->process("assetId"           ) eq "new"
                 && $form->process("func"              ) eq "editSave"
                 && $form->process("class","className" ) eq "WebGUI::Asset::WikiPage";
    return $wiki->canAdminister
        || ( $wiki->canEditPages && ( $addNew || $editSave || !$self->isProtected) );
}

#-------------------------------------------------------------------
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 =
	    (
	     content => { fieldType => "HTMLArea",
			  defaultValue => undef },
		views => {
			fieldType => "integer",
			defaultValue => 0,
			noFormPost => 1
			},
		isProtected => {
			fieldType => "yesNo",
			defaultValue => 0,
			noFormPost => 1
			},
		actionTaken => {
			fieldType => "text",
			defaultValue => '',
			noFormPost => 1,
			},
		actionTakenBy => {
			fieldType => "user",
			defaultValue => '',
			noFormPost => 1,
			},
                isFeatured => {
                    fieldType       => "yesNo",
                    defaultValue    => 0,
                    noFormPost      => 1,
                },
	    );

	push @$definition,
	     {
	      assetName => $i18n->get('assetName'),
	      icon => 'wikiPage.gif',
	      autoGenerateForms => 1,
	      tableName => 'WikiPage',
	      className => 'WebGUI::Asset::WikiPage',
	      properties => \%properties,
	     };

	return $class->next::method($session, $definition);
}


#-------------------------------------------------------------------

=head2 getAutoCommitWorkflowId 

Overrides the master class to handle spam prevention.  If the content matches any of
the spamStopWords, then the commit is canceled and the content is rolled back to the
previous version.  Otherwise, it returns the autoCommitWorkflowId for the regular asset
commit flow to handle.

=cut

sub getAutoCommitWorkflowId {
	my $self = shift;
    my $wiki = $self->getWiki;
    if ($wiki->hasBeenCommitted) {

        # delete spam
        my $spamStopWords = $self->session->config->get('spamStopWords');
        if (ref $spamStopWords eq 'ARRAY' && @{ $spamStopWords }) {
            my $spamRegex = join('|',@{$spamStopWords});
            $spamRegex =~ s/\s/\\ /g;
            if ($self->get('content') =~ m{$spamRegex}xmsi) {
                my $tag = WebGUI::VersionTag->new($self->session, $self->get('tagId'));
                $self->purgeRevision;
                if ($tag->getAssetCount == 0) {
                    $tag->rollback;
                }
                return undef;
            }
        }

        return $wiki->get('approvalWorkflow')
            || $self->session->setting->get('defaultVersionTagWorkflow');
    }
    return undef;
}


#-------------------------------------------------------------------

=head2 getEditForm 

Renders a templated edit form for adding or editing a wiki page.

=cut

sub getEditForm {
	my $self = shift;
	my $session = $self->session;
	my $form = $session->form;
	my $i18n = WebGUI::International->new($session, "Asset_WikiPage");
	my $newPage = 0;
	my $wiki = $self->getWiki;
	my $url = ($self->getId eq "new") ? $wiki->getUrl : $self->getUrl;
	my $var = {
		title=> $i18n->get("editing")." ".(defined($self->get('title'))? $self->get('title') : $i18n->get("assetName")),
		formHeader => WebGUI::Form::formHeader($session, { action => $url}) 
			.WebGUI::Form::hidden($session, { name => 'func', value => 'editSave' }) 
			.WebGUI::Form::hidden($session, { name=>"proceed", value=>"showConfirmation" }),
	 	formTitle => WebGUI::Form::text($session, { name => 'title', maxlength => 255, size => 40, 
                value => $self->get('title'), defaultValue=>$form->get("title","text") }),
		formContent => WebGUI::Form::HTMLArea($session, { name => 'content', richEditId => $wiki->get('richEditor'), value => $self->get('content') }) ,
		formSubmit => WebGUI::Form::submit($session, { value => 'Save' }),
		formProtect => WebGUI::Form::yesNo($session, { name => "isProtected", value=>$self->getValue("isProtected")}),
                formFeatured => WebGUI::Form::yesNo( $session, { name => 'isFeatured', value=>$self->getValue('isFeatured')}),
        formKeywords => WebGUI::Form::keywords($session, {
            name    => "keywords",
            value   => WebGUI::Keyword->new($session)->getKeywordsForAsset({asset=>$self}),
            }),
		allowsAttachments => $wiki->get("allowAttachments"),
		formFooter => WebGUI::Form::formFooter($session),
		isNew => ($self->getId eq "new"),
		canAdminister => $wiki->canAdminister,
		deleteConfirm => $i18n->get("delete page confirmation"),
		deleteLabel => $i18n->get("deleteLabel"),
		deleteUrl => $self->getUrl("func=delete"),
		titleLabel => $i18n->get("titleLabel"),
		contentLabel => $i18n->get("contentLabel"),
		attachmentLabel => $i18n->get("attachmentLabel"),
		protectQuestionLabel => $i18n->get("protectQuestionLabel"),
		isProtected => $self->isProtected
		};
    my $children = [];
	if ($self->getId eq "new") {
		$var->{formHeader} .= WebGUI::Form::hidden($session, { name=>"assetId", value=>"new" }) 
			.WebGUI::Form::hidden($session, { name=>"class", value=>$form->process("class","className") });
	} else {
        $children = $self->getLineage(["children"]);
    }
    $var->{formAttachment} = WebGUI::Form::Attachments($session, { 
        value           => $children,
        maxAttachments  => $wiki->get("allowAttachments"),
        maxImageSize    => $wiki->get("maxImageSize"),
        thumbnailSize   => $wiki->get("thumbnailSize"),
        });
	return $self->processTemplate($var, $wiki->getValue('pageEditTemplateId'));
}

#-------------------------------------------------------------------

=head2 getSubscriptionTemplate ( )

=cut

sub getSubscriptionTemplate { 
    my ( $self ) = @_;
    return $self->getParent->getSubscriptionTemplate;
}

#-------------------------------------------------------------------

=head2 getTemplateVars ( )

Get the common template vars for this asset

=cut

sub getTemplateVars {
    my ( $self ) = @_;
    my $session  = $self->session;
    my $i18n     = WebGUI::International->new($session, "Asset_WikiPage");
    my $wiki     = $self->getWiki;
    my $owner    = WebGUI::User->new( $session, $self->get('ownerUserId') );
    my $keyObj   = WebGUI::Keyword->new($session);

    my $keywords    = $keyObj->getKeywordsForAsset({
        asset       => $self,
        asArrayRef  => 1,
    });

    my @keywordsLoop = ();
    foreach my $word (@{$keywords}) {
        push @keywordsLoop, {
            keyword => $word,
            url     => $wiki->getUrl("func=byKeyword;keyword=".$word),
        };
    }
    my $var = {
        %{ $self->get },
        url                 => $self->getUrl,
        keywordsLoop        => \@keywordsLoop,
        viewLabel           => $i18n->get("viewLabel"),
        editLabel           => $i18n->get("editLabel"),
        historyLabel        => $i18n->get("historyLabel"),
        wikiHomeLabel       => $i18n->get("wikiHomeLabel", "Asset_WikiMaster"),
        searchLabel         => $i18n->get("searchLabel", "Asset_WikiMaster"),	
        searchUrl           => $wiki->getUrl("func=search"),
        recentChangesUrl    => $wiki->getUrl("func=recentChanges"),
        recentChangesLabel  => $i18n->get("recentChangesLabel", "Asset_WikiMaster"),
        mostPopularUrl      => $wiki->getUrl("func=mostPopular"),
        mostPopularLabel    => $i18n->get("mostPopularLabel", "Asset_WikiMaster"),
        wikiHomeUrl         => $wiki->getUrl,
        historyUrl          => $self->getUrl("func=getHistory"),
        editContent         => $self->getEditForm,
        allowsAttachments   => $wiki->get("allowAttachments"),
        comments	        => $self->getFormattedComments(),
        canEdit             => $self->canEdit,
        canAdminister       => $wiki->canAdminister,
		isProtected         => $self->isProtected,
        content             => $wiki->autolinkHtml(
            $self->scrubContent,
            {skipTitles => [$self->get('title')]},
        ),	
        isSubscribed        => $self->isSubscribed,
        subscribeUrl        => $self->getSubscribeUrl,
        unsubscribeUrl      => $self->getUnsubscribeUrl,
        owner               => $owner->get('alias'),
    };
    return $var;
}

#-------------------------------------------------------------------

=head2 getWiki 

Returns an object referring to the wiki that contains this page.  If it is not a WikiMaster,
or the parent is undefined, it returns undef.

=cut

sub getWiki {
	my $self = shift;
	my $parent = $self->getParent;
	return undef unless defined $parent and $parent->isa('WebGUI::Asset::Wobject::WikiMaster');
	return $parent;
}

#-------------------------------------------------------------------

=head2 indexContent 

Extends the master class to handle indexing the wiki content.

=cut

sub indexContent {
	my $self = shift;
	my $indexer = $self->next::method;
	$indexer->addKeywords($self->get('content'));
	return $indexer;
}

#-------------------------------------------------------------------

=head2 isProtected 

Returns a boolean indicating whether or not this WikiPage is protected.

=cut

sub isProtected {
	my $self = shift;
	return $self->get("isProtected");
}

#-------------------------------------------------------------------

=head2 preparePageTemplate 

This is essentially prepareView, but is smart and will only do the template
preparation once.  Returns the preparted page template.

=cut

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};
}

#-------------------------------------------------------------------

=head2 prepareView 

Extends the master class to handle preparing the main view template for the page.

=cut

sub prepareView {
	my $self = shift;
	$self->next::method;
	$self->preparePageTemplate;
}


#-------------------------------------------------------------------

=head2 processPropertiesFromFormPost 

Extends the master method to handle properties and attachments.

=cut

sub processPropertiesFromFormPost {
    my $self    = shift;
    my $session = $self->session;
    $self->next::method(@_);
    my $actionTaken = ($session->form->process("assetId") eq "new") ? "Created" : "Edited";
    my $wiki = $self->getWiki;
    my $properties = {
        groupIdView 	=> $wiki->get('groupIdView'),
        groupIdEdit 	=> $wiki->get('groupToAdminister'),
        actionTakenBy 	=> $self->session->user->userId,
        actionTaken 	=> $actionTaken,
    };

    if ($wiki->canAdminister) {
        $properties->{isProtected} = $session->form->get("isProtected");
        $properties->{isFeatured}  = $session->form->get("isFeatured");
    }

    ($properties->{synopsis}) = $self->getSynopsisAndContent(undef, $self->get('content'));

	$self->update($properties);

    # deal with attachments from the attachments form control
    my $options = {
        maxImageSize    => $wiki->get('maxImageSize'),
        thumbnailSize   => $wiki->get('thumbnailSize'),
    };
    my @attachments = $session->form->param("attachments");
    my @tags = ();
    foreach my $assetId (@attachments) {
        my $asset = WebGUI::Asset->newByDynamicClass($session, $assetId);
        if (defined $asset) {
            unless ($asset->get("parentId") eq $self->getId) {
                $asset->setParent($self);
                $asset->update({
                    ownerUserId => $self->get( "ownerUserId"      ),
                    groupIdEdit => $wiki->get( "groupToEditPages" ),
                    groupIdView => $self->get( "groupIdView"      ),
                    });
            }
            $asset->applyConstraints($options);
            push(@tags, $asset->get("tagId"));
            $asset->setVersionTag($self->get("tagId"));
        }
    }

    # clean up empty tags
    foreach my $tag (@tags) {
        my $version = WebGUI::VersionTag->new($self->session, $tag);
        if (defined $version) {
            if ($version->getAssetCount == 0) {
                $version->rollback;
            }
        }
    }
}

#-------------------------------------------------------------------

=head2 scrubContent ( [ content ] )

Uses WikiMaster settings to remove unwanted markup and apply site wide replacements.

=head3 content

Optionally pass the ontent that we want to run the filters on.  Otherwise we get it from self.

=cut

sub scrubContent {
        my $self = shift;
        my $content = shift || $self->get("content");

        $content =~ s/\^-\;//g;
        my $scrubbedContent = WebGUI::HTML::filter($content, $self->getWiki->get("filterCode"));

        if ($self->getWiki->get("useContentFilter")) {
                $scrubbedContent = WebGUI::HTML::processReplacements($self->session, $scrubbedContent);
        }

        return $scrubbedContent;
}

#-------------------------------------------------------------------

=head2 update

Wrap update to force isHidden to be on, all the time.

=cut

sub update {
    my $self = shift;
    my $properties = shift;
	$properties->{isHidden} = 1;
    return $self->next::method($properties);
}

#-------------------------------------------------------------------

=head2 validParent

Make sure that the current session asset is a WikiMaster for pasting and adding checks.

This is a class method.

=cut

sub validParent {
    my $class   = shift;
    my $session = shift;
    return $session->asset->isa('WebGUI::Asset::Wobject::WikiMaster');
}

#-------------------------------------------------------------------

=head2 view 

Renders this asset.

=cut

sub view {
	my $self = shift;
	return $self->processTemplate($self->getTemplateVars, $self->getWiki->get("pageTemplateId"));
}

#-------------------------------------------------------------------

=head2 www_delete 

Overrides the master method so that privileges are checked on the parent wiki instead
of the page.  Returns the user to viewing the wiki.

=cut

sub www_delete {
	my $self = shift;
	return $self->session->privilege->insufficient unless $self->getWiki->canAdminister;
	$self->trash;
	$self->session->asset($self->getParent);
	return $self->getParent->www_view;
}

#-------------------------------------------------------------------

=head2 www_edit 

Overrides the master class to render the edit form in the parent wiki's style.

=cut

sub www_edit {
	my $self = shift;
	return $self->session->privilege->insufficient unless $self->canEdit;
	return $self->session->privilege->locked unless $self->canEditIfLocked;
	return $self->getWiki->processStyle($self->getEditForm);
}

#-------------------------------------------------------------------

=head2 www_getHistory 

Returns the version history of this wiki page.  The output is templated.

=cut

sub www_getHistory {
	my $self = shift;
	return $self->session->privilege->insufficient unless $self->canEdit;
	my $var = {};
	my ($icon, $date) = $self->session->quick(qw(icon datetime));
	my $i18n = WebGUI::International->new($self->session, 'Asset_WikiPage');
	foreach my $revision (@{$self->getRevisions}) {
		my $user = WebGUI::User->new($self->session, $revision->get("actionTakenBy"));
		push(@{$var->{pageHistoryEntries}}, {
			toolbar => $icon->delete("func=purgeRevision;revisionDate=".$revision->get("revisionDate"), $revision->get("url"), $i18n->get("delete confirmation"))
                        	.$icon->edit('func=edit;revision='.$revision->get("revisionDate"), $revision->get("url"))
                        	.$icon->view('func=view;revision='.$revision->get("revisionDate"), $revision->get("url")),
			date => $date->epochToHuman($revision->get("revisionDate")),
			username => $user->profileField('alias') || $user->username,
			actionTaken => $revision->get("actionTaken"),
			interval => join(" ", $date->secondsToInterval(time() - $revision->get("revisionDate")))
			});		
	}
	return $self->processTemplate($var, $self->getWiki->get('pageHistoryTemplateId'));
}

#-------------------------------------------------------------------

=head2 www_purgeRevision

Override the main method to change which group is allowed to purge revisions for WikiPages.  Only
members who can administer the parent wiki (canAdminister) can purge revisions.

=cut

sub www_purgeRevision {
	my $self    = shift;
	my $session = $self->session;
	return $session->privilege->insufficient() unless $self->getWiki->canAdminister;
	my $revisionDate = $session->form->process("revisionDate");
	return undef unless $revisionDate;
	my $asset = WebGUI::Asset->new($session, $self->getId, $self->get("className"), $revisionDate);
	return undef if ($asset->get('revisionDate') != $revisionDate);
	my $parent = $asset->getParent;
	$asset->purgeRevision;
	if ($session->form->process("proceed") eq "manageRevisionsInTag") {
		my $working = (defined $self) ? $self : $parent;
		$session->http->setRedirect($working->getUrl("op=manageRevisionsInTag"));
		return undef;
	}
	unless (defined $self) {
		return $parent->www_view;
	}
	return $self->www_manageRevisions;
}

#-------------------------------------------------------------------

=head2 www_restoreWikiPage 

Publishes a wiki page that has been put into the trash or the clipboard.

=cut

sub www_restoreWikiPage {
	my $self = shift;
	return $self->session->privilege->insufficient unless $self->getWiki->canAdminister;
	$self->publish;	
	return $self->www_view;
}


#-------------------------------------------------------------------

=head2 www_showConfirmation ( )

Shows a confirmation message letting the user know their page has been submitted.

=cut

sub www_showConfirmation {
	my $self = shift;
	my $i18n = WebGUI::International->new($self->session, "Asset_WikiPage");
	return $self->getWiki->processStyle('<p>'.$i18n->get("page received").'</p><p><a href="'.$self->getWiki->getUrl.'">'.$i18n->get("493","WebGUI").'</a></p>');
}

#-------------------------------------------------------------------

=head2 www_view 

Override the master method to count the number of times this page has been viewed,
and to render it with the parent's style.

=cut

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->isVisitor);
	$self->session->http->sendHeader;
	$self->prepareView;
	return $self->getWiki->processStyle($self->view);
}



1;
