
package HTML::WebMake::CGI::CGIBase;

use CGI qw/:standard/;
use strict;
use HTML::Entities;
use HTML::WebMake::Main;
use HTML::WebMake::Util;
use HTML::WebMake::CGI::RWMetaTable;
use File::Basename;

use vars	qw{
  	@ISA $BASIC_TMPL_TOP $BASIC_TMPL_REST
};

@ISA = qw();

###########################################################################

$BASIC_TMPL_TOP = q{
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  <html><head><title>WebMake: __PAGE_TITLE__</title>
    <style>
      body, table, td, th {
        background-color: white;
        color: black;
        font-family: verdana,lucida,helvetica,sans-serif;
        font-size: 11px;
        line-height: 110%;
        margin-left: 10px;
        margin-right: 10px;
      }
      code, samp, pre {
        font-family: geneva,lucida console,Courier New,courier,fixed-width,monospace;
      }
      H1 {
        font-size: 150%; font-family: Garamond,Book Antiqua,Times,serif;
	text-align: center; padding: 1em 1em 1em 1em;
	border-width: 2px; border-color: black; border-style: solid;
	line-height: 150%;
        background: #FFCC66;
      }
      h4.breadcrumbs {
	font-size: 9pt;
	background: #FFDD77;
	text-align: center; padding: 1em 1em 1em 1em;
	border-width: 1px; border-color: black; border-style: dashed;
	line-height: 80%;
      }
      p.note {
        font-size: 9pt;
	margin: 1px 15px 1px 15px;
      }
      p.footer {
        font-size: 9pt;
        background: #FFEE88;
	text-align: center; padding: 1em 1em 1em 1em;
	border-width: 2px; border-color: black; border-style: solid;
	line-height: 200%;
      }
      div.create_file_box {
        font-size: 9pt;
	text-align: center; padding: 1em 1em 1em 1em;
	border-width: 1px; border-color: black; border-style: solid;
	margin: 1px 20px 1px 20px;
      }
      :link {
        font-weight: bold;
        color: #004000;
        text-decoration: underline; 
      }
      :visited {
        font-weight: bold;
        color: #008000;
        text-decoration: underline; 
      }
      :active {
        font-weight: bold;
        color: #800000;
        text-decoration: underline; 
      }
      .wm_textarea 
      {
	font-family: Verdana,lucida,Helvetica,sans-serif; 
	font-size: 9pt;
	border-bottom: 2px solid #cccccc;
	border-top: 2px solid #888888;
	border-left: 2px solid #777777;
	border-right: 2px solid #dddddd;
	width: 420px;
      }
      .wm_textfield 
      {
	font-family: lucida console,lucida sans typewriter,monospace; 
	font-size: 10pt;
	font-weight: bold;
	border-bottom: 2px solid #cccccc;
	border-top: 2px solid #888888;
	border-left: 2px solid #777777;
	border-right: 2px solid #dddddd;
	width: 420px;
      }
      .wm_short_textfield 
      {
	font-family: lucida console,lucida sans typewriter,monospace; 
	font-size: 10pt;
	font-weight: bold;
	border-bottom: 2px solid #cccccc;
	border-top: 2px solid #888888;
	border-left: 2px solid #777777;
	border-right: 2px solid #dddddd;
	width: 190px;
      }
    </style>

    <!-- based on code found on Filepile.  Sorry, do not know who contributed it!
	 If it was you, mail jm-preview-js /at/ jmason.org + I will happily
	 give credit.  -->

    <SCRIPT LANGUAGE="Javascript">
    <!--

var preview_win = null;

function ReloadTextDiv() {
  if (preview_win && !preview_win.closed) {
    var NewText = document.getElementById("upload_text").value;
    var DivElement = preview_win.document.getElementById("preview_div");
    DivElement.innerHTML = NewText;
  }
}

function OpenPreviewWindow() {
  preview_win = window.open ("", "wm_preview", "height=400,width=400");
  var d = preview_win.document;
  d.write ("<html><head><title>HTML Preview</title></head>"+
	"<body><h1>HTML Preview</h1><p><em>(This is a 'live' preview of any HTML "+
	"text entered in the WebMake Edit text box.  You may close this window "+
	"whenever you like, it will not affect your edits.)</em></p> "+
	"<div id='preview_div'></div></body></html>");
  d.close ();
  ReloadTextDiv();
}

    -->
    </SCRIPT>

    </head>
    <body bgcolor="#ffffff" text="#000000" link="#004000" vlink="#008000">
    <h1>WebMake: __PAGE_HEADER__</h1>
    <h4 class="breadcrumbs">__BREADCRUMBS__</h4>
};

