P4CGI.pm #12

  • //
  • guest/
  • fredric_fredricson/
  • P4DB/
  • main/
  • P4CGI.pm
  • View
  • Commits
  • Open Download .zip Download (39 KB)
#!/usr/bin/perl -w
# -*-perl-*-
##################################################################
# "P4CGI" perl module.
#
#
#
package P4CGI ;
###
# You might need to uncomment and set the lib to point to the  perl libraries
#
#use lib '/usr/local/lib/perl5/site_perl' ;

sub ConfigFileName()
{
    return "./P4DB.conf" ; # Change here to move or rename configuration file
}

use CGI ;
use CGI::Carp ;
use strict;

#### Conficuration file name
my $CONFIG_FILE ;


my @ERRLOG ;
my $errors ;

#### The following constants are set or updated by the init() routine

my $VERSION ;			# P4DB version
my $CHANGELEVEL ;		# P4 change level
my $P4 ;			# Contains p4 command
my $PORT;			# Contains p4 port
my $P4_HOSTNAME;		# Contains p4 host name
my $CGI;			# Contains CGI object from CGI.pm 
my $lastChange ;  	        # Current change level
my $IGNORE_CASE;		# "YES" if case should be ignored
my $MAX_CHANGES;		# Max changes for change list
my $TAB_SIZE;			# Tab size for file viewer
#my @RESTRICTED_FILES ;		# Files where view is restricted

my $SERVER_VERSION_YEAR ;	# Server year
my $SERVER_VERSION_NO ;		# Server count
my $SERVER_VERSION_QUAL ;	# Server version qualifier
my $SERVER_VERSION_CHANGE ;	# Server change No.
my $REQUIRED_SERVER_VERSION_YEAR ;
my $STYLE_SHEET ;		# Style sheet file name
my $HELPFILE_PATH;		# Path to help file (html)
my $REDIRECT_ERROR_TO_NULL_DEVICE; # Part of command thar redirects errors to /dev/null
my $REDIRECT_ERROR_TO_STDOUT;   # Part of command thar redirects errors to stdout
my $DEBUG ;			# When true, prints log

my $UNUSEDCLIWL ;               # Unused client warning level
my $UNUSEDUSRWL ;               # Unused user warning level

my %SHORTCUTS ; 		# Shortcuts (or favorites)

my %PREF ;			# Preferences
my %PREF_LIST ;			# List of preferences

my $LEGEND ;			# Legend supplied to header

my $NO_CONTACT_ERROR ;		# Defined if problem with server

my $SETCOOKIES ; 		# Set to 1 if cookies should be set

my $RELOADTHIS ;                # Set to 1 if this page should be reloaded
                                # according to the current parameters

#### Other package variables

my $pageStartPrinted ;		# Used to avoid mutliple headers if an error occurres

my @P4DBadmin ;	# Admins for P4DB and Perforce server

my %CONF ;		# Hash containing configuration

my %EXTRAHEADER ;

my $ONLOADSCRIPT ;

my $helpTarget ;		# target for help text


###
### Subroutine that prints the error log
###
sub prerrlog() {
    my $res = "" ;
    if(@ERRLOG> 0) {
	map { 
	    if(/^ERROR:/) { $_ = "<font color=red>" . &htmlEncode($_) . "</font>" ; } 
	    else { $_ = "<font color=blue>" . &htmlEncode($_) . "</font>" ; } 
	  }  @ERRLOG ;
	$res .= 
	    "<p><font color=blue>Log printout:<br><pre>" .
	    $CGI->self_url() .
	    join("\n",@ERRLOG) .
	    "</pre></font>\n" ;
    } ;    
    return $res ;
}

