#?SuikaWikiConfig/2.0

Plugin:
  @Name: WikiEdit
  @Description:
    @@@: WikiPage editing components
    @@lang:en
  @License: %%GPL%%
  @Author:
    @@Name:
      @@@@: Wakaba
      @@@lang:ja
      @@@script:Latn
    @@Mail[list]: w@suika.fam.cx
  @Date.RCS: $Date: 2003/11/25 12:38:22 $
  @RequiredModule[list]:
    SuikaWiki::Name::Space
    Digest::SHA1
  @RequiredPlugin[list]:
    WikiView
    WikiStruct
    WikiLinking
    WikiFormCore
    WikiFormText
    HTML

PluginConst:
  @NS_XHTML1:
    http://www.w3.org/1999/xhtml

ViewDefinition:
  @Mode: edit
  @Condition:
    @@http-method[list]:
      GET
      HEAD
  @Description:
    @@@: Edit WikiPage content as whole, as a plaintext
    @@lang: en
  @template:
    @@http-status-code: 200
    @@media-type: text/html
    @@use-media-type-charset: 1
    @@expires: %%edit%%
    @@body:
	%html-document (
          title => {%res(name=>{Edit:WebPageTitle});}p,
          link-meta => {%template (name => links);
                        %html-meta(name => ROBOTS, content => NOINDEX);}p,
          content => {
            %template (
              name => ws--page,
              -content => {%template (name => we--edit-body);},
            );
          }p,
        );

ViewDefinition:
  @Mode: write
  @Condition:
    @@http-method[list]:
      POST
  @Description:
    @@@: Saving modified (new) WikiPage content
    @@lang: en
  @method:
    @@@:
      my $touch = $self->{view}->{wiki}->{input}->parameter ('we--touch');
      
      $self->{view}->{wiki}->{var}->{db}->{read_only}->{'content'} = 0;
      $self->{view}->{wiki}->{var}->{db}->{read_only}->{'lastmodified'} = 0
        if $touch;
      $self->{view}->init_db;

      my $page = $self->{view}->{wiki}->{var}->{page};

## TODO: Implements access control
      #if (&frozen_reject()) {
      #}
      
      if (not __FUNCPACK__::page_is_editable ($page)) {
## TODO: Implements this
#        &_do_view_msg (-view => '-error', -page => $main::form{mypage},
#                       error_message => &Resource ('Error:ThisPageIsUneditable'));
#        return;
         die "Uneditable!";
      }
      
      ## Check confliction
      my $current_content = $self->{view}->{wiki}->{db}->get (content => $page);
      if (length $current_content) {
        my $current_digest = __FUNCPACK__::digest ($current_content,
                                                  normalize => 1);
        my $prev_digest
             = $self->{view}->{wiki}->{input}->parameter ('we--digest');
        if ($current_digest ne $prev_digest) {
## TODO: Implements this
#        _do_view_msg (-view => '-conflict', -page => $page_name);
#          die "@{[join '//', @$page]}: Conflict : (current) $current_digest [$current_content] vs (prev) $prev_digest";
          return $self->{view}->{wiki}->view_in_mode
                   (mode => '-conflict');
        }
      }
      
      my $new_content
           = $self->{view}->{wiki}->{input}->parameter ('we--content');    
      if (length $new_content) {
        $self->{view}->{wiki}->{db}->set (content => $page => $new_content);
        if ($touch) {
          $self->{view}->{wiki}->{db}->set (lastmodified => $page => time);
        }

## TODO: Use SuikaWiki 3 interface
        my $new_mode = $self->{view}->{wiki}->{input}->parameter
                         ('we--mode-modified');
        $new_mode =~ s/[^0-9A-Za-z._-]+//g;
	my $uri = SuikaWiki::Plugin->_uri_wiki_page (join ('//', @$page), 
                                                     mode => $new_mode,
                                                     with_lm => 1,
                                                     absolute => 1);
        $uri .= qq<;we--mode-modified=$new_mode>;
        
        require SuikaWiki::Output::HTTP;
        my $output = SuikaWiki::Output::HTTP->new
          (wiki => $self->{view}->{wiki});
        $output->set_redirect (uri => $uri, status_code => 303);
        $output->output (output => 'http-cgi');
      } else {
        for (qw/content lastmodified/) {
          $self->{view}->{wiki}->{db}->delete (content => $page);
        }
        $self->{view}->{wiki}->view_in_mode (mode => '-deleted');
      }
    @@Name: main

