# Module of TWiki Enterprise Collaboration Platform, http://TWiki.org/
#
# Copyright (C) 1999-2006 Peter Thoeny, peter@thoeny.org
# and TWiki Contributors. All Rights Reserved. TWiki Contributors
# are listed in the AUTHORS file in the root of this distribution.
# NOTE: Please extend that file, not this notice.
#
# Additional copyrights apply to some or all of the code in this
# file as follows:
# Based on parts of Ward Cunninghams original Wiki and JosWiki.
# Copyright (C) 1998 Markus Peter - SPiN GmbH (warpi@spin.de)
# Some changes by Dave Harris (drh@bhresearch.co.uk) incorporated
#
# 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. For
# more details read LICENSE in the root of this distribution.
#
# 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.
#
# As per the GPL, removal of this notice is prohibited.

=begin twiki

---+ package TWiki::UI::Edit
Edit command handler

=cut
package TWiki::UI::Edit;

use strict;
use Assert;
use TWiki;
use TWiki::Form;
use TWiki::Plugins;
use TWiki::Prefs;
use TWiki::Store;
use TWiki::UI;
use Error qw( :try );
use TWiki::OopsException;
use CGI qw( -any );

=pod

---++ StaticMethod edit( $session )

Edit command handler.
This method is designed to be
invoked via the =TWiki::UI::run= method.
Most parameters are in the CGI query:

| =cmd= | Undocumented save command, passed on to save script |
| =onlywikiname= | if defined, requires a wiki name for the topic name if this is a new topic |
| =onlynewtopic= | if defined, and the topic exists, then moans |
| =formtemplate= | name of the form for the topic; will replace existing form |
| =templatetopic= | name of the topic to copy if creating a new topic |
| =skin= | skin(s) to use |
| =topicparent= | what to put in the topic prent meta data |
| =text= | text that will replace the old topic text if a formtemplate is defined (what the heck is this for?) |
| =contenttype= | optional parameter that defines the application type to write into the CGI header. Defaults to text/html. |
| =action= | Optional. If supplied, use the edit${action} template instead of the standard edit template. An empty value means edit both form and text, "form" means edit form only, "text" means edit text only |


=cut

sub edit {
    my $session = shift;
    my ( $text, $tmpl ) = init_edit( $session, 'edit' );
    finalize_edit ( $session, $text, $tmpl );
}