$BASIC_TMPL_REST = q{
    __ERRORS__
    __MODULE_OUTPUT__

    <p class="footer">

    <a href="__REINVOKE__build=1__">[Build Site]</a>
    &nbsp;
    <a href="__REINVOKE__build=1&full=1__">[Build Fully]</a>

  <CVSONLY>
    &nbsp;
    <a href="__REINVOKE__Update=1__">[Update From CVS]</a>
    &nbsp;
    <a href="__REINVOKE__Commit=1__">[Commit Changes To CVS]</a>
  </CVSONLY>
  
    <br />

      You are logged in as __USERNAME__, editing the site "__WMKF__",
      with webmake.cgi from WebMake __WMVER__.

    </p>
    </body></html>
};

###########################################################################

sub new {
  my $class = shift;
  $class = ref($class) || $class;

  my $self = {
    'q',		shift,
    'file_base',	undef,
    'template',		$BASIC_TMPL_TOP.$BASIC_TMPL_REST,
    'info_msgs',	'',
    'msgs',		''
  };

  $self->{metatable} = new HTML::WebMake::CGI::RWMetaTable ();

  $self->{cvs_supported} = 0;
  if (defined $ENV{'CVSROOT'}) {
    $self->{cvs_supported} = 1;
  }
  $self->{cvs} = new HTML::WebMake::CGI::CVS ($self);

  bless ($self, $class);
  $self;
}

sub set_file_base {
  my ($self, $base) = @_;
  $self->{file_base} = $base;
  
  if (!-d $base) {
    die ("WebMakeCGI: FILE_BASE setting is invalid: ".
    		"\"$base\" is not a directory.\n");
  }

  # if we have a CVS dir in the file_base, the user has set this
  # area up with a "cvs login" and "cvs checkout". Good call.
  if (-f $base."/CVS/Root") {
    $self->{cvs_supported} = 1;
  }
}

###########################################################################

sub txt2html {
  my $self = shift;
  my $txt = join ('',@_);
  $txt =~ s/&/&amp;/gs;
  $txt =~ s/</&lt;/gs;
  $txt =~ s/>/&gt;/gs;
  $txt =~ s/\n/<br \/>\n/gs;
  $txt;
}

sub warn {
  my ($self, $err) = @_;
  
  chomp $err; warn "WebMakeCGI: $err\n";
  $self->{msgs} .= "<font color=\"#ff0000\">Warning: $err</font><br />\n";
}

sub info {
  my ($self, $err) = @_;
  $self->{info_msgs} .= "<font color=\"#008800\">Note: $err</font><br />\n";
}

sub is_media {
  my ($self, $filename) = @_;
  return 0 if (!defined $filename);

  if ($filename =~ /\.(?:gif|jp[eg]+|png|mov|avi|qt|mp[eg]+|ra|ram|gz|Z|zip)$/i
    || $filename =~ /\.(?:class|jar|cab|db|hist|sys|exe|com|mp3|prc|pdb|dat)$/i)
  {
    return 1;

  } else {
    return 0;
  }
}


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

sub run {
  my ($self) = @_;
  my $q = $self->{q};

  $|++;

  if (!$q->param ('dump')) {
    print "Content-Type: text/html\r\n\r\n";
  }

  $self->{msgs} = '';
  $self->{info_msgs} = '';
  my $form = '';
  $self->{filename} = '';

  if (!is_authorised ($q)) {
    $self->warn ("This site can only be edited by authenticated users.");
    goto end;
  }

  $self->{wmkfile} = &mksafepath($q->param('wmkf'));
  # this may be overridden in Site.pm, the module for editing .wmk files

  # check to see if CVS is available in this subdir
  my $base = File::Basename::dirname ($self->{wmkfile});
  if (-d $self->{file_base}."/".$base."/CVS") {
    $self->{cvs_supported} = 1;
  }

  $self->{cvsadd} = &mksafepathlist($q->param('cvsadd'));
  $self->{cvsaddbin} = &mksafepathlist($q->param('cvsaddbin'));
  $self->{cvsrm} = &mksafepathlist($q->param('cvsrm'));
  $self->{cvsrmdir} = &mksafepathlist($q->param('cvsrmdir'));

  # if we have a dirprefix parameter, add it to the filename.
  if ($q->param('dirprefix')) {
    $self->{filename} =
    		$self->makepath ($q->param('dirprefix'), $q->param('f'));
    $q->param('dirprefix', '');
    $q->param('f', $self->{filename});

  } else {
    $self->{filename} = &mksafepath($q->param('f'));
  }

  if (!defined $self->{wmkfile} && !$self->{no_wmkf_needed}) {
    $self->warn ("No .wmk file specified! Please use the 'wmkf' parameter.");
    goto end;
  }

  if (!$self->{no_filename_needed}
  	&& (!defined $self->{filename} || $self->{filename} eq ''))
  {
    $self->warn ("No filename provided.\n");
  } else {
    $form = $self->subrun ($q);
  }

end:
  if (!$q->param ('dump')) {
    $self->write_html_main ($form);
  }
}

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