ViewDefinition:
  @Mode: adminedit
  @Condition:
    @@http-method[list]:
      GET
      HEAD
  @Description:
    @@@: Edit WikiPage content as whole, as a plaintext (administrator mode)
    @@lang: en
  @template:
    @@http-status-code: 200
    @@media-type: application/xhtml+xml
    @@use-media-type-charset: 1
    @@expires: %%edit%%
    @@body:
	%html-document (
          title     => {%res(name=>{Edit:Admin:WebPageTitle});}p,
          link-meta => {%predefined-template(name=>links);
                        %html-meta(name=>ROBOTS,content=>NOINDEX);}p,
          content   => {
	    %template (
              name => ws--page,
              -content => {%template (name => we--adminedit-body);},
            );
          }p,
        );

ViewDefinition:
  @Mode: -conflict
  @Condition:
    @@http-method[list]:
      GET
      HEAD
  @Description:
    @@@: Confliction message
    @@lang: en
  @template:
    @@http-status-code: 409
    @@media-type: text/html
    @@use-media-type-charset: 1
    @@body:
	%html-document (
          title     => {%res(name=>{Edit:Conflict:WebPageTitle});}p,
          link-meta => {%template(name=>links);
                        %html-meta(name=>ROBOTS,content=>NOINDEX);}p,
          content   => {
	    %template (
              name => ws--page,
              -content => {
                %section (level=>2,
                  title   => {%res(name=>{Edit:Conflict:Title});}p, heading,
                  content => {%template (name => we--conflict-body);}p,
                );
              },
            );
          }p,
        );

ViewDefinition:
  @Mode: -deleted
  @Condition:
    @@http-method[list]:
      GET
      HEAD
  @Expires:%%view%%
  @Description:
    @@@: Confliction message
    @@lang: en
  @template:
    @@http-status-code: 200
    @@media-type: text/html
    @@use-media-type-charset: 1
    @@body:
	%html-document (
          title     => {%res (name => {Edit:Deleted:WebPageTitle});}p,
          link-meta => {%template (name => links);
                        %html-meta (name => ROBOTS, content => NOINDEX);}p,
          content   => {
	    %template (
              name => ws--page,
              -content => {
                %section (level=>2,
                  title   => {%res(name=>{Edit:Deleted:Title});}p, heading,
                  content => {
                    %paragraph (content => {%res
                                  (name => {Edit:Deleted:Description});}p);
                  }p,
                );
              },
            );
          }p,
        );

ViewFragment:
  @Name: links
  @Description:
    @@@: Link to edit mode of the WikiPage
    @@lang:en
  @Formatting:
    %link-wiki(mode=>edit,rel=>edit,class=>wiki-cmd,
               title=>{%res(name=>{Link:Edit:Description});}p,up-to-date);
    %link-wiki(mode=>adminedit,rel=>edit,class=>wiki-cmd,
               title=>{%res(name=>{Link:AdminEdit:Description});}p,up-to-date);

ViewFragment:
  @Name: navbar
  @Description:
    @@@: Link to edit mode of the WikiPage
  @Order: 10
  @Formatting:
    %link-to-wikipage (
      mode  => edit,
      rel   => edit,
      up-to-date,
      label => {%link-to-it (
        class => wiki-cmd,
        label => {%res (name => EditThisPage);}p,
        description => {%res (name => EditThisPageLong);}p,
      );},
      page-anchor-name => edit,
    ); 

ViewFragment:
  @Template[list]:
     we--conflict-body
  @Order: 30
  @Description:
    @@@: Conflict report
    @@lang:en
  @Formatting:
        %paragraph (content=>{%res(name=>{Edit:Conflict:Description});}p);
        %we--conflict-modified-content;
        %section (level=>3,
          id      => edit-conflict-diff,
          title   => {%res(name=>{Edit:Conflict:Diff:Title});}p, heading,
          content => {
            %paragraph (
              content => {%res(name=>{Edit:Conflict:Diff:Description});}p);
            %conflict-diff;
          }p,
        );

ViewFragment:
  @Template[list]: we--edit-body
  @Order:80
  @Description:
    @@@: Editing WikiPage form section
    @@lang:en
  @Formatting:
                %section (level=>2,
                  id => edit,
                  title => {%res (name => {Edit:Title});}p, heading,
                  content => {%edit-form;}p,
                );