sub init_edit {
    my ( $session, $templateName )  = @_;

    my $query = $session->{cgiQuery};
    my $webName = $session->{webName};
    my $topic = $session->{topicName};
    my $user = $session->{user};

    # empty means edit both form and text, "form" means edit form only,
    # "text" means edit text only
    my $editaction = lc($query->param( 'action' )) || "";

    my $saveCmd = $query->param( 'cmd' ) || '';
    my $onlyWikiName = TWiki::isTrue( $query->param( 'onlywikiname' ));
    my $onlyNewTopic = TWiki::isTrue( $query->param( 'onlynewtopic' ));
    my $formTemplate  = $query->param( 'formtemplate' ) || '';
    my $templateTopic = $query->param( 'templatetopic' ) || '';
    # apptype is undocumented legacy
    my $cgiAppType = $query->param( 'contenttype' ) ||
      $query->param( 'apptype' ) || 'text/html';
    my $skin = $session->getSkin();
    my $theParent = $query->param( 'topicparent' ) || '';
    my $ptext = $query->param( 'text' );
    my $store = $session->{store};

    TWiki::UI::checkWebExists( $session, $webName, $topic, 'edit' );
    TWiki::UI::checkMirror( $session, $webName, $topic );

    my $tmpl = '';
    my $text = '';
    my $meta = '';
    my $extra = '';
    my $topicExists  = $store->topicExists( $webName, $topic );

    # If you want to edit, you have to be able to view and change.
    TWiki::UI::checkAccess( $session, $webName, $topic, 'view', $user );
    TWiki::UI::checkAccess( $session, $webName, $topic, 'change', $user );

    # Check lease, unless we have been instructed to ignore it
    # or if we are using the 10X's topic name for dynamic topic names
    my $breakLock = $query->param( 'breaklock' ) || '';
    unless( $breakLock || ($topic =~ /X{10}/ )) {
        my $lease = $store->getLease( $webName, $topic );
        if( $lease ) {
            my $who = $lease->{user}->webDotWikiName();

            if( $who ne $user->webDotWikiName() ) {
                # redirect; we are trying to break someone else's lease
                my( $future, $past );
                my $why = $lease->{message};
                my $def;
                my $t = time();
                if( $t > $lease->{expires} ) {
                    # The lease has expired, but see if we are still
                    # expected to issue a "less forceful' warning
                    if( $TWiki::cfg{LeaseLengthLessForceful} < 0 ||
                          $t < $lease->{expires} +
                            $TWiki::cfg{LeaseLengthLessForceful} ) {
                        $def = 'lease_old';
                        $past = TWiki::Time::formatDelta(
                            $t - $lease->{expires}, $session->{i18n} );
                        $future = '';
                    }
                }
                else {
                    # The lease is active
                    $def = 'lease_active';
                    $past = TWiki::Time::formatDelta(
                        $t - $lease->{taken}, $session->{i18n} );
                    $future = TWiki::Time::formatDelta(
                        $lease->{expires} - $t, $session->{i18n} );
                }
                if( $def ) {
                    # use a 'keep' redirect to ensure we pass parameter
                    # values in the query on to the oops script
                    throw TWiki::OopsException( 'leaseconflict',
                                                def => $def,
                                                web => $webName,
                                                topic => $topic,
                                                keep => 1,
                                                params =>
                                                  [ $who, $past, $future, 'edit' ] );
                }
            }
        }
    }

    # Prevent editing existing topic?
    if( $onlyNewTopic && $topicExists ) {
        # Topic exists and user requested oops if it exists
        throw TWiki::OopsException( 'attention',
                                    def => 'topic_exists',
                                    web => $webName,
                                    topic => $topic );
    }

    # prevent non-Wiki names?
    if( ( $onlyWikiName )
        && ( ! $topicExists )
        && ( ! TWiki::isValidTopicName( $topic ) ) ) {
         # do not allow non-wikinames
        throw TWiki::OopsException( 'attention',
                                    def => 'not_wikiword',
                                    web => $webName,
                                    topic => $topic,
                                    params => [ $topic ] );
    }

    if( $topicExists ) {
        ( $meta, $text ) =
          $store->readTopic( undef, $webName, $topic, undef );
    }

    if( $saveCmd && ! $session->{user}->isAdmin()) {
        throw TWiki::OopsException( 'accessdenied', def=>'only_group',
                                    web => $webName, topic => $topic,
                                    params => $TWiki::cfg{UsersWebName}.
                                    '.'.$TWiki::cfg{SuperAdminGroup} );
    }

    my $templateWeb = $webName;

    # Get edit template, standard or a different skin
    my $template = $query->param( 'template' ) ||
	$session->{prefs}->getPreferencesValue('EDIT_TEMPLATE') ||
        $templateName;
    $tmpl =
      $session->{templates}->readTemplate( $template.$editaction, $skin );

    if( !$tmpl && $template ne $templateName ) {
        $tmpl = $session->{templates}->readTemplate( $templateName, $skin );
    }

    if( !$tmpl ) {
        throw TWiki::OopsException( 'attention',
                                    def => 'no_such_template',
                                    web => $webName,
                                    topic => $topic,
                                    params => [ $template.$editaction,
                                                'EDIT_TEMPLATE' ] );
    }

    unless( $topicExists ) {
        if( $templateTopic ) {
            ( $templateWeb, $templateTopic ) =
              $session->normalizeWebTopicName( $templateWeb, $templateTopic );

            unless( $store->topicExists( $templateWeb, $templateTopic )) {
                throw TWiki::OopsException( 'accessdenied',
                                            def => 'no_such_topic',
                                            web => $templateWeb,
                                            topic => $templateTopic,
                                            params => [ 'templatetopic' ] );
            }

            ( $meta, $text ) =
              $store->readTopic( $session->{user}, $templateWeb,
                                        $templateTopic, undef );
        } else {
            ( $meta, $text ) = TWiki::UI::readTemplateTopic( $session, 'WebTopicEditTemplate' );
        }

        $extra = "(not exist)";

        # If present, instantiate form
        if( ! $formTemplate ) {
            my $form = $meta->get( 'FORM' );
            $formTemplate = $form->{name} if $form;
        }

        $text = $session->expandVariablesOnTopicCreation( $text, $user );
        $tmpl =~ s/%NEWTOPIC%/1/;
    } else {
        $tmpl =~ s/%NEWTOPIC%//;
    }
    $tmpl =~ s/%TEMPLATETOPIC%/$templateTopic/;

    # override with parameter if set
    $text = $ptext if defined $ptext;

    # Insert the rev number/date we are editing. This will be boolean false if
    # this is a new topic.
    if( $topicExists ) {
        my ( $orgDate, $orgAuth, $orgRev ) = $meta->getRevisionInfo();
        $tmpl =~ s/%ORIGINALREV%/${orgRev}_$orgDate/g;
    } else {
        $tmpl =~ s/%ORIGINALREV%/0/g;
    }

    # parent setting
    if( $theParent eq 'none' ) {
        $meta->remove( 'TOPICPARENT' );
    } elsif( $theParent ) {
        my $parentWeb;
        ($parentWeb, $theParent) =
          $session->normalizeWebTopicName( $webName, $theParent );
        if( $parentWeb ne $webName ) {
            $theParent = $parentWeb.'.'.$theParent;
        }
        $meta->put( 'TOPICPARENT', { name => $theParent } );
    }
    $tmpl =~ s/%TOPICPARENT%/$theParent/;

    if( $formTemplate ) {
        $meta->remove( 'FORM' );
        if( $formTemplate ne 'none' ) {
            $meta->put( 'FORM', { name => $formTemplate } );
        } else {
            $meta->remove( 'FORM' );
        }
        $tmpl =~ s/%FORMTEMPLATE%/$formTemplate/go;
    }

    if( $saveCmd ) {
        $text = $store->readTopicRaw( $session->{user}, $webName,
                                                 $topic, undef );
    }

    $session->{plugins}->beforeEditHandler(
        $text, $topic, $webName, $meta ) unless( $saveCmd );

    if( $TWiki::cfg{Log}{edit} ) {
        # write log entry
        $session->writeLog( 'edit', $webName.'.'.$topic, $extra );
    }

    $tmpl =~ s/\(edit\)/\(edit cmd=$saveCmd\)/go if $saveCmd;

    $tmpl =~ s/%CMD%/$saveCmd/go;
    $session->enterContext( 'can_render_meta', $meta );

    $tmpl = $session->handleCommonTags( $tmpl, $webName, $topic );
    $tmpl = $session->{renderer}->getRenderedVersion( $tmpl, $webName, $topic );
    # Don't want to render form fields, so this after getRenderedVersion
    my $formMeta = $meta->get( 'FORM' );
    my $form = '';
    my $formText = '';
    $form = $formMeta->{name} if( $formMeta );
    if( $form && !$saveCmd ) {
        my $formDef = new TWiki::Form( $session, $templateWeb, $form );
        unless( $formDef ) {
            throw TWiki::OopsException( 'attention',
                                        def => 'no_form_def',
                                        web => $session->{webName},
                                        topic => $session->{topicName},
                                        params => [ $templateWeb, $form ] );
        }
        $formDef->getFieldValuesFromQuery( $session->{cgiQuery}, $meta );
        # And render them for editing
        # SMELL: these are both side-effecting functions, that will set
        # default values for fields if they are not set in the meta.
        # This behaviour really ought to be pulled out to a common place.
        if ( $editaction eq 'text' ) {
            $formText = $formDef->renderHidden( $meta );
        } else {
            $formText = $formDef->renderForEdit( $webName, $topic, $meta );
        }
    } elsif( !$saveCmd && $session->{prefs}->getWebPreferencesValue( 'WEBFORMS', $webName )) {
        $formText = $session->{templates}->readTemplate( "addform", $skin );
        $formText = $session->handleCommonTags( $formText, $webName, $topic );
    }
    $tmpl =~ s/%FORMFIELDS%/$formText/g;

    $tmpl =~ s/%FORMTEMPLATE%//go; # Clear if not being used

    return ( $text, $tmpl );
}

sub finalize_edit {

    my ( $session, $text, $tmpl ) = @_;

    my $query = $session->{cgiQuery};
    my $webName = $session->{webName};
    my $topic = $session->{topicName};
    my $user = $session->{user};
    # apptype is undocumented legacy
    my $cgiAppType = $query->param( 'contenttype' ) ||
      $query->param( 'apptype' ) || 'text/html';

    $tmpl =~ s/%UNENCODED_TEXT%/$text/g;

    $text = TWiki::entityEncode( $text );
    $tmpl =~ s/%TEXT%/$text/g;

    $session->{store}->setLease( $webName, $topic, $user, $TWiki::cfg{LeaseLength} );

    $session->writeCompletePage( $tmpl, 'edit', $cgiAppType );
}

1;