###
### Initialization code, called from BEGIN().
###
sub init( )
{
    $SETCOOKIES = 0 ;
    $RELOADTHIS = 0 ;
    $REQUIRED_SERVER_VERSION_YEAR = 2001 ;
    #
    # Set config file name
    #
    $CONFIG_FILE = &ConfigFileName ;
			## death handler
    $SIG{'__DIE__'} = sub {	# Thank you Ron Shalhoup for the idea
	my($error) = shift; 
	&P4CGI::bail("Signal caught: $error") ;	
	exit 0; 
    }; 

    #
    # clear error counter
    #
    $errors = 0 ;

    #
    # Set version
    #
    $VERSION="3.0beta1" ;
    $CHANGELEVEL=2989 ;

    #
    # Set configuration defaults
    #
    $HELPFILE_PATH     = "./" ;
    $UNUSEDCLIWL       = 10 ;
    $UNUSEDUSRWL       = 10 ;
    
    #
    # Initiate CGI module 
    #
    $CGI = new CGI ;
    $CGI->autoEscape(undef) ;

    #
    # Setup preference list
    #
    %PREF_LIST = 
	(
	 "DP"   => ["a:LIST","P4 Depot",0],
	 "IC"   => ["d:BOOL","Ignore case",0],
	 "MX"   => ["d:INT","Max changes to show",400],
	 "TB"   => ["d:INT","Default tab stop",8],
	 "HD"   => ["e:BOOL","Hide deleted files by default",0],
	 "VC"   => ["f:BOOL","View files with colors",1],
	 "ST"   => ["f:LIST","Style Sheet File",0],
	 "DBG"  => ["z:BOOL","Print log information (debug)",0],
	 ) ;
    
				### Set preferences
    %PREF=$CGI->cookie(-name=>"P4DB_PREFERENCES") ; # First try cookie...
    my $p ;
    foreach $p (keys %PREF_LIST) {
	if(! defined $PREF{$p}) { 
	    $PREF{$p} = $ {$PREF_LIST{$p}}[2];
	    ERRLOG("Set $p!") ;
	}
    }  ;
    foreach $p (keys %PREF) {
	if(exists $PREF_LIST{$p}) {
	    ERRLOG("PREF: $p => $PREF{$p} (${$PREF_LIST{$p}}[1])") ;
	}
	else {
	    delete $PREF{$p} ;
	} ;
    } ;
    if(defined $CGI->param("SET_PREFERENCES")) {
	my $c ;
	foreach $c (keys %PREF) {
	    my $val =  $CGI->param($c) ;
	    if(defined $val) {
		my $type = $ {$PREF_LIST{$c}}[0] ;
		if($type eq "INT") {  $val =~ /^\d+$/ or next ;	} ;
		if($type eq "BOOL") { $val =~ /^[01]$/ or next ; } ;
		$PREF{$c} = $val ;
	    }
	}
	$SETCOOKIES = 1 ;
    }

				### Set up data structure for configuration file read
    my %configReaderData = ( "P4PATH"         => \$P4,
			     "HTML_HELPFILE_PATH" => \$HELPFILE_PATH,
			     "P4DB_ADMIN"     => \@P4DBadmin,
			     "SHELL"          => \$ENV{"SHELL"},
			     "REDIRECT_ERROR_TO_NULL_DEVICE" => \$REDIRECT_ERROR_TO_NULL_DEVICE,
			     "REDIRECT_ERROR_TO_STDOUT"      => \$REDIRECT_ERROR_TO_STDOUT,
			     "DEPOT"          => $PREF_LIST{"DP"},
			     "STYLES"         => $PREF_LIST{"ST"},
			     "UNUSED_CLIENT_WARNING_LEVEL" => \$UNUSEDCLIWL,
			     "UNUSED_USER_WARNING_LEVEL" => \$UNUSEDUSRWL
			     ) ;
    
				### Read configuration file 
    local *F ;
    my $line = 0 ;
    open(F,"<$CONFIG_FILE") or &P4CGI::bail("Could not open config file \"$CONFIG_FILE\" for read") ;   
    while(<F>) {
	$line++ ;
	chomp ;			# Remove newline
	s/^\s+// ;		# remove leading spaces
	next if (/^\#/ or /^\s*$/) ; # Skip if comment or empty line
	s/\s+/ /g ;		# Normalize all spaces to a single space
	s/ $// ;		# Remove trailing spaces

				# Check syntax and get data
	/^(\S+):\s*(.*)/ or 
	    &P4CGI::bail("Parse error in config file \"$CONFIG_FILE\" line $line:\n\"$_\"") ;
				# Get values
	my ($res,$val) = ($1,$2);
				# Make sure config value exist
	if(! exists $configReaderData{$res}) {
	    &P4CGI::bail("Parse error in config file \"$CONFIG_FILE\" line $line:\n\"$_\"") ;
	} ;
				# Get config value and check type
	my $ref = $configReaderData{$res} ;
	my $type = ref($ref) ;
	$type eq "SCALAR" and do { $$ref = $val ; ERRLOG("$res=$val") ; next ; } ;
	$type eq "ARRAY" and do {
	    if($res =~ /^\@/) {
		push @$ref,split /\s+/,$val ;
	    }
	    else {
		push @$ref,$val ;
	    } ;
# Potetial security hole, any user can se p4 user and password	    
#	    ERRLOG("push $res,$val") ; 
	    next ;
	} ;
	
	&P4CGI::bail("Illegal config type $type line $line:\n\"$_\"") ;	
    }
    close F ;

				### Set port and p4 command
    $PORT = $ {$PREF_LIST{"DP"}}[$PREF{"DP"}+3] ;
    if(!defined $PORT) {
	$PORT = $ {$PREF_LIST{"DP"}}[3] ;
	$PREF{"DP"} = 0 ;
    }
    bail("DEPOT NOT DEFINED") unless defined $PORT ;
    $P4_HOSTNAME=$PORT ;
    $P4_HOSTNAME =~ s/:.*$//;
    
    $PORT =~ /(\S+)\s(\S+)\s(\S+)\s(\S+)/ or do { bail("DEPOT line not correct ($PORT)") ; } ;
    $PORT = $1 ;
    $P4 .= " -p $1 -u $2 -c $3 " ;
    if($4 ne "*") {
	$P4 .= "-P $4 " ;
    } ;

# Potential security hole, any user can se the log..
#    push @ERRLOG,"P4 command set to: \"$P4\"" ;
    
				### fix undelines

    $IGNORE_CASE   = $PREF{"IC"} ? "Yes" : "No" ;
    $TAB_SIZE      = $PREF{"TB"} ;
    $TAB_SIZE = 16 if $TAB_SIZE > 16 ;
    $TAB_SIZE = 0 if $TAB_SIZE <= 0 ;
    $MAX_CHANGES   = $PREF{"MX"} ;
    my @t =split(/ /,$ {$PREF_LIST{"ST"}}[3+$PREF{"ST"}]) ;
    $STYLE_SHEET   = $t[0]  ;
    
    push @ERRLOG,"Style sheet file: $STYLE_SHEET" ;

    foreach (keys %ENV) {
	push @ERRLOG,"Environment variable $_: \"". $ENV{$_} . "\"" ;
    } ;

    #
    # Check that we have contact with p4 server
    #
    $lastChange= undef ;
    my $d ;
    p4call(\$d,"changes -m 1") ;
    $d =~ /Change (\d+)/ and do { $lastChange=$1 ;} ;

    # 
    # Get version
    #
    $SERVER_VERSION_YEAR   = 0 ;
    $SERVER_VERSION_NO     = 0 ;
    $SERVER_VERSION_CHANGE = 0 ; 

    my @tmp ;
    p4call(\@tmp,"info") ;
    foreach $d (@tmp) {
	$d =~ /^Server version: (.+?)\/(.+?)\/(\d+)\.(\d+)([^\/]*)\/(\d+)/ and 
	    do { $SERVER_VERSION_YEAR   = $3 ;
		 $SERVER_VERSION_NO     = $4 ;
		 $SERVER_VERSION_QUAL   = $5 ;
		 $SERVER_VERSION_CHANGE = $6 ; }
    } ;
    if($SERVER_VERSION_YEAR == 0) {
	$NO_CONTACT_ERROR = "NO CONTACT WITH SERVER \"$PORT\"" ;
    }
    else {
	if($SERVER_VERSION_YEAR < $REQUIRED_SERVER_VERSION_YEAR) {
	    $NO_CONTACT_ERROR = "P4DB REQUIRES VERSION $REQUIRED_SERVER_VERSION_YEAR.1 OR NEWER" ;
	} ;
    } ;

				### Handle shortcuts

    %SHORTCUTS=$CGI->cookie(-name=>"P4DB_F_$P4_HOSTNAME") ;
    my $addShortCut=$CGI->param(-name=>"ADDSHORTCUT") ;
    if(defined $addShortCut) {
	my ($target,$name) = split(":::",$addShortCut) ;
	$SHORTCUTS{$target} = $name  ;
	$SETCOOKIES = 1 ;
	$CGI->delete("ADDSHORTCUT") ;
	$RELOADTHIS = 1 ;
    } ;
    my $rmShortCut=$CGI->param(-name=>"RMSHORTCUT") ;
    if(defined $rmShortCut) {	
	my ($target,$name) = split(":::",$rmShortCut) ;	
	foreach (keys %SHORTCUTS) {
	    if($SHORTCUTS{$_} eq $name) {
		delete $SHORTCUTS{$_}  ;
		last ;
	    }
	}
	$SETCOOKIES = 1 ;
	$CGI->delete("RMSHORTCUT") ;
	$RELOADTHIS = 1 ;
    };
    my $clearShortCuts=$CGI->param(-name=>"CLRSHORTCUTS") ;
    if(defined $clearShortCuts) {	
	my %empt ;
	%SHORTCUTS = %empt ;
	$SETCOOKIES = 1 ;
 	$CGI->delete("CLRSHORTCUTS") ;
	$RELOADTHIS = 1 ;
   };
       

} ;


#################################################################
### Documentation start

=head1 About

P4CGI - Support for CGIs that interface p4. Written specifically for P4DB

=cut
    ;

################################################################
## Short access functions used to get preferences and such    ##
## in CGIs.                                                   ##
################################################################

=head1 General functions

General functions.

=cut
    ;


sub CURRENT_CHANGE_LEVEL() { return $lastChange ? $lastChange : -1 ; } ;
#sub RESTRICTED()           { return @RESTRICTED_FILES ;    } ;
sub USER_P4PORT()          { return $PORT ;                } ;
sub HELPFILE_PATH()        { return $HELPFILE_PATH ;       } ;
sub REDIRECT_ERROR_TO_NULL_DEVICE() { return $REDIRECT_ERROR_TO_NULL_DEVICE ; } ;
sub REDIRECT_ERROR_TO_STDOUT() { return $REDIRECT_ERROR_TO_STDOUT ; } ;
sub VIEW_WITH_COLORS()     { return $PREF{"VC"} ;          } ;
sub SHOW_FULL_DESC()       { return $PREF{"FD"} ;          } ;
sub HIDE_DELETED()         { return $PREF{"HD"} ;          } ;
sub CURR_DEPOT_NO()        { return $PREF{"DP"} ;          } ;
sub DEPOT_NAME($)          { my $s = ${$PREF_LIST{"DP"}}[$_[0]+3] ; 
			     $s =~ s/^.*;\s+// ;
			     return $s ;} ;
sub NO_OF_DEPOTS()         { return scalar(@{$PREF_LIST{"DP"}}) - 3 ; } ;
sub CHANGES_IN_SEPARATE_WIN() { return $PREF{"NW"} ;      } ;
sub MAX_CHANGES()          { return $MAX_CHANGES ;         } ;
sub ERRLOG                 { push @ERRLOG,@_ ; }; 
sub ERROR                  { &ERRLOG(map { "ERROR: $_" } @_) ; $errors++ ; }; 
sub EXTRAHEADER(% )        { %EXTRAHEADER = @_ ; } ;
sub ONLOADSCRIPT($ )        { $ONLOADSCRIPT= shift @_ ; } ;
sub SET_HELP_TARGET($ )    { $helpTarget = shift @_ ; } ;
sub IGNORE_CASE()          { return $IGNORE_CASE ; } ;
sub UNUSEDCLIWL()          { return $UNUSEDCLIWL ; } ; 
sub UNUSEDUSRWL()          { return $UNUSEDUSRWL ; } ;
sub VERSION()              { return $VERSION ; } ;
sub CHANGELEVEL()          { return $CHANGELEVEL ; } ;
sub SERVER_VERSION()       { return ($SERVER_VERSION_YEAR,
				     $SERVER_VERSION_NO,
				     $SERVER_VERSION_QUAL) ; } ;
sub SHORTCUTS()            { return %SHORTCUTS ; } ;
sub PREF()                 { return %PREF ; } ;
sub PREF_LIST()            { return %PREF_LIST ; } ;
sub SET_COOKIES()          { $SETCOOKIES = 1 ; } ;


###################################################################
###  cgi
###

=head2 cgi

C<&P4CGI::cgi()>

Return CGI reference 

Example:

    my $file = P4CGI::cgi()->param("file") ;
    print "File parameter value: $file\n" ;

=cut
    ;
sub cgi() {
    confess "CGI not initialized" unless defined $CGI ;
    return $CGI ;
}

###################################################################
### Fix some special characters
###

=head2 htmlEncode

C<&P4CGI::htmlEncode(>B<str>C<)>

Convert all '>' to "C<&gt;>", '<' to "C<&lt;>" and '&' to "C<&amp;>".

=over 4

=item str

String to convert

=back

Example:

    my $cvstr = &P4CGI::htmlEncode("String containing <,> and &") ;

=cut ;

sub htmlEncode($ )
{    
    my $d = &rmTabs(shift @_) ;
    $d =~ s/&/&amp;/g ; # & -> &amp;
    $d =~ s/\"/&quot;/g;# " -> &quot;
    $d =~ s/</&lt;/g  ; # < -> &lt;
    $d =~ s/>/&gt;/g  ; # > -> &gt;
    return $d ;
}

###################################################################
### Replace tabs with spaces
###

=head2 rmTabs

C<&P4CGI::rmTabs(>B<str>C<)>

Returns B<str> with all tabs converted to spaces

=over 4

=item I<str>

String to convert

=back

=cut ;

sub rmTabs($ )
{    
    # This algorithm is kind of, well, the first thing I came up
    # with. Should be replaced with a smarter (== more efficient)
    # eventually.......
    my $l = shift @_ ;    
    if($l =~ /\t/) {
	my $tabsz=$TAB_SIZE ;
	$tabsz = 8 unless $tabsz ;
	my $pos = 0 ;
	$l = join('',map
		  { 
		      if($_ ne "\t") {
			  $pos++ ;	
			  $_  ;
		      } else
		      {
			  my $p = $pos % $tabsz ;
			  $pos += $tabsz-$p ;
			  substr("                ",0,$tabsz-$p) ;
		      } ;
		  } split('',$l)) ;
	# For those that wonder what is going on:
	# 1.  Split string to an array (of characters)
	# 2.  For each entry of array, map a function that returns value
	#     for entry or, if value is <TAB>, returns a number of spaces
	#     depending on position in string
	# 3.  Make string (scalar) of array returned from map using join().
	# (Note that the steps appear in the reverse order in the code)
    }
    return $l ;
}

###################################################################
### Set magic buttons
###

=head2 magic

C<&P4CGI::magic(>B<text>C<)>

Returns B<text> with some magic "patterns" substituted with links.

Currently the pattern "change I<number>" (and some variants) is replaced with a link to the
change browser.

Example:

    my $t = "Integrated change 4711 to this codeline" ;

    print &P4CGI::magic($t) ; # inserts a link to change 4711

=cut ;
sub magic($;\@)
{    
    my $t = shift @_ ;
    my %found ;
    my $res = "" ;
    my $hot = 0 ;
    my $max = &P4CGI::CURRENT_CHANGE_LEVEL() ;
    while($t =~ s/^([\s\n]*)(no\.|\.|ch\.|[a-zA-Z-0-9]+|[^a-zA-Z-0-9]+)//i) {
	$res .= $1 ;
	my $tok = $2 ;	
	if($hot == 0) {
	    $hot = 3 if $tok =~ /^(ch[\.]?|change|integrate|submit)/i ;
	}
	else {
	    $hot-- ;
	    if($tok =~ /^\d+$/ and !($t =~ /\.\d+/)) {
		if($tok > 0 and $tok < $max) {
		    $hot = 3 ;
		    $found{$tok} = 1 ;
		    $tok = ahref(-url => "changeView.cgi",
				 "CH=$tok",
				 "HELP=View change $tok",
				 "<b>$tok</b>") ;
		}		
	    }
	    elsif($tok eq ".") {
		$hot = 0 ;
	    }	    
	}
	$res .= $tok ;	
    } ;
    $res .= $t ;

    my $ar ;
    if($ar = shift @_) {
	@$ar = sort { $a <=> $b } keys %found ;
    } ;
    return $res ;
}

###################################################################
### UrlEncode
###

=head2 urlEncode

C<&P4CGI::urlEncode(>B<text>C<)>

Returns B<text> with characters like space substituted with "%<ASCII value>".

Example:

    my $t = "/File with spaces" ;

    print &P4CGI::urlEncode($t) ; # prints: /File%20with%20spaces

=cut
;
sub urlEncode($)
{    
    my $t = shift @_ ;
    $t =~ s/%(?![\da-fA-F][\da-fA-F])/%25/g ;
    $t =~ s/\?/%3f/g ;
    $t =~ s/&/%26/g ;
    $t =~ s/ /%20/g ;
    $t =~ s/;/%3b/g ;
    $t =~ s/\+/%2b/g ;
    $t =~ s/-/%2d/g ;
    $t =~ s/_/%5f/g ;
    $t =~ s/~/%7e/g ;
    return $t ;
}

###################################################################
### UrlEncode
###

=head2 urlDecode

C<&P4CGI::urlDecode(>B<text>C<)>

Reverses the operation of C<urlEncode>. See above.

=cut
;
sub urlDecode($)
{    
    my $t = shift @_ ;  
    my $r = "" ;
    while($t =~ /(.*)\%(..)(.*)/) {
	my ($start,$code,$end) = ($1,$2,$3) ;
	$r .= $start ;
	$t = $end ;
	if($code eq "25") {
	    $r .= "%" ;
	}
	else {
	    if($code =~ /[\da-fA-F][\da-fA-F]/) {
		$r .= char("0x$code") ;
	    }
	    else {
		$r .= "%$code" ;
	    }
	}
    }
    return  $r . $t ;
}

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


=head1 Functions for p4 access

    These fuctions used the "p4" command to access the depot.

=cut

###################################################################
###  p4call
###

=head2 p4call

C<&P4CGI::p4call(>B<result>C<,>B<command>C<)>

Request data from p4. Calls p4 with command B<command> and returns data in B<result>.

This function is really three different functions depeding in the type of the
B<result> parameter.

=over 4

=item result

This parameter can be of three different types:

=over 4

=item Filehandle (typeglob)

Data from command can be read from filehandle. NOTE! File must be closed by
caller.

=item Reference to array

Returns result from command into array (newlines stripped)

=item Reference to scalar

Returns result from command into scalar. (lines separated by newline)

=back

Any other type of parameter will abort operation

=item command

Command to send to p4 command line client.

=back

Example:

    my $d ;
    &P4CGI::p4call(\$d,"changes -m 1") ;
    $d =~ /Change (\d+)/ or &bail("No contact with P4 server") ;
    $lastChange=$1 ;    

=cut
    ;

sub p4call {
    my ( $par, @command ) = @_; 
    my $partype = ref $par ;
    push @ERRLOG,"p4call(<$partype>,@command)" ;
    if(!$partype) {
	open( $par, "$P4 @command|" ) || &bail( "$P4 @command failed" );
	return ;
    }  ;
    "ARRAY" eq $partype and do {
	local *P4 ;
	@$par = () ;
	open( P4, "$P4 @command|" ) || &bail( "$P4 @command failed" );
	while(<P4>) {
	    chomp ;
	    push @$par,$_ ;
	} ;
	close P4 ;
	return ;
    } ;
    "SCALAR" eq $partype and do {
	$$par = "" ;
	local *P4 ;
	open( P4, "$P4 @command|" ) || &bail( "$P4 @command failed" );
	while(<P4>) {
	    $$par .= $_ ;
	} ;
	close P4 ;	
	return ;
    } ;
    die("Called with illegal parameter ref: $partype") ;
} ;

###################################################################
###  p4readform
###

=head2 p4readform

C<&P4CGI::p4readform(>B<command>,B<resulthash>C<)>

Reads output from a P4 command and assumes the data is a form (e.g. "client -o").

The form is stored in a hash and the function returns an array containing all
field names in the order they appeared. The hash will contain the field names as
key and field values as data.

=over 4

=item command

Command to send to p4 command line client.

=item resulthash

Reference to a hash to receive reults

=back

Example:

    my %fields ;
    my @fields = &P4CGI::p4readforml("client -o",\%fields) ;
    my $f ;
    foreach $f (@fields) {
        print "field $f: $fields{$f}\n" ;
    }

=cut
    ;

sub p4readform($\% )
{
    my $cmd = shift @_ ;
    my $href = shift @_ ;
    my @result ;
				# clear hash
    %$href = () ;

    local *F ;
    p4call(*F,$cmd) ;
    my $cline = <F>;
    while($cline) {
	chomp $cline ;
	$_ = $cline ;
	if(/^\#/ or /^\s*$/) {	# skip comments and empty line
	    $cline = <F>;
	    next ;
	}
	if(/^(\S+):\s*(.*)\s*$/) {
	    my $fld=$1 ;
	    my $val=$2 ;
	    push @result,$fld ;
	    my $ws ;
	    if($val eq "") {
		$val = undef ;
		while(defined ($cline = <F>)) {
		    $_ = $cline ;
		    chomp ;
		    last if /^\w/ ;
		    s/^\s+// ;
		    if(defined $val) {
			$val .= "\n$_" ;
		    }
		    else {
			$val = "$_" ;
		    }
		}
	    }
	    else {
		$cline = <F>;
	    }
	    $$href{$fld}=$val ;
	}
	else {
	    $cline = <F>;
	}
    }
    close *F ;
    return @result ;
} ;

################################################################
################################################################
################ Page header, footer and common ################
################ routines.                      ################
################################################################
################################################################

################################################################
### page_header
###

=head2 page_header

C<&P4CGI::page_header()>

Print header of page. Called by start_page().

=cut ;

sub page_header()
{
    
    
}


###################################################################
###  start_page
###

=head2 start_page

C<&P4CGI::start_page(>B<title>[C<,>B<legend>]C<)>

Start a page. Print http header and first part of HTML.

=over 4

=item title

Title of page. If the text contains " -- " it is removed and all text 
is used for the page title but only the text before " -- " is printed 
in the page header.

=item legend (Optional)

Short help text to be displayed at top of page

=back

Example:

 my @buttons ;
 push @button,P4CGI::buttonCell("http://www.perforce.com",
				"Tooltip help text",
				"Go To Perforce") ;

 my $start = P4CGI::start_page("Title of page",
			       @buttons) ;
 print $start ;

=cut ;

# Common to start_page and start_page_header
sub start_page_common($)
{
    my $addcookies = shift @_ ;
    my @cookiepar ;
    if($addcookies or $SETCOOKIES) {
				# Set up cookies and print header
	my @cookieArray ;
	push @cookieArray,$CGI->cookie(-name=>"P4DB_PREFERENCES",
				       -value=>\%PREF,
				       -path=>"/",
				       -expires=>'+6M');
	push @cookieArray,$CGI->cookie(-name=>"P4DB_F_$P4_HOSTNAME",
				       -value=>\%SHORTCUTS,
				       -path=>"/",
				       -expires=>'+6M');
	push @cookiepar,"-cookie",\@cookieArray
    } ;
    return $CGI->header(@cookiepar,
			%EXTRAHEADER). "\n"  ;
} ;

sub start_page($;@)
{
#    print STDERR "Start_page()\n" ;

    my $ret = &start_page_common(0) ;

    my $title  = shift @_ ;
    my @buttons = @_ ;
    my $buttons ;

    my $helpURL="${HELPFILE_PATH}/P4DB_Help.html" ;
    if(defined $helpTarget) {
	$helpURL .= "#$helpTarget" ;
    } ;

#    if($CGI->url(-relative=>1) ne "index.cgi") {
#	unshift @buttons,buttonCell("index.cgi","Go to main page","Main") ;
#    } ;
    #push @buttons, buttonCell($helpURL,"About page","About") ;
    if(@buttons) {
	$buttons = buttonHMenuTable(@buttons) ;
    }
    my $n = 0 ;
    
    
    my $p4help = "" ;
    $p4help = join("\n",("<table><tr>",
			 "  <td class=\"Button\">",
			 ahref(-url=>$helpURL,
			       -class=>"Help",
			       "Help"),
			 "  </td></tr>",
			 "</table>\n")) ;

    my $shdata ="";
    foreach (keys %SHORTCUTS) {
	if(length $shdata) { $shdata .= "&&&" ; } 
	$shdata .= "$_===$SHORTCUTS{$_}" ;
    }

    $ONLOADSCRIPT = "" unless defined $ONLOADSCRIPT ;
    $ONLOADSCRIPT .= "reloadHeaderPage() ; " ;
    if($RELOADTHIS) {
	$ONLOADSCRIPT .= " setDataFrame('" . $CGI->self_url ."');" ;
    }

    my $t = "$title" ;		# Take title and removed all HTML tags 
    $t =~ s/<br>/ /ig ;
    $t =~ s/<[^>]*>//g ;
    $t =~ s/ -- / /;
    $title =~ s/ -- .*$/ / ;
    my %header ;
    $header{"-title"}  = "P4DB: $t" ;
    $header{"-author"} = "fredric\@mydata.se" ;
    $header{"-style"} = { -src=>$STYLE_SHEET } ;
    $header{"-class"} = "DataFrame" ;
    $header{"-head"} = CGI::meta({"-http-equiv" => 'Content-Script-Type',
				  "-content" => 'text/javascript' }) ;
    $header{"-onload"} = $ONLOADSCRIPT if $ONLOADSCRIPT ;
    $header{"-script"} = { -src=>"P4DB.js" , -language=>"JavaScript" } ;
    $ret .=  $CGI->start_html(%header) ;
    my $myurl=$CGI->self_url ;
    $myurl=urlEncode($myurl) ;
    $ret .= 
	"<p class=\"selflink\">".
	"<a target=\"_top\" ".
	"   href=\"index.cgi?STARTPAGE=$myurl\"".
	"   class=\"selflink\"".
	"   title=\"This link can be copied as reference to this page\">".
	"[link to this]</a></p>" ;
	;
    if(defined $NO_CONTACT_ERROR) {
	$ret .= "<p class=\"Error\">ERROR: $NO_CONTACT_ERROR</p>" ;
    }
    
    my $headerTable = start_table("class=\"Header\"") ;
    my $prHeaderTable = 0 ;
    if($title ne "") {
	$headerTable .= table_row({-class  => "Header",
				   -text   => "$title"}) ;
	$prHeaderTable++ ;
    }
    if(defined $buttons) {
	$headerTable .= table_row($buttons) ;
	$prHeaderTable++ ;
    } ;
    $headerTable .= end_table() ;
    if($prHeaderTable) { $ret .= $headerTable ; } ;
    $pageStartPrinted = 1 ;
    return $ret . "\n" ;    
} ;

sub start_page_header()
{
    my $ret = &start_page_common(0) ;
    
    if($RELOADTHIS) {
	$ONLOADSCRIPT .= " setHeaderFrame('" . $CGI->self_url ."');" ;
    }
    else {
	$ONLOADSCRIPT .= "setPageSel() ; setShortCutSel() ; " ;
    } ;

    my %header ;		# Fill in header fields
    $header{"-title"}  = "P4DB 3.0" ;
    $header{"-author"} = "fredric\@mydata.se" ;
    $header{"-style"} = { -src=>$STYLE_SHEET } ;
    $header{"-class"} = "HeaderFrame" ;
    $header{"-head"} = CGI::meta({"-http-equiv" => 'Content-Script-Type',
				  "-content" => 'text/javascript' }) ;
    $header{"-onload"} = $ONLOADSCRIPT if $ONLOADSCRIPT ;
    $header{"-script"} = { -src=>"P4DB.js" , -language=>"JavaScript" } ;
    $ret .=  $CGI->start_html(%header) ;
  
} ;
    
###################################################################
###  end_page
###

=head2 end_page

C<&P4CGI::end_page()>

End a page. Print HTML trailer.

Example:

 print P4CGI::end_page() ;

=cut ;

sub end_page()
{
    my $err = "" ;
    if($PREF{"DBG"}) {
	$err = &prerrlog() ;
    } ;
    return  $err .  $CGI->end_html() ;
} ;

sub end_page_header()
{
    return  $CGI->end_html() ;
} ;

###################################################################
### bail
###

=head2 bail

C<&P4CGI::bail(>B<message>C<)>

Report an error. This routine will emit HTML code for an error message, print the error log  and exit.

This rouine is intended to report internal errors in the code (much like assert(3) in c). 

=over 4

=item message
Message that will be displayed to user

=back

Example:

 unless(defined $must_be_defined) { 
     &P4CGI::bail("was not defined") ; 
 } ;

=cut ;

my $bailed ;

sub bail(@) {
    unless(defined $bailed) {
	$bailed = 1 ;
	my $message = shift @_ ;
	my $text = shift @_ ;    
	unless(defined $pageStartPrinted) {
	    print 
		"",
		$CGI->header(),
		$CGI->start_html(-title   => "Error in script",
				 -bgcolor => "white");
	    $pageStartPrinted = 1 ;
	} ;    
	$message = &htmlEncode($message) ;
	print 
	    "<br><hr color=red><p align=center><font color=red size=+2>An error has occurred<br>Sorry!</font><p><font color=red>Message:<BR><pre>$message</pre><br>" ;
	if(defined $text) { 
	    $text = &htmlEncode($text) ;
	    print "<pre>$text</pre><br>\n" ; 
	} ;
	print
	    "<p>Parameters to script:<br>",
	    $CGI->dump() ;
	print "</font>\n",prerrlog(), end_page() ;	
	die($message) ;
    }
} ;

###################################################################
### signalError
###

=head2 signalError

C<&P4CGI::signalError(>B<message>C<)>

Report an operator error in a reasonable fashion.
SignalError can be called before or after start_page() but if it is called 
before start_page() a "default" page header will appear. It is recommended 
to call signalError() after start_page() to make it more obvious to the
operator what the problem was.

=over 4

=item message
Message that will be displayed to user

=back

Example:

 unless(defined $must_be_defined) { 
     &P4CGI::signalError("was not defined") ; 
 } ;

=cut ;

sub signalError {
    my $message = shift @_ ;
    my $text = shift @_ ;    
    unless(defined $pageStartPrinted) {
	print "",start_page("Error","") ;
	$pageStartPrinted = 1 ;
    } ;    
    $message = &htmlEncode($message) ;
    print 
	"<p align=center><font color=red size=+2>$message</font><br><br>" ;
    if(defined $text) { 
	$text = &htmlEncode($text) ;
	print "<pre>$text</pre><br>\n" ; 
    } ;
    print "", end_page() ;	
    exit 0 ;
} ;

###################################################################
### help_link
###  
sub help_link($ ) {
    my $helpURL="$HELPFILE_PATH/P4DB_Help.html#" . shift @_ ; ;
    return ahref(-url=>$helpURL,
		 "<font size=+2 style=fixed><B>?</B></font>") ;
}


###################################################################
### start_table
###  

=head2 start_table

C<&P4CGI::start_table(>B<table_attribute_text>C<)>

Start a table with optional table attributes

=over 4

=item table_attribute_text

This text will be inserted as attributes to table tag

=back

Example:

    print P4CGI::start_table("align=center border") ;

=cut ;
sub start_table
{
    my $attribs = shift @_ ;
    my $ret = "<table " ;
    if($attribs) {
	$ret .=  " $attribs" ;
    }
    return $ret . ">\n";
}

###################################################################
### end_table
###  

=head2 end_table

C<&P4CGI::end_table()>

Return end of table string. (trivial function included mostly for symmetry)

=cut ;

sub end_table() 
{
    return "</table>\n" ;
}

###################################################################
### table_row
###

=head2 table_row

C<&P4CGI::table_row(>B<options>C<,>B<listOfValues>C<)>

Insert a row in table.

=over 4

=item options

A list of key/value pairs (a hash will do just fine) containing options for 
the row. 

The key must start with a "-".

Most key/value pairs are treated as attributes to the <TR>-tag.
The following keys are recognized as special:

=over 4

=item C<-type>

Type of cells. Default is <TD>-type. 

=item C<->I<anykey>

I<anykey> will be assumed to be a row option and will be inserted in the TR-tag. 
The value for the option is the key value, unless value is empty or undefined, in which
case the option anykey is assumed to have no value.

=back

=item listOfValues

Row data. Remaining values are assumed to be data for each cell. 
The data is typically the text in the cell but can also be:

=over 4

=item undef

An undefined value indicates that the next cell spans more than one column. 

=item Reference to a hash

The hash contains two keys: "-text" for cell text and "-type" for cell type.
All other key/value pairs are treated as attributes to the <TD> or <TH> tag.

=back

=back

Example:

 print P4CGI::start_table("align=center") ;
                                   ### print header row
 print P4CGI::table_row(-type   => "th",
			-valign => "top",
			-align  => "left",
                        "Heading 1","Heading 2",undef,"Heading 3") ;
                                   ### print data
 my %h = (-text    => "text in hash", 
	  -bgcolor => "blue") ;
 print P4CGI::table_row(-valign  => "top",
			-bgcolor => "white",
                        "Cell 1",
			{-text    => "Cell 2",
			 -bgcolor => "red"},
			\%h,
			"Cell 3-2") ;
 print P4CGI::end_table() ;

=cut ;

sub table_row 
{   
    confess ("P4CGI::table_row() Parameters required!") if @_ == 0 ;
    my @ret ;
    my $n = 0 ;
    my $ec = 0 ;
    my $option = shift @_ ;
    my %options ;
    while(defined $option and ($option =~ s/^-//)) {	
	confess ("P4CGI::table_row() Option value required!") if @_ == 0 ;
	$options{lc($option)} = shift @_ ;
	$option = shift @_ ;
    }
    unshift @_,$option ;
    
    my $type      = "td" ;
    $type = $options{"type"} if defined $options{"type"} ;
    delete $options{"type"} ;

    push @ret,"<tr" ;
    my $attr ;
    foreach $attr (keys %options) {
	push @ret," $attr" ;
	if($options{$attr}) {
	    push @ret,"=$options{$attr}" ;
	}
    }
    push @ret,">\n" ;

    my $colspan = 0 ;
    my $cell ;
    foreach $cell (@_) {
	$colspan++ ;
	if(defined $cell) {
	    my $COLSPAN="colspan=$colspan" ;
	    $colspan=0 ;
	    if(ref $cell) {
		my $reftyp = ref $cell ;
		"HASH" eq $reftyp and do { my $txt = $$cell{"-text"} ;
					   confess "P4CGI::table_row() Missing text argument" 
					       unless defined $txt ;
					   delete $$cell{"-text"} ;
					   my $tp = $type ;
					   $tp = $$cell{"-type"} if defined 
					       $$cell{"-type"} ;
					   delete $$cell{"-type"} ;
					   push @ret,"<$tp $COLSPAN" ;
					   my $attr ;
					   foreach $attr (keys %$cell) {
					       ($a = $attr) =~ s/^-// ;
					       push @ret," $a=$$cell{$attr}" ;
					   }
					   push @ret,">$txt</$tp>\n" ;
					   next ; } ;
		confess "Illegal cell data type \"$reftyp\"" ;
	    } 
	    else {
		push @ret,"<$type $COLSPAN>$cell</$type>\n" ;
	    }
	}
    }
    push @ret,"</tr>\n" ;
    return join("",@ret) ;
}

###################################################################
### table_header
###

=head2 table_header

C<&P4CGI::table_header(>B<list of label/hint>C<)>

Create a table header row with a a description and an optional hint for each column. 

=over 4

=item list of label/hint

A list of column labels optionally followed by a '/' and a hint.

=back

Example:

 print P4CGI::start_table("align=center") ;
                                   ### print header row
 print P4CGI::table_header("File/click for story","Revision/click to view") ;
                                   ### print data
 my %h = (-text    => "text in hash", 
	  -bgcolor => "blue") ;
 print P4CGI::table_row(-valign  => "top",
			-bgcolor => "white",
                        "Cell 1",
			{-text    => "Cell 2",
			 -bgcolor => "red"},
			\%h,
			"Cell 3-2") ;
 print P4CGI::end_table() ;

=cut ;

sub table_header
{   
    my @cols ;
    my @tmp = @_ ;
    my $tmp ;
    my $n ;
    while(@tmp > 0) {	
	$tmp = shift @tmp ;
	if(defined $tmp) {
	    my $label = $tmp ;
	    push @cols,{ -text => $label,
			 -class => "ListHeader" } ;
	}
	else {
	    push @cols,$tmp ;
	}
    }
    return table_row(-class => "ListHeader", @cols) ;
} ;


################################################################
### Make a framed table with a title
###

sub start_framedTable($ ) 
{
    my $title = shift @_ ;
    return   
	"<span class=\"FrameTitle\">$title</span>\n" .
	"<table class=\"Frame\"><tr><td>\n" ;
}

sub end_framedTable() 
{
    return "</td></tr></table>\n" ;
}

sub framedTable($$ ) 
{
    my $title = shift @_ ;
    my $contents = shift @_ ;
    return join("\n",(&start_framedTable($title),
		      $contents,
		      &end_framedTable())) ;
}

###################################################################
### Make a list
###

=head2 ul_list

C<&P4CGI::ul_list(>B<list>C<)>

Return a bulleted list.

=over 4

=item I<list>

Lits of data to print as bulleted list

=back

Example:

 print P4CGI::ul_list("This","is","a","bulleted","list") ;

=cut ;

sub ul_list(@ ) 
{
    my @ret ;
    if($_[0] eq "-title") {
	shift @_ ;
	push @ret, shift @_ ;
    }
    push @ret,"<ul>\n" ;
    my $a ;
    foreach $a (@_) {
	push @ret,"<li>$a\n" ;
    }
    push @ret,"</ul>\n" ;
    return join("",@ret) ;
}

###################################################################
### Make a dl list
###

=head2 dl_list

C<&P4CGI::dl_list(>B<list_of_pairs>C<)>

Returns a definition list.

=over 4

=item list_of_pairs

List of data pairs to print as a definition list. 
A hash will do just fine, only you have no control over the order in the list. 

=back

Example:

 print P4CGI::dl_list("This","Description of this",
                      "That","Description of that") ;

=cut ;

sub dl_list
{
    my @ret ;
    if($_[0] eq "-title") {
	shift @_ ;
	push @ret,shift @_ ;  
    }
    if($_[0] eq "-compact") {
	push @ret,"<dl compact>\n" ;
	shift @_ ;
    } 
    else {
	push @ret,"<dl>\n" ;
    }
    while(@_ > 1) {
	push @ret,"<dt>",shift @_,"<dd>",shift @_,"\n" ;
    }
    push @ret,"</dl>\n" ;
    return join("",@ret) ;
}

###################################################################
### Create a href tag
###

=head2 ahref

C<&P4CGI::ahref(>B<options>C<,>B<parameters>C<,>B<text>C<)>

Returns a <A HREF...>...</A> tag pair.

=over 4

=item I<options>

Optional list of option-value pairs. Valid options are:

=over 4

=item C<-url>

Url for link. Default is current.

=item C<-anchor>

Anchor in url. Default is none.

=back

Any non-valid option marks the end of the options

=item I<parameters>

Optional list of parameters for link.

=item I<text>

The last parameter is used as text for link. 

If the next to the last parameter has the format: C<"HELP=Help text"> the
help text is displayed as a tooltip.

=back

Example:

    print &P4CGI::ahref("Back to myself") ; # link to this. No parameters.

    print &P4CGI::ahref("-url","www.perforce.com",
			"To perforce") ; # link to perforce

    print &P4CGI::ahref("-anchor","THERE",
			"Go there") ; # link to anchor THERE

    print &P4CGI::ahref("-url","changeList.cgi",
			"FSPC=//.../doc/...",
			"HELP=Click here",               # a tooltop help text
			"Changes for all documentation") ; # url with parameter

=cut ;

sub ahref
{    
    my $args=@_ ;
    
    my @tmp = @_ ;
    my $url = $ENV{SCRIPT_NAME} ;
    my $anchor = "" ;
    my $pars   = "" ;
    my $params = "" ;
    while($tmp[0] =~ /^-/) {
	$tmp[0] =~ /^-url$/i and do {
	    shift @tmp ;
	    $url = shift(@tmp) ;
	    next ;
	} ;
	$tmp[0] =~ /^-anchor$/i and do {
	    shift @tmp ;
	    $anchor = "#" . shift @tmp ;
	    next ;
	} ;
	$tmp[0] =~ /^-(.*)/ and do {
	    my $p = $1 ;
	    shift @tmp ;
	    my $v = shift @tmp ;
	    $params .= " $p=$v" ;
	    next ;
	} ;
	last ;
    }
    my $tooltips="" ;
    while(@tmp > 1) {
	if($tmp[0] =~ /HELP=(.*)/) {
	    $tooltips=" title=\"$1\"" ;
	    shift @tmp ;
	    next ;
	}
	if(length($pars) > 0) {
	    $pars .= "&" ;
	}
	else {
	    $pars = "?" ;
	} ;
	$pars .= urlEncode(shift @tmp) ;
    } ;
    my $txt = shift @tmp ;
    $pars =~ s/ /\+/g ;
    return "<a href=\"${url}${pars}${anchor}\"$params$tooltips>$txt</a>" ;
}

###################################################################
### ButtonCell
### Create a "button"-like link in a table cell
###
sub buttonCell($$@)
{
    my $url  = shift @_ ;
    my $help = shift @_ ;
    my @text = @_ ;
    return 
	"<td class=\"Button\">" . &P4CGI::ahref(-url =>$url,
						-class =>"Button",
						"HELP=$help",
						@text) . "</td>\n" ;
}

###################################################################
### buttonVMenuTable
### Create a vertical menu of "buttons".
###
sub buttonVMenuTable(@)
{
    my $r = "<table cellspacing=\"3\" cellpadding=\"0\" class=\"Button\">\n" ;
    while(@_) {
	my $m = shift @_ ;
	$r .= "<tr>\n    " . $m ."</tr>\n" ;
    }
    return $r . "</table>\n" ;
}

###################################################################
### buttonHMenuTable
### Create a horizontal menu of "buttons".
###
sub buttonHMenuTable(@)
{
    return
	"<table cellspacing=\"3\" cellpadding=\"0\" class=\"Button\">\n".
	"<tr>\n    " .
	join('    ',@_) .
	"</tr>\n" .
	"</table>\n" ;
}

###################################################################
### splitLine
### Split a line containing html tags with <br>-tags
###
sub splitLine($$)
{
    my $line = shift @_ ;
    my $maxlen  = shift @_ ;
    my @line = split(/(<[^>]+>)/,$line) ; 
    $line = "" ;
    my $pos = 0 ;
    while(@line) {
	my $l = shift @line ;
	my $txt ="" ;
	my $len = $maxlen-$pos ;
	while(length($l) > $len and
	      $l =~ s/(.{1,$len}\S*)\s*(.*)/$2/) {
	    $txt .= $1  ;
	    $txt .= "<br>\n" if length($l) ;
	    $pos = 0 ;
	}	
	$pos += length($l) ;
	$txt .= $l ;
	$txt =~ s/ /&nbsp;/g ;
	$line .= $txt ;
	last if @line == 0 ;
	$line .= shift @line ;
    }
    return $line ;
}

###################################################################
### formatDescription
### Format a description text and insert it into a cell
###
sub formatDescription($;\@)
{
    my $desc = shift @_ ;
    my @tmp ;
    my $refref ;
    $refref = shift @_ or $refref = \@tmp ;
    $desc = &htmlEncode($desc) ;
    $desc = &P4CGI::magic($desc,$refref) ;
    my @desc = map { my $d = splitLine($_,78) ;
		     $d ;
		 } split("\n",$desc) ;
    return join("<br>\n",@desc) ;
}

sub BEGIN ()
{
    init() ;
} ;

1;

# Change User Description Committed
#38 4998 Fredric Fredricson P4DB: cleaned up some code.
Added p4users(), p4client() and p4user2name() to P4CGI.pm and modified
all cgi:s to use these,
#37 4990 Fredric Fredricson P4DB: Improved error handling:
#36 4989 Fredric Fredricson P4DB: Some small fixed in documentation and make shortcut icon
configurable
#35 4973 Fredric Fredricson P4DB: Worked around some IE CSS-bugs.   
Improved page layout for branch, client and job lists.
#34 4965 Fredric Fredricson P4BD: Made P4DB 4,0 ready for "official" release
* removed Beta status flag
* added an .ico file
* some minor cosmetic changes
#33 4883 Fredric Fredricson P4DB:
* Added a warning in intro page for untested versions
* Some CSS changes
* Updated info in README file and on intro page
#32 4872 Fredric Fredricson P4DB: Added info about current user to page footer
#31 4859 Fredric Fredricson P4DB: Fixed minor security issue (user password displayed on screen
if page immediately after login received baf parameters)
#30 4855 Fredric Fredricson P4DB: Opps!
A small bug managed to sneak in....
#29 4854 Fredric Fredricson P4DB: Added Cache-Control directive to http header to tell IE6 to
reload page each time it is loaded (IE6 will, by default, cache all
pages and this is not what the user expects).
#28 4844 Fredric Fredricson P4DB: A few fixes:
Added new config option that controls login page
Added two new CSS-files (for large fonts and smaller fonts)
Fixed sort order on users in login page
Stepped up to version 4.0
#27 4835 Fredric Fredricson P4DB: Fixed problem with depot statistics (now much better html
and CSS (but not 100% yet..)
#26 4834 Fredric Fredricson P4DB: Incremented version to 4.0beta1
#25 4832 Fredric Fredricson P4DB:
* Added login procedure (using tickes)
* Requires 2004.2
* Minor cosmetic upgrade
#24 4313 Fredric Fredricson P4DB:
- Removed some error messages (that clobbers the error log for the web server)
- Added a CSS file that works for people with color vision deficiency
- Fixed minor bug in the scripts that creates the tarball and zip-file
#23 4306 Fredric Fredricson P4DB: Hardened P4DB against malicious parameters (cross site scripting),
performed some cleanup and increased version to 3.1.1.
#22 4303 Fredric Fredricson P4DB: Cosmetic changes (formatted code)
#21 4302 Fredric Fredricson P4DB: Fixed problem with line splitting in wide change descriptions
#20 4237 Fredric Fredricson P4DB: Maybe the final submit for P4DB 3.1.0
#19 4216 Fredric Fredricson P4DB: Another partial submit on my way to P4DB 3.1...
#18 4176 Fredric Fredricson P4DB: Still working my way to P4DB 3.1...
#17 4152 Fredric Fredricson P4DB: Some more work on tha way to version 3.1....
#16 4069 Fredric Fredricson P4DB: More changes on the way to 3.1
#15 4048 Fredric Fredricson P4DB: Updated for Explorer.
* Updated Style Sheet to work for Explorer as well as Netscape
* Improved alternate header
* Some other small fixes....
#14 4046 Fredric Fredricson P4DB: First submit for 3.1.
* Removed frame-stuff and some related files
* Added new page header
* Started update of documentation
* Changed a lot of CGI:s to conform to new "look and feel"
Still a lot to do:
- clean up stuff (especially the javascript)
- Fix the file list to use new annotate-command
- Clean up and document css-file
- and more.......
#13 3574 Fredric Fredricson P4DB: stepped up version to 3.0beta2
#12 3573 Fredric Fredricson P4DB: Added a link to page that can be used in emails etc.
#11 2990 Fredric Fredricson P4DB: Added depot change level to version in intro page
#10 2923 Fredric Fredricson P4DB: Changed vesion to 3.0beta1
#9 2904 Fredric Fredricson P4DB: Moved server info from page footer to intro-page
#8 2875 Fredric Fredricson P4DB 3.0 first beta...
#7 1931 Fredric Fredricson P4DB: Small user interface improvement,
#6 1929 Fredric Fredricson P4DB: Minor user interface improvement.
Real minor.
#5 1920 Fredric Fredricson P4DB: Mainly some user interface fixes:
* Added a small arrow that points to selection in list of options
* Added tooltip help
* Added user prefereces to turn the above off (or on)
* Some other user interface fixes
And fixed a bug in jobList.cgi and some minor bugs in label and branch
viewers.
#4 1647 Fredric Fredricson P4DB: Made it possible to select "hide deleted" or "show deleted" as
default in depot tree browser.
#3 1646 Fredric Fredricson P4DB: file log can now show full descriptions.
Added a new "preference" that makes the full descriptions default or
not.
#2 1645 Fredric Fredricson P4DB: Cosmetic change to table headers (valign top by default)
#1 1638 Fredric Fredricson P4DB: Added all (I think) files for P4DB