ViewFragment:
  @Template[list]: we--adminedit-body
  @Order:80
  @Description:
    @@@: Editing WikiPage form section (Admin edit mode)
    @@lang:en
  @Formatting:
                %section (level=>2,
                  id      => edit,
                  title   => {%res(name=>{Edit:Title});}p, heading,
                  content => {%edit-form(admin);}p,
                );

ViewFragment:
  @Template[list]: we--conflict-body
  @Order: 80
  @Description:
    @@@: Editing-WikiPage-form section (conflict mode)
    @@lang:en
  @Formatting:
        %section (level=>2,
          id      => edit,
          title   => {%res(name=>{Edit:Title});}p, heading,
          content => {
            %paragraph(name=>{%res(name=>{Edit:Conflict:Edit:Description});}p);
            %edit-form;
          }p,
        );

ViewFragment:
  @Name: we--edit
  @Order: -1000
  @Formatting:
    %block (content => {%form-hiddens;
ViewFragment:
  @Name: we--edit
  @Description:
    @@@: Edit form --- main textarea
    @@lang:en
  @Order: 0
  @Formatting:
    %textarea(id=>we--content,size=>{%res(name=>{Edit:Form:Size});}p,
                              lines=>{%res(name=>{Edit:Form:Lines});}p);
ViewFragment:
  @Name: we--edit
  @Description:
    @@@: Submit button
    @@lang:en
  @Order: 200
  @Formatting:
    %submit(label=>{%res(name=>{Edit:Save});}p);
ViewFragment:
  @Name: we--edit
  @Order: 1000
  @Formatting:
    }p);

FormattingRule:
  @Category[list]: view
  @Name: edit-form
  @Description:
    @@@: Provides WikiPage editing form
    @@lang: en
  @Parameter:
    @@Name: admin
    @@Type: boolean
    @@Default: {0}
    @@Description:
      @@@@: Whether administrator's editing mode or not
      @@@lang:en
  @Formatting:
    __ATTRTEXT:%admin__;
        if (not __FUNCPACK__::page_is_editable ($o->{wiki}->{var}->{page})) {
          $p->{-parent}->append_new_node (type => '#element',
                                          namespace_uri => $NS_XHTML1,
                                          local_name => 'p')
                  ->append_text ($o->resource('Error:ThisPageIsUneditable'));
#        } elsif (!$p->{admin} && &main::is_frozen ($o->{page})) {
#          $r = '<p>'.$o->resource('Error:ThisPageIsUneditable',escape=>1).'</p>';
        } else {
          #$r = '<p>'.$o->resource('Error:PasswordIsNotSpecified',escape=>1).'</p>' if $p->{admin};

    my $form = $p->{-parent}->append_new_node
	(type => '#element',
         namespace_uri => $NS_XHTML1,
         local_name => 'form');
      $form->set_attribute (method => 'post');
## TODO: Use SuikaWiki 3 interface
      $form->set_attribute (action => $o->uri ('wiki'));
      $form->set_attribute ('accept-charset' => 'iso-2022-jp utf-8');
    
    my $hidden = ref ($p->{-parent})->new (type => '#fragment');
    local $o->{form}->{hidden} = $hidden;
      for ($hidden->append_new_node (type => '#element',
                                   namespace_uri => $NS_XHTML1,
                                   local_name => 'input')) {
        $_->set_attribute (type => 'hidden');
        $_->set_attribute (name => 'mypage');
## TODO: Use SuikaWiki 3 interface
        $_->set_attribute (value => join '//', @{$o->{wiki}->{var}->{page}});
        $_->option (use_EmptyElemTag => 1);
      }
      for ($hidden->append_new_node (type => '#element',
                                   namespace_uri => $NS_XHTML1,
                                   local_name => 'input')) {
        $_->set_attribute (type => 'hidden');
        $_->set_attribute (name => 'mode');
        $_->set_attribute (value => 'write');
        $_->option (use_EmptyElemTag => 1);
      }
    ## _charset_ hack
      for ($hidden->append_new_node (type => '#element',
                                   namespace_uri => $NS_XHTML1,
                                   local_name => 'input')) {
        $_->set_attribute (type => 'hidden');
        $_->set_attribute (name => '_charset_');
        $_->option (use_EmptyElemTag => 1);
      }
      
    my $ovar_orig = $o->{var};
    $o->{var} = {
      #content
      is_admin_mode => $p->{admin},
      is_conflict_mode => 0,
      #is_new_page_template
    };
    $o->{var}->{content} = __FUNCPACK__::get_content
                             ($o, $o->{wiki}->{var}->{page});
    for ($hidden->append_new_node (type => '#element',
                                 namespace_uri => $NS_XHTML1,
                                 local_name => 'input')) {
      $_->set_attribute (type => 'hidden');
      $_->set_attribute (name => 'we--digest');
      $_->set_attribute (value => ($o->{var}->{is_new_page_template}?'':
                                   __FUNCPACK__::digest ($o->{var}->{content},
                                                        normalize => 1)));
      $_->option (use_EmptyElemTag => 1);
    }
    
    $form->append_node ($o->formatter ('form_input')->replace 
                                      ($o->{wiki}->{view}->assemble_template
                                                             ('we__edit'),
                                       param => $o));
    $o->{var} = $ovar_orig;
  }