sub std_cgi_hidden_items {
  my ($self, $q) = @_;
  $self->{cvsadd} ||= ''; $self->{cvsaddbin} ||= '';
  $self->{cvsrm} ||= ''; $self->{cvsrmdir} ||= '';
  $self->{wmkfile} ||= '';
  return $q->hidden(-name=>'wmkf',-value=>$self->{wmkfile})
  	. $q->hidden(-name=>'cvsadd',-value=>$self->{cvsadd})
  	. $q->hidden(-name=>'cvsaddbin',-value=>$self->{cvsaddbin})
  	. $q->hidden(-name=>'cvsrm',-value=>$self->{cvsrm})
  	. $q->hidden(-name=>'cvsrmdir',-value=>$self->{cvsrmdir});
}

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

sub std_cgi_hidden_items_as_str {
  my ($self, $q) = @_;
  $self->{cvsadd} ||= ''; $self->{cvsaddbin} ||= '';
  $self->{cvsrm} ||= ''; $self->{cvsrmdir} ||= '';
  $self->{wmkfile} ||= '';
  return 'wmkf='.$q->escape ($self->{wmkfile}) . '&'
    	. 'cvsadd='.$q->escape ($self->{cvsadd}) . '&'
    	. 'cvsaddbin='.$q->escape ($self->{cvsaddbin}) . '&'
    	. 'cvsrm='.$q->escape ($self->{cvsrm}) . '&'
    	. 'cvsrmdir='.$q->escape ($self->{cvsrmdir});
}

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

sub write_html_main {
  my ($self, $form) = @_;
  my $q = $self->{q};

  my $txt = $self->{template};
  my $user = $q->remote_user(); $user ||= '(nobody)';
  my $wmkf = $self->{wmkfile}; $wmkf ||= '(none)';
  my $bread = $self->get_breadcrumbs();

  if (!$self->{cvs_supported}) { $txt =~ s/<CVSONLY>.*<\/CVSONLY>//gs; }

  $txt =~ s/__PAGE_TITLE__/$self->{page_title}/ge;
  $txt =~ s/__PAGE_HEADER__/$self->{page_header}/ge;
  $txt =~ s/__BREADCRUMBS__/$bread/gs;
  $txt =~ s/__ERRORS__/$self->{msgs} $self->{info_msgs}/gs;
  $txt =~ s/__MODULE_OUTPUT__/${form}/gs;
  $txt =~ s/__FNAME__/$self->{filename}/gs;
  $txt =~ s/__USERNAME__/${user}/gs;
  $txt =~ s/__WMKF__/${wmkf}/gs;
  $txt =~ s/__WMVER__/${HTML::WebMake::Main::VERSION}/gs;
  $txt =~ s{__REINVOKE__(\S+?)__}{ $self->reinvoke_with_param(0,$1); }ge;
  $txt =~ s{__REINVOKEALL__(\S+?)__}{ $self->reinvoke_with_param(1,$1); }ge;

  print $txt;
}

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

sub reinvoke_with_param {
  my ($self, $keepexisting, $params) = @_;
  my $q = $self->{q};

  my $href = $q->url (-relative=>1, -path=>1) . '?' . $params;
  my $str;
  if ($keepexisting)
  {
    # keep all CGI parameters (except the ones overridden by new settings)
    $str = $q->query_string ();
  } else {
    # just keep the essentials. namely: the name of the .wmk file,
    # and cvs operations pending
    $str = $self->std_cgi_hidden_items_as_str ($q);

  }

  my %pkeys = ();
  foreach my $pkey (split (/[\&\;]/, $params)) {
    $pkey =~ s/=.*$//; $pkeys{$pkey} = 1;
  }

  foreach my $elem (split (/\&/, $str)) {
    if ($elem =~ /^(.*?)=/) {
      if (defined $pkeys{$1}) { next; }
    }

    $href .= '&'.$elem;
  }

  $href;
}

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

sub mydirname {
  my ($self) = @_;
  return File::Basename::dirname ($self->{filename});
}

sub mydirurl {
  my ($self) = @_;
  return $self->{q}->escape ($self->mydirname());
}

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

sub makepath {
  my ($self, $dir, $path) = @_;

  if (!defined($dir) || $dir eq '' || $dir eq '.') {
    # ignore it
  } else {
    $path = $dir.'/'.$path;
  }

  return mksafepath ($path);
}

###########################################################################

sub cvs_add {
  my ($self, $fname) = @_;

  return if (!$self->{cvs_supported});

  if ($self->is_media ($fname)) {
    if ($self->{cvsaddbin}) {
      $self->{cvsaddbin} .= "|".$fname;
    } else {
      $self->{cvsaddbin} = $fname;
    }
  } else {
    if ($self->{cvsadd}) {
      $self->{cvsadd} .= "|".$fname;
    } else {
      $self->{cvsadd} = $fname;
    }
  }
}

sub cvs_delete {
  my ($self, $fname) = @_;

  return if (!$self->{cvs_supported});

  if (-d $self->{file_base}.$fname) {
    if ($self->{cvsrmdir}) {
      $self->{cvsrmdir} .= "|".$fname;
    } else {
      $self->{cvsrmdir} = $fname;
    }
  } else {
    if ($self->{cvsrm}) {
      $self->{cvsrm} .= "|".$fname;
    } else {
      $self->{cvsrm} = $fname;
    }
  }
}

###########################################################################

sub read_wmk_file
{
  my ($self, $file) = @_;

  my $dir = $self->mydirname();
  $self->{webmake} = new HTML::WebMake::Main ( {
        'base_dir'      => $self->{file_base}.'/'.$dir
    } );

  my $cgi = $self->{webmake}->cgi_parse_file
                                ($self->{file_base}.'/'.$file);

  if (!defined $cgi) {
    $self->warn ("Failed to parse WebMake file \"$file\"");
    return 0;
  }

  $self->{fulltext} = $cgi->{fulltext};
  $self->{items} = $cgi->{items};

  1;
}

###########################################################################

sub get_breadcrumbs {
  my ($self) = @_;
  my $q = $self->{q};

  # [edit]  Top > main.wmk > contents: data/ > cv.txt   [view]

  $self->{cvsadd} ||= ''; $self->{cvsaddbin} ||= '';
  $self->{cvsrm} ||= ''; $self->{cvsrmdir} ||= '';
  my $href = $q->url (-relative=>1, -path=>1).'?'
    	. 'cvsadd='.$q->escape ($self->{cvsadd}) . '&'
    	. 'cvsaddbin='.$q->escape ($self->{cvsaddbin}) . '&'
    	. 'cvsrm='.$q->escape ($self->{cvsrm}) . '&'
    	. 'cvsrmdir='.$q->escape ($self->{cvsrmdir});

  my $txt = qq{
    <a href="${href}">Top</a>
  }; #"

  if (defined $self->{wmkfile}) {
    $txt .= qq{
      &raquo;
      <a href="__REINVOKE__site=1__">$self->{wmkfile}</a>
    }; #"
  }

  if (defined $self->{task_breadcrumb}) {
    $txt .= qq{
      &raquo;
      $self->{task_breadcrumb}
    }; #"
  }

  $txt .= qq{
    <br />
  }; #"
  return $txt;
}

###########################################################################

sub mksafe {
  local($_) = shift;
  if (!defined $_) { return undef; }

  s/\0/_/gs;		# strip NULs
  s/[^-=_+\[\]\@\#,.\/:\~%^\(\)\{\}A-Za-z0-9 ]/_/gs;
  $_;
}

sub mksafepath {
  local($_) = shift;
  if (!defined $_) { return undef; }

  $_ = mksafe($_);
  s/[^-_+\@,.\/\#\=:%A-Za-z0-9 ]/_/gs;
  s,^\/+,,gs;		# //foo -> foo
  s,\/\/+,/,gs;		# foo//bar -> foo/bar
  1 while s,^\.\/+,,gs;	# strip ./././foo
  s,[^/]+/+\.\./+,,gs;	# strip ..s
  s,\.\./+,,gs;		# strip any leftover ..s
  s,^\.\.$,.,gs;	# ".." = "."
  $_;
}

sub mksafepathlist {
  local($_) = shift;
  if (!defined $_) { return undef; }

  my @new = ();
  foreach my $item (split (/\|/, $_)) {
    next if ($item eq '');
    push (@new, mksafepath ($item));
  }
  return join ('|', @new);
}

sub is_authorised {
  my ($q) = @_;

  my $auth = $q->auth_type();
  my $user = $q->remote_user();
  if (defined $auth && defined $user) { return 1; }

  CORE::warn "unauthorised access from ".$q->remote_host()."\n";
  return 0;
}

###########################################################################

sub subst_template {
  my ($self, $tmpl, $vars) = @_;
  foreach my $key (keys %{$vars}) {
    $tmpl =~ s/\{${key}\}/$vars->{$key}/gs;
    $tmpl =~ s/__${key}__/$vars->{$key}/gs;
  }
  return $tmpl;
}

###########################################################################

1;