FormattingRule:
  @Category[list]: view
  @Name: we--conflict-modified-content
  @Description:
    @@@:
      Modified content that is conflicted with current content
    @@lang:en
  @Formatting:
    $p->{-parent}->append_new_node (type => '#element',
                                    namespace_uri => $NS_XHTML1,
                                    local_name => 'pre')
      ->append_text (scalar $o->{wiki}->{input}->parameter ('we--content'));

FormattingRule:
  @Category[list]:view
  @Name: conflict-diff
  @Description:
    @@@:
      Provides marked diff between latest WikiPage content and 
      submitted one
    @@lang:en
  @Formatting:
    require Algorithm::Diff;
    my $diff = $p->{-parent}->append_new_node (type => '#element',
                                               namespace_uri => $NS_XHTML1,
                                               local_name => 'pre');
    $diff->set_attribute (class => 'diff');
    for (Algorithm::Diff::diff (
               [split /\x0D?\x0A/,
                      scalar $o->{wiki}->{input}->parameter ('we--content')],
               [split /\x0D?\x0A/,
                      $o->{wiki}->{db}->get (content
                                             => $o->{wiki}->{var}->{page})]
            )) {
          for (@{$_}) {
            my ($sign, $lineno, $text) = @{$_};
            my $ename = $sign eq '+' ? 'ins' : $sign eq '-' ? 'del' : 'span';
            my $line = $diff->append_new_node (type => '#element',
                           namespace_uri => $NS_XHTML1,
                           local_name => ({qw/+ ins - del/}->{$sign}||'span'));
            
            $line->set_attribute (class => 'line');
            for ($line->append_new_node (type => '#element',
                                         namespace_uri => $NS_XHTML1,
                                         local_name => 'span')) {
              $_->set_attribute (class => 'lineno');
              $_->append_text ($lineno);
            }
            $line->append_text (' ');
            for ($line->append_new_node (type => '#element',
                                         namespace_uri => $NS_XHTML1,
                                         local_name => 'span')) {
              $_->set_attribute (class => 'sign');
              $_->append_text ($sign);
            }
            $line->append_text (' ');
            for ($line->append_new_node (type => '#element',
                                         namespace_uri => $NS_XHTML1,
                                         local_name => 'span')) {
              $_->set_attribute (class => 'content');
              $_->append_text ($text);
            }
            $line->append_text ("\n"); 
          } # diff lines
        }

FormattingRule:
  @Name:mode-after-edit-selection
  @Category[list]:we--edit
  @Parameter:
    @@Name: name
    @@Type: html4:name
    @@Default:"we--mode-modified"
    @@Description:
      @@@@:Form control name for the selection
      @@@lang:en
  @Formatting:
    __ATTRTEXT:%name__;
    my $magic = '';
    $magic = $1 if $o->{var}->{content} =~ m/^([^\x0A\x0D]+)/s;
    
    my $name = $p->{name} || 'we--mode-modified';
    my $selected_mode = $o->{wiki}->{input}->parameter ($name);
    unless ($selected_mode) {
      if ($magic =~ /C(?:on(?:fig|st)|SS)/) {
        $selected_mode = 'edit';
      } else {
        $selected_mode = 'default';
      }
    }
    
    my $SELECT = $p->{-parent}->append_new_node (type => '#element',
                                                 namespace_uri => $NS_XHTML1,
                                                 local_name => 'select');
    $SELECT->set_attribute (name => $name);
    for (qw/default read edit/) {
      for my $OPTION ($SELECT->append_new_node (type => '#element',
                                                namespace_uri => $NS_XHTML1,
                                                local_name => 'option')) {
        $OPTION->set_attribute (value => $_);
        my $label = $o->resource ('Edit:SaveAnd:'.$_.':Label');
        $OPTION->set_attribute (label => $label);
        $OPTION->append_text ($label);
        $OPTION->set_attribute
          (title => $o->resource ('Edit:SaveAnd:'.$_.':Description'));
        $OPTION->set_attribute (selected => 'selected')
          if $selected_mode eq $_;
      }
    }

Resource:
  @Edit:Conflict:Title:
    @@@:Confliction detected!
    @@lang:en
  @Edit:Conflict:WebPageTitle:
    @@@:%page-name; (Edit : 409 CONFLICTION)
    @@lang:en
  @Edit:Deleted:Description:
    @@@:Content of WikiPage %page-name; has been removed.
    @@lang:en
  @Edit:Deleted:Title:
    @@@:WikiPage Deleted
    @@lang:en
  @Edit:Deleted:WebPageTitle:
    @@@:%page-name; (Deleted)
    @@lang:en
  @Edit:Form:Lines:15
  @Edit:Form:Size:18
  @Edit:Save:
    @@@:Save
    @@lang:en
  @Edit:SaveAnd:default:Description:
    @@@: Save modified content and show content with your default view mode
    @@lang:en
  @Edit:SaveAnd:default:Label:
    @@@: Default view
    @@lang:en
  @Edit:SaveAnd:edit:Description:
    @@@: Save modified content and show content in the edit mode again
    @@lang:en
  @Edit:SaveAnd:edit:Label:
    @@@: Edit again
    @@lang:en
  @Edit:SaveAnd:read:Description:
    @@@: Save modified content and show content in the read mode
    @@lang:en
  @Edit:SaveAnd:read:Label:
    @@@: Show content
    @@lang:en
  @EditThisPage:
    @@@: Edit
    @@lang:en
  @EditThisPageLong:
    @@@: Edit this WikiPage
    @@lang:en
  @Edit:WebPageTitle:
    @@@: %page-name; (Modification)
    @@lang:en
  @Edit:Title:
    @@@: Modification of the WikiPage
    @@lang:en
  @Link:AdminEdit:Description:
    @@@: Edit this WikiPage (administrator's mode)
    @@lang:en
  @Link:Edit:Description:
    @@@: Edit this WikiPage
    @@lang:en

Function:
  @Name:digest
  @Description:
    @@@: Compute digest of given string
    @@lang:en
  @Main:
    my ($s, %opt) = @_;
    require Digest::SHA1;
    if ($opt{normalize}) {
      $s =~ s/\x0D\x0A/\x0A/g;
      $s =~ tr/\x0D/\x0A/;
    }
    Digest::SHA1::sha1_base64 ($s);

Function:
  @Name:get_content
  @Description:
    @@@:Get WikiPage content to be modified.  For new (currently not exist) 
        WikiPage, new page template content is returned.
    @@lang:en
  @Main:
    my ($o, $page) = @_;
    my $content;
    $content = $o->{wiki}->{db}->get (content => $page);
    
    unless (length $content) {
      ## TODO: use namespaced template page
      $content = $o->{wiki}->{db}->get
        (content => $o->{wiki}->{config}->{page}->{NewPageTemplate});
      $o->{var}->{is_new_page_template} = 1;
    }
    $content;

Function:
  @Name:page_is_editable
  @Description:
    @@@:
      Returns whether the WikiPage is editable or not.
      Note that this function is temporary. 
    @@lang:en
  @Main:
    my ($page) = @_;
    $page = join '//', $page;
    return 0 unless SuikaWiki::Name::Space::validate_name ($page);
    return 0 if $page =~ /[\x00-\x20\[\]\x7F]/;
    1;

Parameter:
  @Name: we--content
  @Type: text
  @Default: ""
  @Description:
    @@@:
      Content of WikiPage (to be)
    @@lang:en

Parameter:
  @Name: we--digest
  @Type: "Base64ed SHA1"
  @Default: {undef}
  @Description:
    @@@:
      SHA1 digest value of the WikiPage content before the modification.
      This value is used to detect confliction of modifying.
      Empty value (undef or length-zero-string) means that
      there were no WikiPage content.
    @@lang:en

Parameter:
  @Name: we--mode-modified
  @Type: mode-name
  @Default: "default"
  @Description:
    @@@:
      WikiEngine running mode to be specified after modification is completed
    @@lang:en

Parameter:
  @Name: we--touch
  @Type: form:checkbox-value
  @Default: "off"
  @Description:
    @@@:
      Whether last-modified date-time of the WikiPage should be updated or not
      when the WikiPage is modified.
    @@lang:en