#!/usr/bin/perl -w
##############################################################################
#                                                                            #
#                          Dynamo                                            #
#                          The  World   is   a  List                         #
#                                                                            #
#                          Content Management                                #
#                          <]{MJ}[>  m.jaggi@gmx.net                         #
#                          http://www.m8j.net/dynamo                         #
#                                                                            #
                           my $version = "2.78";
#                                                                            #
##############################################################################

# Configuration
our ($language, $sysname, $basedir, $datadir, $baseurl, $dataurl, $script, $buttons, @extensions, $dateformat, $shortdateformat, $rsstimezone, $mailprog, $filterspam, $do_backup, $number_of_backups_to_keep, $backup_text_only, $wysiwyg_code);

eval { require "dynamo-config.pl" };

#############################################################
my %lang;
SetLanguage();

if ($@) { Error("$lang{'couldnotloadconfig'}<br/> $@") }

my $debugMode = 'off';
				
									# the buttons
my %button = (	submit =>			"$buttons/$language/submit.gif",
				logout =>			"$buttons/$language/logout.gif",
				edit =>				"$buttons/$language/edit.gif",
				deleteselected =>	"$buttons/$language/deleteselected.gif",
				insert =>			"$buttons/$language/insert.gif",
				insertv =>			"$buttons/$language/insert-v.gif",
				insertn =>			"$buttons/$language/insert-n.gif",
				addit =>			"$buttons/$language/addit.gif",
				save =>				"$buttons/$language/save.gif",
				left =>				"$buttons/$language/left.gif",
				right =>			"$buttons/$language/right.gif",
				file =>				"$buttons/$language/file.gif",
				wait =>				"$buttons/$language/wait.gif",
			  ); 

my $do = 3;							# database formating: data offset = number of lines before the first data line

#use strict;
use CGI;
$CGI::POST_MAX = 100 *1024 *1024; 	# maximum upload size 100 mB
use CGI::Carp qw(fatalsToBrowser);
use File::Copy;

									# global vars
my %DB=(); my %Form=(); my ($status, $listname, $bottom, @keys, @signs, @rows, @monthnames, @weekdaynames); my $HaveImageMagick = '?';

my $cgi = new CGI;

my @Paramnames = $cgi->param();
foreach my $key (@Paramnames) {
	$Form{$key} = $cgi->param($key);
} 
my $pwd = crypt($Form{'password'},"hallo");

#############################################################
# read template
	if (index("$basedir/$Form{'template'}",$datadir) >= 0) { Error("$lang{'sectemplates1'} $datadir/ <br/>$lang{'sectemplates2'} $basedir/.<br/><br/>$lang{'sectemplates3'}"); }
	if (-f "$basedir/$Form{'template'}") {
		open(DATAF, "<$basedir/$Form{'template'}"); @rows = <DATAF>; close(DATAF);
	} elsif ($Form{'template'}) {
		Debug("Template &quot;$Form{'template'}&quot; $lang{'couldnotbefoundin1'} $basedir/ $lang{'couldnotbefoundin2'}<small><br/><br/>($!)</small>");
	}
	my $template = join("",@rows);
	
	if ($Form{'list'}) { my $ln=$Form{'list'}; my $lno="$ln\/"; $lno=~s/([0-9]+)\//\//g; chop($lno); $template =~ s/$lno/$ln/g; } # autotemplate for lists of lists
	
	$Form{'thanks'} =~ s/\*/\&/g;

############################################################# admin
if ($Form{'action'} 													|| $Form{'command'} eq 'admin') {
	
	$listname = $Form{'list'};
	LoadDatabase($listname);
	
	############################ authorisation
	my $allowed = (HasPassword($cgi->remote_host(), $DB{$listname}{options}{adminpwd}) || $pwd eq $DB{$listname}{options}{adminpwd}) && !$Form{'key'};
	
	if ($allowed || (($Form{'action'} eq 'addform' || $Form{'action'} eq 'addit') && !$DB{$listname}{options}{allauthorize}) ) {
	
		############# authorize
		if ($allowed) {
				AddPassword($cgi->remote_host(), $DB{$listname}{options}{adminpwd});
				AddPassword($cgi->remote_host(), $DB{$listname}{options}{viewpwd});
		}
		############# user is authorized to admin, now do action
	
		############################ logout
		if ($Form{'action'} eq 'logout') {
				Logout($cgi->remote_host());
				RedirectTo("$script?template=$Form{'template'}");
		
		############################ addform or editform
		} elsif ($Form{'action'} eq 'addform' || $Form{'action'} eq 'editform') {
		
				AddOrEditForm( $listname, $Form{'position'} );
		
		############################ addit or saveit
		} elsif ($Form{'action'} eq 'addit' || $Form{'action'} eq 'saveit') {
				
				# spam blocker
				my $spam = ''; if ($filterspam eq 'yes' && !$allowed) { foreach my $key (keys(%Form)) { $spam = 'true' if ($Form{$key} =~ "http://|https://" && $key !~"thanks|template"); } $spam = '' if ($filterspam ne 'yes'); Error($lang{'nospam'}) if ($spam); }
				# captcha (separate option for each list)
				if (!$allowed && $DB{$listname}{options}{captcha} && $Form{'captcha'} !~ /18564|25073/) { Error($lang{'wrongcaptcha'}); }
				
				if ($Form{'action'} eq 'addit') { $DB{$listname}{d}{numentries} ++; }
				my $pos = $Form{'position'}; if ($pos < 1) { $pos = 1; } if ($pos > $DB{$listname}{d}{numentries}) { $pos = $DB{$listname}{d}{numentries}; }
				
				if ($allowed || !$spam) {
					AddItOrSaveIt( $listname, $pos );
				}
				
				if ($allowed) {
					RedirectTo("$script?action=admin&list=$listname&template=$Form{'template'}");
				} else {
													# send confirmation email to customer
					my $id = $DB{$listname}{$pos}{'id'}; my $key = cryptId($id);
					my $email = ""; my $confirm = 0;
					while (my ($k, $field) = each(%{ $DB{$listname}{fields} })) {
						my $fieldtype = $DB{$listname}{fieldtypes}{$k};
						if (($fieldtype eq 'email confirm' || lc($field) =~ /^(e-|e)mail$/) && $Form{'action'} eq 'addit') {
							$email = $DB{$listname}{$pos}{$field};
							if ($fieldtype eq 'email confirm') { $confirm = 1; }
						}
					}			
					if ($confirm) { SendConfirmation($listname,$email,$key,$pos); }
					SendNotification($listname,$email,$key,$pos);			# send notification email to list owner (from user)
					
					RedirectTo("$script?template=$Form{'template'}");
				}
		} elsif ($Form{'action'} eq 'uploadzip') {
				ReceiveMultiFileUpload($listname);
				RedirectTo("$script?action=admin&list=$listname&template=$Form{'template'}");
				
		############################ delete
		} elsif ($Form{'action'} eq 'delete') {
		
				for(my $r = $DB{$listname}{d}{numentries}; $r >= 1; $r--) { 
					my $key = "delete".$DB{$listname}{$r}{'id'};
					if ($Form{$key} eq 'true') {
						Delete( $listname, $r );
					}
				}
				SaveDatabase($listname);
				RedirectTo("$script?action=admin&list=$listname&template=$Form{'template'}");
				
		############################ change ordering
		} elsif ($Form{'action'} eq 'swap') {
		
				my ($pos1,$pos2) = ($Form{'a'},$Form{'b'});
				if ($pos1 && $pos2) {
					SwapEntries($listname,$pos1,$pos2);
					SaveDatabase($listname);
				}
				RedirectTo("$script?action=admin&list=$listname&template=$Form{'template'}");
				
		############################ sort by field
		} elsif ($Form{'action'} eq 'sort') {
		
				my @Order = OrderBy($listname,$Form{'by'}); my %temp;
				for(my $r = 1; $r <= $DB{$listname}{d}{numentries}; $r++) {
				 	%{ $temp{$r} } = %{ $DB{$listname}{ $Order[$r] } };
				}
				%{ $temp{d} }          = %{ $DB{$listname}{d} };
				%{ $temp{fields} }     = %{ $DB{$listname}{fields} };
				%{ $temp{fieldtypes} } = %{ $DB{$listname}{fieldtypes} };
				%{ $temp{options} }    = %{ $DB{$listname}{options} };
				%{ $DB{$listname} }    = %temp;
				# TODO: swap auto lists if there are any
				SaveDatabase($listname);
				RedirectTo("$script?action=admin&list=$listname&template=$Form{'template'}");
				
		############################ add a new field to a list
		} elsif ($Form{'action'} eq 'addfield') {
				$DB{$listname}{d}{numfields}++;
				my $nf = $DB{$listname}{d}{numfields};
				for(my $i = $nf+8; $i > $nf; $i--) { # make room for a new field
					$DB{$listname}{fields}{$i} = $DB{$listname}{fields}{$i-1};
					$DB{$listname}{fieldtypes}{$i} = $DB{$listname}{fieldtypes}{$i-1};
				}
				$DB{$listname}{fields}{$nf} = $Form{'name'} ? $Form{'name'} : "newfield$nf";
				$DB{$listname}{fieldtypes}{$nf} = $Form{'type'} ? $Form{'type'} : "";
				SaveDatabase($listname);
				RedirectTo("$script?action=admin&list=$listname&template=$Form{'template'}");
				
		############################ premanently delete a field of a list
		} elsif ($Form{'action'} eq 'deletefield') {
				my $k = 0;
				foreach my $key (keys %{ $DB{$listname}{fields} }) {
					if ($DB{$listname}{fields}{$key} eq $Form{'name'}) { $k = $key;	}
				}
				if ($k > 0) {
					$DB{$listname}{d}{numfields}--;
					my $nf = $DB{$listname}{d}{numfields};
					for(my $i = $k; $i <= $nf+8; $i++) {
						$DB{$listname}{fields}{$i} = $DB{$listname}{fields}{$i+1};
						$DB{$listname}{fieldtypes}{$i} = $DB{$listname}{fieldtypes}{$i+1};
					}
					SaveDatabase($listname);
				}
				RedirectTo("$script?action=admin&list=$listname&template=$Form{'template'}");
				
		############################ render admin list
		} else {
				RenderAdminList($listname);
			
		} ##### end case $Form{'action'}
	
	############################ unauthorized
	} else {
		
		if ($Form{'key'} && ($Form{'action'} eq 'editform' || $Form{'action'} eq 'saveit' || $Form{'action'} eq 'delete')) {
		
			############################ can autorize by the code received via confirmation email
			
			for(my $r = 1; $r <= $DB{$listname}{d}{numentries}; $r++) {
				my $id = $DB{$listname}{$r}{'id'}; my $key = cryptId($id);
				if ($Form{'key'} eq $key) {	##### is allowed to edit, save or delete his personal entry	
					
					if ($Form{'action'} eq 'editform') {	##### editform
							AddOrEditForm( $listname, $r );
					
					} elsif ($Form{'action'} eq 'saveit') {	##### saveit
							AddItOrSaveIt( $listname, $r );		
							RedirectTo("$script?template=$Form{'template'}");
							
					} elsif ($Form{'action'} eq 'delete') {	##### delete
							Delete( $listname, $r );
							SaveDatabase($listname);
							if ($Form{'thanks'}) {
								RedirectTo($Form{'thanks'});
							} else {
								Alert("$lang{'deleted1'} \"$listname\" $lang{'deleted2'}</p><p>$lang{'gotolist'} <b><a href=\"$script?template=$Form{'template'}\">$listname</a></b>");
							}
					}
				}
			}
		} else {
			############################ ask password
			my $code = AskPassword("$lang{'adminpwd1'} \"$listname\"$lang{'adminpwd2'}:");
			my $var = GetAdminVariable($listname);
			if ($var) {
				#$template =~ s/\{for (all entries|this entry) .*?$listname.*?\}(?s).*?\{\/for\}//g;
				$template =~ s/\{adminmarker $var\}(?s).*?\{\/adminmarker\}/$code/;
			} else {
				$template = StandardHTML("",$code);
			}
		}
		
	} ##### end if allowed
			
	
	$template =~ s/\{on admin\}((?s).*?)\{\/on\}/$1/g;
	
} ##### end if action is admin
	

############################################################# rendering
			
Rendering();


if ($template) {											# output generated html

	if ($Form{'template'} =~ "\.css") { print "Content-type: text/css\n\n" }
	elsif ($Form{'template'} =~ "\.xml") { print "Content-type: text/xml\n\n" }
	else { print "Content-type: text/html\n\n"; }
	
	print $template;
	
	my ($Systemt, $Usert) = times(); my $CPUt = $Systemt + $Usert;
	print "\n\n<!-- Page served in $CPUt seconds by Dynamo v$version   http://www.m8j.net/dynamo -->\n"  if ($Form{'template'} =~ "htm");
	

	######################################################### data backup if necessary
	Backup();

	print "<!-- Messages:\n$status -->\n\n" if ($status && $Form{'template'} =~ "htm");

} else {
	Error("No template found");
}

exit;	


#############################################################
######################## Subroutines ########################
############################################################# rendering
sub Rendering {
	my @Order; my ($command, $content, $varname, $listname, $ids, $showmax, $showfrom, $sortby, $paging, $restrictby, $code, $foundsomething);
	
				# url.parameters
				foreach my $key (keys(%Form)) { $template =~ s/\{url\.$key\}/$Form{$key}/g; $template =~ s/url\.$key/\'$Form{$key}\'/g; } $template =~ s/\{url\.(.*?)\}//g;


	my ($pos1, $pos2) = MarkNextStatement($template);
	while ($pos2 > 0) {
		$content = substr($template, $pos1, $pos2 - $pos1);
		$content =~ /^\{(.*?)\}((?s).*)\{\/(.*?)\}$/;
		$command  = $1; $content  = $2;

		################################# render 'for all entries' statement
		if ($command =~ /^for all entries (.*?) in list (.*)$/) {
			$varname  = $1;
			$listname = $2;
		
			$showmax = $Form{$varname.'.showmax'}; $showfrom = $Form{$varname.'.showfrom'}; $sortby = $Form{$varname.'.sortby'};
			$paging = "", $restrictby = ""; $code = "";
			if ($listname =~ /^(.*?), ((show|order|restrict).*)$/) {
				$listname = $1; my $options = $2;
				if ($options =~ /show(| )max (.*?)(,|$)/) { $showmax = $2; }
				if ($options =~ /show(| )from (.*?)(,|$)/) { $showfrom = $2; }
				if ($options =~ /order(| )by (.*?)(,|$)/) { $sortby = $2; }
				if ($options =~ /do(| )paging(,|$)/) { $paging = 'true'; }
				if ($options =~ /restrict(|ed)(| )by (.*?)(,|$)/) { $restrictby = $3; }
			}
			
			# listentries
			if ($listname =~ /^(.*?)\[ids(.*)$/) {	$listname = $1; $ids = $2; } else { $ids = ""; }
			
			LoadDatabase($listname);
			@Order = OrderBy($listname,$sortby);
				
			if (!$ids && !(exists $Form{$varname.'.search'})) {									# display all entries
				my ($start, $stop) = CalcStartStop($listname, $showmax, $showfrom, $paging);
				for(my $r = $start; $r <= $stop; $r++) { 
					$code .= RenderEntry($listname,$Order[$r],$content,$varname,$restrictby);
				}
			} else {							
				my $c = 1;
				for(my $r = 1; $r <= $DB{$listname}{d}{numentries}; $r++) {
					my $id = $DB{$listname}{ $Order[$r] }{'id'};
					if ($ids =~ /(,|\=)$id(,|\])/) {										# display only selected ids
						if ((!$showfrom || $c >= $showfrom) && (!$showmax || $c<=$showmax)) {
							$code .= RenderEntry($listname,$Order[$r],$content,$varname,$restrictby); $c++;
						}
					} elsif ($Form{$varname.'.search'}) {									# display only entries matching search query
						my @query = split(/ /, lc($Form{$varname.'.search'}));	
						my $row = lc(join(" ",values %{$DB{$listname}{ $Order[$r] }}));
						my $found = 1; foreach (@query) { if( $row !~ $_ ) { $found = 0; } }
						if ($found) {
							if ($c >= $showfrom && ($c<=$showmax || !$showmax)) {
								$code .= RenderEntry($listname,$Order[$r],$content,$varname); $c++;
							}
							$foundsomething = 1;
						}
					}
				}
			}
			if (CheckIfViewAllowed($listname)) {
				substr($template, $pos1, $pos2 - $pos1) = $code;
			}
			
			$template =~ s/\{on found\}((?s).*?)\{\/on\}/$1/g if ($foundsomething);
			
		################################# render 'for this entry' statement
		} elsif ($command =~ /^for this entry (.*?) in list (.*)$/) {
			$varname = $1;
			$listname = $2;
			
			LoadDatabase($listname);
			
			my $num = 0; my $field = "";
			$num = $Form{"$varname.number"} ? $Form{"$varname.number"} : $Form{"number"};
			if (!$num) {									# try to extract a specifier from the url for an entry of this list
				foreach my $ky (keys(%Form)) {						# find matching field
					foreach my $f (values(%{ $DB{$listname}{fields} })) {
						if ($ky eq "$varname.$f" || !$field && $ky eq $f) { $field = $f; }
					}
				}
				if ($field) {										# go through  database
					for(my $r = 1; $r <= $DB{$listname}{d}{numentries}; $r++) {
						if (!$num && $DB{$listname}{$r}{$field} eq $Form{"$varname.$field"}) { $num = $r; }	# if the entry matches a value we have
						if (!$num && $DB{$listname}{$r}{$field} eq $Form{"$field"})          { $num = $r; }
					}
				}
			}
			
			if ($num) {
				$content = RenderEntry($listname,$num,$content,$varname);
			} else {
				if ($content =~ /\{on no entry found}((?s).*){\/on\}/) { $content = $1;
				} else { 
					$content = ($debugMode eq 'on') ? "$lang{'noentryfound'} $listname" : "";
				}
			}
			if (CheckIfViewAllowed($listname)) {
				substr($template, $pos1, $pos2 - $pos1) = $content;
			}
				
		################################# render 'if' statement
		} elsif ($command =~ /^if (.*)$/) {
			my $booleanequation = $1;
			
			# hack for additional easy to use 'contains' operator on listentry and listentries fields
			while ($booleanequation =~ /'.*?(\[.*?\])'\s*contains\s*'(.*?)'/) { $booleanequation =~ s/(.*)'.*?(\[.*?\])'\s*contains\s*'(.*?)'/$1'$2' =~ \/(,|\=)$3(,|\])\//; } $booleanequation =~ s/contains/=~/g;
			
			# lets also support an else part (TODO: this does not work for nested ifs yet !!!)
			my @parts = split(/\{else\}/, $content);
			$content = eval($booleanequation) ? $parts[0] : $parts[1];
			
			substr($template, $pos1, $pos2 - $pos1) = $content;

		################################# malformated command
		} else {
			substr($template, $pos1, $pos2 - $pos1) = "\{could not translate\}$content\{\/could not\}";
		}
		
		($pos1, $pos2) = MarkNextStatement($template);
	}
	
	################################# perl code in templates
	while ($template =~ /\{calc\}((?s).*?)\{\/calc\}/) {
		my $val = eval($1);
		$template =~ s/\{calc\}(?s).*?\{\/calc\}/$val/;
	}
	
	################################# render exactly specified entries ( listname[..].something, or listname.something )
	RenderExactlySpecEntries();

	################################# remove admin tags
	$template =~ s/\{on .*?\}((?s).*?)\{\/on\}//g;
	$template =~ s/\{adminmarker.*?\}|\{\/adminmarker\}|\{marker insert .*?\}(?s).*?\{\/marker\}|\{marker select .*?\}|\{marker edit .*?\}//g;
}

############################################################# mark the next statement ('if' or 'for') we have to render
sub MarkNextStatement {
	my $text = $_[0];
	
 	my $f = index($text,"{for ");
 	my $i = index($text,"{if ");
 	
	if (0 <= $f && ($f < $i || $i < 0)) {
		return ScanNestedTags($text,"{for ","{/for}");	
	} else {
		return ScanNestedTags($text,"{if ","{/if}");	
	}
}

############################################################# render exactly specified entries ( listname[..] )
sub RenderExactlySpecEntries {
	while ($template =~ /\{(.*?)\[(.*?)\]\.(.*?)\}/) {
		my $ln = $1; LoadDatabase($ln);
		my $arg = $2; my ($key,$val) = split(/=/,$arg); my $num = $arg; my $tail = $3;
		for(my $r = 1; $r <= $DB{$ln}{d}{numentries}; $r++) {
			if ($val && $DB{$ln}{$r}{$key} eq $val) { $num = $r; }	 # if entry matches hard-coded condition
		}
		if ($num>0) {
			$template = RenderEntry($ln,$num,$template,"$ln\\\[$arg\\\]");
		} elsif ($arg =~ /^ids=(.*)$/) {
			my $numEntries = split(/,/,$1);
			$template =~ s/\{$ln\[$arg\]\.number of entries\}/$numEntries/g;
		}
		$template =~ s/\{$ln\[$arg\]\.$tail\}//g;
	}
}

############################################################# render an entry
sub RenderEntry {
	my $listname = $_[0];				# listname
	my $num      = $_[1];				# number of the data row
	my $code     = $_[2];				# the piece of the html-template
	my $varname  = $_[3];				# name of the variable concerned by the replacement
	my $restrictby  = $_[4];			# this can be set if only entries that are nonempty in some field should be displayed
	
	if ($restrictby && !$DB{$listname}{$num}{$restrictby}) { 	# ignore this entry because it's empty in the field '$restrictby'
		return "";
	} else {
		
		for(my $k = 1; $k <= $DB{$listname}{d}{numfields}+8; $k++) {   # render all fields
			my $field     = $DB{$listname}{fields}{$k};
			my $fieldtype = $DB{$listname}{fieldtypes}{$k};
			my $value     = $DB{$listname}{$num}{$field};
			$field = quotemeta($field);
			
															# standard replacement
			$code =~ s/\{$varname\.$field\}/$value/g;
			
			while ($code =~ /\{$varname\.$field, ((lang|show|url|strip).*?)\}/) {
				my $options = $1; my $modifiedvalue = $value;
				if ($options =~ /lang (.*?)(,|$)/) {						# multi-languages
					$lang = $1;
					if ($value =~ /\{$lang\:((?s).*?)\}/) {		# found the desired translation
						$modifiedvalue = $1;
					} elsif ($value =~ /\{.+?\:((?s).*?)\}/) {	# language not available, just take the first translation
						$modifiedvalue = $1;
					}
				}
				if ($options =~ /show(| )max (.*?)(,|$)/) {					# truncated
					$truncate = $2;
					unless (length($modifiedvalue) <= $truncate) {
						$modifiedvalue = substr($modifiedvalue,0,$truncate)."...";
					}
				}
				if ($options =~ /url(c| c)ompatible(,|$)/) {				# make url compatible
					my $u = lc( substr($modifiedvalue,0,200) );
					$u =~ s/Š/ae/g;	$u =~ s/š/oe/g;	$u =~ s/Ÿ/ue/g;
					$u =~ s/€/ae/g;	$u =~ s/…/oe/g;	$u =~ s/†/ue/g;
					$u =~ s/§/ss/g; $modifiedvalue = $u;
				}
				if ($options =~ /strip(| )tags(,|$)/) {				# strip any html tags
					$modifiedvalue =~ s/<.+?>//g;
				}
				$code =~ s/\{$varname\.$field, $options\}/$modifiedvalue/g;
			}
															# single or multiple entries of another list 
			$code =~ s/\{$varname\.$field\.(.*?)\}/\{$value\.$1\}/g;
															# entries of another list 
			$code =~ s/in list $varname\.$field(,|\})/in list $value$1/g;
			
															# aaaaaaaaaaaaaaaaaaaaaaaaarrrrrrrrrrrrrgh .entries in if conditions
			if ($code =~ /\{if (.*?)$varname\.$field\.(.*?)\}/) {
				my ($ln, $arg, $n);
				if ($value =~ /^(.*?)\[(.*?)\]/) {
					$ln = $1; LoadDatabase($ln);
					$arg = $2; my ($key,$val) = split(/=/,$arg); $n = $arg;
							my $noe = split(/,/,$val); $code =~ s/\{if (.*?)$varname\.$field\.number of entries/\{if $1$noe/g; # ugly hack, just for number of entries
					for(my $r = 1; $r <= $DB{$ln}{d}{numentries}; $r++) {
						if ($val && $DB{$ln}{$r}{$key} eq $val) { $n = $r;  }	 # if entry matches hard-coded condition
					}
				}
				if ($ln && $n > 0) { # yes, found an existing entry for $varname\.$field with value $value, make recursive call for this new 'variable'
					$code =~ s/\{if (.*?)$varname\.$field\./\{if $1$value\./g;
					$code = RenderEntry($ln,$n,$code,quotemeta("$ln\[$arg\]"));
				} else { # no, could not find an existing entry for $varname\.$field with value $value
					#Debug(" If expression: found no matching entry for '$varname\.$field' with value '$value'.");
					$code =~ s/\{if (.*?)$varname\.$field\.(.*?)(\}|\)|\&|\||\=|\!|>|<)/\{if $1''$3/g;
				}
			};
															# entries in if conditions
			while ($code =~ /\{if ([^\}]*?)$varname\.$field(\W)/) {
				$value =~ s/\'/\\\'/g;
				$code =~ s/\{if ([^\}]*?)$varname\.$field(\W)/\{if $1\'$value\'$2/g;
			}
	
															# a file size
			if ($code =~ /\{size( |)of $varname\.$field\}/) {
				my $size = (-s "$datadir\/$listname\/".$value) / 1024;
				if ($size > 1024) {	$size = sprintf("%.1f MB" , $size / 1024);
				} else {			$size = sprintf("%.1f KB" , $size);			}
				$code =~ s/\{size( |)of $varname\.$field\}/$size/g;
			}
															# auto thumbnails for images
			while ($code =~ /\{$varname\.$field(,|) resize( |)to (.*?)\}/) {
				my $resizeto = $3; $value =~ /^(.*)\.(.*?)$/;
				my $filename = $1; my $extension = $2;
				my $orign = "$datadir/$listname/$filename.$extension";
				my $thumb = "$datadir/$listname/t-$filename-$resizeto.$extension";
				if ((-f $orign) && (!(-f $thumb) || (-M $orign) < (-M $thumb))) {	# thumbnail inexistent or outdated ?
					MakeTumbnail($orign,$thumb,$resizeto);
				}
				if (-f $thumb) { $code =~ s/\{$varname\.$field(,|) resize( |)to $resizeto\}/t-$filename-$resizeto.$extension/g;
				} else {         $code =~ s/\{$varname\.$field(,|) resize( |)to $resizeto\}/$filename.$extension/g; }
			}
		}
															# dates
		if ($code =~ /\{$varname\.(date|short|rss)/) {
			my $d;
			my @changed = DatePrint($DB{$listname}{$num}{'changed'});
			my @created = DatePrint($DB{$listname}{$num}{'created'});
			$d = $changed[0];	$code =~ s/\{$varname\.date\}/$d/g;
			$d = $changed[1];	$code =~ s/\{$varname\.short(-| |)date\}/$d/g;
			$d = $changed[2];	$code =~ s/\{$varname\.rss(-| |)date\}/$d/g;
			$d = $changed[3];	$code =~ s/\{$varname\.date\.sec\}/$d/g;
			$d = $changed[4];	$code =~ s/\{$varname\.date\.min\}/$d/g;
			$d = $changed[5];	$code =~ s/\{$varname\.date\.hour\}/$d/g;
			$d = $changed[6];	$code =~ s/\{$varname\.date\.day\}/$d/g;
			$d = $changed[7];	$code =~ s/\{$varname\.date\.month\}/$d/g;
			$d = $changed[8];	$code =~ s/\{$varname\.date\.year\}/$d/g;
			$d = $changed[9];	$code =~ s/\{$varname\.date\.weekday\}/$d/g;
			$d = $changed[10];	$code =~ s/\{$varname\.date\.monthname\}/$d/g;
			$d = $created[0];	$code =~ s/\{$varname\.date(-| |)created\}/$d/g;
			$d = $created[1];	$code =~ s/\{$varname\.short(-| |)date(-| |)created\}/$d/g;
			$d = $created[2];	$code =~ s/\{$varname\.rss(-| |)date(-| |)created\}/$d/g;
			$d = $created[3];	$code =~ s/\{$varname\.date(-| |)created\.sec\}/$d/g;
			$d = $created[4];	$code =~ s/\{$varname\.date(-| |)created\.min\}/$d/g;
			$d = $created[5];	$code =~ s/\{$varname\.date(-| |)created\.hour\}/$d/g;
			$d = $created[6];	$code =~ s/\{$varname\.date(-| |)created\.day\}/$d/g;
			$d = $created[7];	$code =~ s/\{$varname\.date(-| |)created\.month\}/$d/g;
			$d = $created[8];	$code =~ s/\{$varname\.date(-| |)created\.year\}/$d/g;
			$d = $created[9];	$code =~ s/\{$varname\.date(-| |)created\.weekday\}/$d/g;
			$d = $created[10];	$code =~ s/\{$varname\.date(-| |)created\.monthname\}/$d/g;
		}
		
		return $code;
	}
}


############################################################# date formating
sub DatePrint {
	my $stamp = $_[0];
	(my $sec,my $min,my $hour,my $mday,my $month,my $year,my $wday,my $yday,my $isdst) = localtime($stamp);
	my $monthname = $lang{'monthnames'}[$month];
	my $weekdayname = $lang{'weekdaynames'}[$wday];
	$year += 1900; $month++;
	my $date = sprintf($dateformat, $mday,$month,$year,$hour,$min);
	my $shortdate = sprintf($shortdateformat, $mday,$month,$year,$hour,$min);
	my $rssmonthname = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")[$month-1];
	my $rssweekdayname = ("Mon","Tue","Wed","Thu","Fri","Sat","Sun")[$wday-1];
	my $rssdate = sprintf("%.3s, %02d %.3s %4d %02d:%02d:%02d ".$rsstimezone, $rssweekdayname,$mday,$rssmonthname,$year,$hour,$min,$sec);
	return ($date, $shortdate, $rssdate, $sec, $min, $hour, $mday, $month, $year, $weekdayname, $monthname);
}


############################################################# calculate start & stop borders
sub CalcStartStop {
	my $listname = $_[0]; my $showmax = $_[1]; my $showfrom = $_[2]; my $paging = $_[3];
	my $numentries = $DB{$listname}{d}{numentries};

	my $params = "";
	foreach my $ky (keys(%Form)) {
		if ($ky ne "offset$listname") { $params .= "$ky=$Form{$ky}&"; }
	} chop($params);	
	my $start = 1; my $stop;
	if ($showfrom > 1) { $start = $showfrom; }
	if ($showmax) { 
		$bottom = "<table width=\"90%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\"><tr>";
		my $of = "offset$listname"; my $offset = $Form{$of} ? $Form{$of} : 0;
		if ($offset) { $start += $offset; }	if ($start < 1) { $start = 1 };
		$stop = $start + $showmax - 1;		if ($stop  > $numentries) { $stop = $numentries };
		if ($start > 1 ) {
			my $newoff = $offset - $showmax; if ($newoff < 0) { $newoff = 0 };
			my $sa = $newoff + 1; my $so = $newoff + $showmax;						if ($showmax > 1) {
			$bottom .= "<td align=left ><a href=\"$script?$params&offset$listname=$newoff\"><img src=\"$button{'left'}\" border=\"0\" align=absmiddle> $sa - $so</a></td>"; } else {
			$bottom .= "<td align=left ><a href=\"$script?$params&offset$listname=$newoff\"><img src=\"$button{'left'}\" border=\"0\" align=absmiddle> $sa      </a></td>"; }
#			$bottom .= "<td align=left ><a href=\"$script?$params&offset$listname=$newoff\"><img src=\"$button{'left'}\" border=\"0\" align=absmiddle></a></td>";
		}
		if ($stop < $numentries ) {
			my $newoff = $offset + $showmax;
			my $sa = $newoff + 1; my $so =  $newoff + $showmax; if ($so > $numentries) { $so = $numentries };				if ($showmax > 1) {
			$bottom .= "<td align=right><a href=\"$script?$params&offset$listname=$newoff\">$sa - $so <img src=\"$button{'right'}\" border=\"0\" align=absmiddle></a></td>"; } else {
			$bottom .= "<td align=right><a href=\"$script?$params&offset$listname=$newoff\">$sa       <img src=\"$button{'right'}\" border=\"0\" align=absmiddle></a></td>"; }
#			$bottom .= "<td align=right><a href=\"$script?$params&offset$listname=$newoff\"><img src=\"$button{'right'}\" border=\"0\" align=absmiddle></a></td>";
		}
		$bottom .= "</tr></table>";
		if ($paging) {
			$template =~ s/\{\/adminmarker\}/$bottom\{\/adminmarker\}/;
		}
	} else {
		$stop = $start + $numentries - 1;
	}
	return ($start, $stop);
}

############################################################# read database
sub LoadDatabase {
	my $listname = $_[0]; my $i; my $txt;
	
	if (not exists $DB{$listname}) {
		if (-f "$datadir/$listname.txt") {
		
			open(DATA, "<$datadir/$listname.txt") || Error("$lang{'list'} '$listname.txt' $lang{'couldnotbefoundin1'} $datadir/ $lang{'couldnotbefoundin2'}<small><br/><br/>($!)</small>");
			my @rows = <DATA>;
			close(DATA);
			my @txt = split(/\n##/, join("",@rows) );
			
			for(my $t = 0; $t <= $txt[3]+$do; $t++) { chomp $txt[$t]; }
	
			$i=1; foreach (split(/\|/, $txt[0])) { $DB{$listname}{fields}{$i}	  = $_;     $i++; }
			$DB{$listname}{d}{numfields} = $i-1; # number of user defined fields
												   $DB{$listname}{fields}{$i} 	  = 'created';   $i++;
												   $DB{$listname}{fields}{$i}	  = 'changed'; $i++;
												   $DB{$listname}{fields}{$i} 	  = 'ip';   $i++;
												   $DB{$listname}{fields}{$i} 	  = 'id';   $i++;
												   $DB{$listname}{fields}{$i}     = 'dataurl'; $i++;	# add some 'artificial' fields
												   $DB{$listname}{fields}{$i}     = 'number of entries'; $i++;
												   $DB{$listname}{fields}{$i}     = 'number'; $i++;
	
			$i=1; foreach (split(/\|/, $txt[1])) { $DB{$listname}{fieldtypes}{$i} = $_;     $i++; }
			$i=1; foreach (split(/\|/, $txt[2])) { (my $key, my $val) = split(/=/,$_);
												   $DB{$listname}{options}{$key}  = $val; }
			$DB{$listname}{d}{numentries} = $txt[3];
			$DB{$listname}{d}{dataurl} = "$dataurl/$listname/";
			
			for(my $r = 1; $r <= $DB{$listname}{d}{numentries}; $r++) { $i=1;
				foreach (split(/\|/, $txt[$r+$do]))  {
					my $key = $DB{$listname}{fields}{$i}; $i++;
					$DB{$listname}{$r}{$key} = $_;
				}
				$DB{$listname}{$r}{'number'}  = $r;
				$DB{$listname}{$r}{'dataurl'}  = $DB{$listname}{d}{dataurl};
				$DB{$listname}{$r}{'number of entries'}  = $DB{$listname}{d}{numentries};
			}
		} else {
			Debug("$lang{'list'} '$listname.txt' $lang{'couldnotbefoundin1'} $datadir/ $lang{'couldnotbefoundin2'}");
		}
	}
	
} 

############################################################# save database
sub SaveDatabase {
	my $listname = $_[0]; my $txt = ""; my $i;
	
	for($i = 1; $i <= $DB{$listname}{d}{numfields}; $i++) {		# field names
		$txt .= "$DB{$listname}{fields}{$i}|";
	}	chop($txt) if ($i>1); $txt .= "\n##";
	
	for($i = 1; $i <= $DB{$listname}{d}{numfields}; $i++) {		# field types
		$txt .= "$DB{$listname}{fieldtypes}{$i}|";
	}	chop($txt) if ($i>1); $txt .= "\n##";
	
	foreach my $key (keys(%{ $DB{$listname}{options} })) {		# options
		$txt .= "$key=$DB{$listname}{options}{$key}|";
	}	chop($txt) if (keys(%{ $DB{$listname}{options} }) > 0); $txt .= "\n##";
	
	$txt .= $DB{$listname}{d}{numentries};						# number of entries
	
	for(my $r = 1; $r <= $DB{$listname}{d}{numentries}; $r++) {	# entries
		$txt .= "\n##";
		for($i = 1; $i <= $DB{$listname}{d}{numfields}+4; $i++) {
			$txt .= "$DB{$listname}{$r}{ $DB{$listname}{fields}{$i} }|";
		}	chop($txt)
	}
	open(DATA, ">$datadir/$listname.txt") || Error("$lang{'nowriteaccess'} $datadir/$listname.txt <small><br/><br/>($!)</small>");
	print DATA $txt;
	close(DATA);
} 
			
############################################################# sort database by fields
sub OrderBy {	
	$listname = $_[0]; my @order = ();
	if ($_[1] !~ 'random') { # order by one or more fields
	   @keys = split(/\*/, $_[1]); @signs = ();
	   for(my $i = 0; $i < @keys; $i++) { if ($keys[$i] =~ /^-(.*)$/) { $keys[$i] = $1; $signs[$i] = -1; } else { $signs[$i] = 1; } }
    
		for(my $r = 0; $r <= $DB{$listname}{d}{numentries}; $r++) { push(@order, $r); }
    	if ($keys[0]) {
	   	@order = sort(MyOrder @order);
	   }
	} else {  # random order
       my $n = $DB{$listname}{d}{numentries}; push(@order, 0);
	   while ($#order < $n) {
	       my $i = int(rand($n) + 1);
	       if (grep(/^$i$/,@order) <= 0) {  push(@order, $i); }
	   }
    }
	return @order;
}
sub MyOrder {
	for(my $i = 0; $i < @keys; $i++) { if ($a == 0) { return -1; } # dummy 0 has to stay at position 0
	
		my $v1 = lc($DB{$listname}{$a}{$keys[$i]});
		my $v2 = lc($DB{$listname}{$b}{$keys[$i]});
		
		if ($v1 =~ /\[ids=(.*)\]$/ || $v2 =~ /\[ids=(.*)\]$/) { # special case for 'listentries' fields
			$v1 =~ /\[ids=(.*)\]$/; my $n1 = split(",",$1); $v1 = sprintf("%08d", $n1);
			$v2 =~ /\[ids=(.*)\]$/; my $n2 = split(",",$1); $v2 = sprintf("%08d", $n2);
		}
		
		if    ($v1 lt $v2) { return -1*$signs[$i]; } # return comparison result depending on 'sign' for this ordering key
		elsif ($v1 gt $v2) { return  1*$signs[$i]; }
	}
	return 0;
}


############################################################# scan for nested tag content, mark them with #%#
sub ScanNestedTags {
	my $innerhtml = ""; my $start = ""; my $reminder = "";
	my $opentag = $_[1];  my $opentagLength  = length($opentag);
	my $closetag = $_[2]; my $closetagLength = length($closetag);
	my $beginning = $_[3] ? $_[3] : $opentag;	# (optional tag we want to have at the beginning)
	
	#my $t = [ Time::HiRes::gettimeofday( ) ];
	
	my $startOffset = index($_[0],$beginning);
	if ($startOffset >= 0) {
		$reminder = substr $_[0], $startOffset;
		
		my $c = 0;
		my $depth = 0;
		while ($depth > 0 && $c < 200 || $c==0) { $c++;
			my $open  = index($reminder,$opentag);
			my $close = index($reminder,$closetag);
			
			if ($open >= 0 && ($open < $close || $close < 0)) {
				$innerhtml .= substr $reminder, 0, $open+$opentagLength;
				$reminder = substr $reminder, $open+$opentagLength;
				$depth ++;
			} elsif ($close >= 0 && ($close < $open || $open < 0)) {
				$innerhtml .= substr $reminder, 0, $close+$closetagLength;
				$reminder = substr $reminder, $close+$closetagLength;
				$depth --;
			}
		}
		
		#my $elapsed = Time::HiRes::tv_interval( $t );
		#$time += $elapsed;
		#Debug("burnt things in ScanNestedTags: $elapsed (global time is: $time)");
		if ($c < 200) {
			return ($startOffset, $startOffset+length($innerhtml));
		} else {
			Debug("ScanNestedTags Error: Runaway scan while searching for $opentag followed by $closetag in the following code:\n $_[0].");
		}
	}
	return (0,0);
}


############################################################# make image thumbnail using ImageMagick
sub MakeTumbnail {
	my $orign = $_[0];
	my $thumb = $_[1];
	my $resizeto = $_[2];
	my $try;
	
	if ($HaveImageMagick eq '?') {		# determine if perl magick is available
		if (eval("require Image::Magick")) { $HaveImageMagick = 'yes'; }
		else {                               $HaveImageMagick = 'no';  }
	}
  	$resizeto =~ /^(.*?)x/; my $quality = $1<200 ? 85 : 75;
	if ($HaveImageMagick eq 'yes') {	# use perl magick
		my $image = Image::Magick->new;
 		$try .= $image->Read($orign); #$try .= $image->Strip(); #$try .= $image->Resize(geometry=>$resizeto);
  		$try .= $image->Thumbnail(geometry=>$resizeto);
  		$try .= $image->Write(filename=>$thumb,quality=>$quality);
  		undef $image;
	} else {							# try direct execution of the command
		my $cmd = "convert \"$orign\" -thumbnail $resizeto -quality $quality \"$thumb\"";
		$try .= qx($cmd);
	}
    Debug("Make thumbnail attempt for $orign (resizing to $resizeto), result: $try");
}


############################################################# add or edit form
sub AddOrEditForm {
	my $listname = $_[0];
	my $pos = $_[1];
	my $nextaction;
		
	$nextaction = $Form{'action'} eq 'addform' ? "addit" : "saveit";
	
	my $code = qq|					
					<form method="post" action="$script" enctype="multipart/form-data" name="listform" onsubmit="document.getElementsByName('button')[0].src = '$button{'wait'}'; document.getElementsByName('button')[0].disabled = true;"><img src="$button{'wait'}" width="1" height="1" border="0">
					<input type="hidden" name="list" value="$listname">
					<input type="hidden" name="template" value="$Form{'template'}">
					<input type="hidden" name="action" value="$nextaction">
					<input type="hidden" name="key" value="$Form{'key'}">
					<input type="hidden" name="position" value="$pos">
					<input type="hidden" name="thanks" value="$Form{'thanks'}">
					
					<table class="dynamo-listentry" cellspacing="0">
			  \n|;
	my ($value, @wysiwyg_fields);
	for(my $k = 1; $k <= $DB{$listname}{d}{numfields}; $k++) { 
		my $field     = $DB{$listname}{fields}{$k};
		my $fieldtype = $DB{$listname}{fieldtypes}{$k};
	
							if ($Form{'action'} eq 'editform') { 					# fill in existing data
								$value = $DB{$listname}{$pos}{$field};
								if ($fieldtype eq 'textbox') {
									$value =~ s/<p>/\n\n/g;
									$value =~ s/<br\/>/\n/g;
								} elsif ($fieldtype eq 'codebox') {
									$value =~ s/</&lt;/g;
									$value =~ s/>/&gt;/g;
								}
							} else {
								$value = "";
							}
		$code .= "  <tr id=\"admin_field$k\"><td class=\"data\" valign=\"top\">$field:</td><td class=\"data\">\n    ";
		if ($fieldtype eq 'small') {					# small textbox
			$code .= "<input type=\"text\" name=\"field$k\" size=\"15\" value=\"$value\">";
			
		} elsif ($fieldtype eq 'textbox') {				# textarea
			$code .= "<textarea name=\"field$k\" cols=\"35\" rows=\"6\">$value</textarea>";
		} elsif ($fieldtype eq 'codebox') {				# code-textarea
			$code .= "<textarea name=\"field$k\" cols=\"35\" rows=\"6\">$value</textarea>";
		
		} elsif ($fieldtype =~ /^wysiwyg(.*)/) {		# wysiwyg editor
			my $rows = 18; if ($1 =~ /big/) { $rows += 10; }
			$code .= "<textarea name=\"field$k\" id=\"field$k\" style=\"width:100%\" rows=\"$rows\">$value</textarea>";
			push(@wysiwyg_fields, "field$k");
			
		} elsif ($fieldtype eq 'checkbox') {			# checkbox
			$code .= "<input type=\"checkbox\" name=\"field$k\" value=\"true\" id=\"box$k\"";
			if ($value) {	$code .= " checked=\"true\">";
			} else {		$code .= ">"; }
			
		} elsif ($fieldtype =~ /radio (.*)/) {			# radio buttons
			my @items = split(/\.\./, $1); my $c = 0;
			foreach my $item (@items) { $c ++;
				$code .= "<input type=\"radio\" name=\"field$k\" value=\"$item\" id=\"but$k-$c\"";
				if ($value eq $item) {	$code .= " checked> ";
				} else { 				$code .= "> "; }
				$code .= "<label for=\"but$k-$c\">$item</label> &nbsp; ";
			}
			
		} elsif ($fieldtype =~ /select (.*)/) {			# dropdown menu (select)
			$code .= "<select name=\"field$k\">";
			my @items = split(/\.\./, $1);
			foreach my $item (@items) {
				if ($value eq $item) {	$code .= "<option selected>$item</option> \n";
				} else { 				$code .= "<option>$item</option> \n"; }
			} $code .= "</select>";
			
		} elsif ($fieldtype eq 'url') {					# url
			$value =~ s/http:\/\///g;
			$code .= "http://<input type=\"text\" name=\"field$k\" size=\"28\" value=\"$value\">";
			
		} elsif ($fieldtype eq 'file') {				# file
			if ($Form{'action'} eq 'editform' && $value && !$Form{"remove$k"}) {			
				$code .= qq|<input type="hidden" name="oldfile$k" value="$value"><span id="file$k"><i><a href="$dataurl/$listname/$value" target="_blank"><img src="$button{'file'}" border="0" align="absmiddle"> $value</a></i>
							&nbsp; &nbsp; &nbsp; (<a href="javascript:removefile$k();">$lang{'removefile'}</a>)</span>
							<script type="text/javascript">function removefile$k() { document.getElementById('file$k').innerHTML = '<input type=hidden name=remove$k value=true>'; }</script>
						   |;
			}
			$code .= "<br/><input type=\"file\" size=\"30\" name=\"field$k\" accept=\"*/*\">";
			
		} elsif ($fieldtype =~ /^list (.*)/) {			# list
			$code .= "<input type=\"hidden\" name=\"field$k\" value=\"$value\">";
			if ($Form{'action'} eq 'editform') {										# open autolist template in new popup window
				$code .= "$lang{'list'} $value <a href=\"$script?action=admin&list=$value&template=$1\" onclick=\"window.open(this.href,'pup','width=700,height=700,left=300,top=25,menubar=no,location=no,resizable=yes,dependent=yes,scrollbars=yes'); return false;\" target=\"pup\"><img src=\"$button{'edit'}\" border=\"0\" align=absmiddle> $lang{'edit'}</a>";
			} else {
				$code .= "$lang{'list'} $listname/$field";
			}
			
		} elsif ($fieldtype =~ /^listentry (.*)/) {		# one entry of another list
			(my $otherlist, my $fielddesc) = split(/\./, $1); my @fields =  split(/\,/, $fielddesc); $otherlist =~ s/^bidirect.*? //;
			$value =~ /\[id=(.*)\]/; $value = $1;
			LoadDatabase($otherlist); $code .= "<select name=\"field$k\"><option value=\"\"></option> \n";

			for(my $r = 1; $r <= $DB{$otherlist}{d}{numentries}; $r++) {
				my $identifier = ""; foreach my $field (@fields) { $identifier .= $DB{$otherlist}{$r}{$field}.", "; } chop($identifier);chop($identifier);
				if ($value eq $DB{$otherlist}{$r}{'id'}) {	$code .= "<option value=\"$otherlist\[id=$DB{$otherlist}{$r}{'id'}\]\" selected=\"true\">$identifier</option> \n";
				} else {									$code .= "<option value=\"$otherlist\[id=$DB{$otherlist}{$r}{'id'}\]\">$identifier</option> \n"; }
			}
			$code .= "</select>";
			
		} elsif ($fieldtype =~ /^listentries (.*)/) {	# several entries of another list
			(my $otherlist, my $fielddesc) = split(/\./, $1); my @fields =  split(/\,/, $fielddesc); $otherlist =~ s/^bidirect.*? //;
			LoadDatabase($otherlist);
			if (!$DB{$listname}{options}{'listentries-by-checkboxes'}) { $code .= "<select name=\"field$k\" size=\"6\" multiple=\"multiple\"> \n"; }

			for(my $r = 1; $r <= $DB{$otherlist}{d}{numentries}; $r++) {
				my $id = $DB{$otherlist}{$r}{'id'};
				my $ok = ($value =~ /(,|\=)$id(,|\])/);
				my $identifier = ""; foreach my $field (@fields) { $identifier .= $DB{$otherlist}{$r}{$field}.", "; } chop($identifier);chop($identifier);
				
				if (!$DB{$listname}{options}{'listentries-by-checkboxes'}) {
					if ($ok) {	$code .= "<option value=\"$DB{$otherlist}{$r}{'id'}\" selected=\"true\">$identifier</option> \n";
					} else {	$code .= "<option value=\"$DB{$otherlist}{$r}{'id'}\">$identifier</option> \n"; }
				} else {
					$code .= "<input type=\"checkbox\" name=\"field$k\" value=\"$DB{$otherlist}{$r}{'id'}\" id=\"box$r\"";
					if ($ok) {	$code .= " checked=\"true\"><label for=\"box$r\">$identifier</label> &nbsp;<br/> \n";
					} else {	$code .=                  "><label for=\"box$r\">$identifier</label> &nbsp;<br/> \n"; }
				}
			}
			if (!$DB{$listname}{options}{'listentries-by-checkboxes'}) { $code .= "</select><br/><small> $lang{'selectmultiple'}</small> \n"; }
			
		} else {										# normal textbox
			$code .= "<input type=\"text\" name=\"field$k\" size=\"33\" value=\"$value\">";
		}
		$code .= "  </td></tr>\n";
	}
	
	$code .= "		  </table><br/>\n";
	if (@wysiwyg_fields) { 									# if any wysiwyg editors
		my $wysiwyg_flds = join(",",@wysiwyg_fields);
		$wysiwyg_code =~ s/#wysiwyg_fields#/$wysiwyg_flds/; # use wysiwyg code from config
		$code = "$wysiwyg_code \n\n $code";
	}
			   
	if ($Form{'action'} eq 'editform') { 
		$code .= qq| <input name="button" type="image" src="$button{'save'}" border="0">    \n  |;
	} else {
		$code .= qq| <input name="button" type="image" src="$button{'addit'}" border="0">    \n  |;
	}
	$code .=		      "</form>\n";
	
													# output
	my $var = GetAdminVariable($listname);
	if ($var) {
		$template =~ s/\{adminmarker $var\}(?s).*?\{\/adminmarker\}/$code/;
	} else {
		my $title = $Form{'action'} eq 'addform' ? "$lang{'addentry1'} '$listname' $lang{'addentry2'}" : "$lang{'editentry1'} '$listname' $lang{'editentry2'}";
		$template = StandardHTML($title,$code);
	}
}


############################################################# addit or saveit
sub AddItOrSaveIt {
	my $listname = $_[0];
	my $pos = $_[1];
	my %Data = ();
	
	if ($Form{'action'} eq 'addit') {
		$Data{'created'} = time;
		$Data{'id'} = GetNextUnusedId($listname);
	} else {
		$Data{'created'} = $DB{$listname}{$pos}{'created'};
		$Data{'id'} = $DB{$listname}{$pos}{'id'};
	}
	my $id = $Data{'id'};
	
	$Data{'changed'} = time;
	$Data{'ip'}   = $cgi->remote_host();
	
	for(my $k = 1; $k <= $DB{$listname}{d}{numfields}; $k++) { 
		my $field     = $DB{$listname}{fields}{$k};
		my $fieldtype = $DB{$listname}{fieldtypes}{$k};
		
		# TODO: if fieldtype multilingual, join different lang-form-fields into one field {de:}...
		
		my $key = "field$k";
		$Form{$key} = join(",",$cgi->param($key)) if (scalar @{[ $cgi->param($key) ]} >1); # if there are multiple entries in a single field, join them
		
		if ($fieldtype eq 'url') { $Form{$key} =~ s/http:\/\///g; }	# make valid url
		if ($fieldtype eq 'textbox') {
			$Form{$key} =~ s/\n\n/<p>/g;
			$Form{$key} =~ s/\n/<br\/>/g;
		}
		if ($fieldtype eq 'codebox') {
			$Form{$key} =~ s/&lt;/</g;
			$Form{$key} =~ s/&gt;/>/g;
		}
		if ($fieldtype =~ /^list / && $Form{'action'} eq 'addit') {	# name of the new list
			$Form{$key} = "$listname/$field-$id";
		}
		if ($fieldtype =~ /^listentr(y|ies) (.*?)\./) {				# listentries of another list
            my $selectedIDs = "[ids=".$Form{$key}."]";
	       	
	       	my $result = UpdateBidirectionalLinks($selectedIDs, $listname, $field, $fieldtype, $id);
	       	
	       	if ($fieldtype =~ /^listentries/) {
	       		unless ($Form{$key} =~ /.+\[ids=.+/) {
                	$Form{$key} = $result;
                } # otherwise it's already formatted
            }
		}
		
		if ($fieldtype eq 'file' && !$Form{'zipfile'}) {           	# receive file upload
			$Data{$field} = ReceiveFileUpload($listname, $k, $id);
		} else {                                                    # default case for all types of fields
			$Data{$field} = $Form{$key};
		}
	}
	
	if ($Form{'action'} eq 'addit') {		# shift data on disk appropriately
															# make place, shift list
		for(my $r =  $DB{$listname}{d}{numentries}; $r > $pos; $r--) { 
			%{ $DB{$listname}{$r} } = %{ $DB{$listname}{$r-1} };
		}
															# create (copy) empty list template & create new folder
											while (my ($k, $field) = each(%{ $DB{$listname}{fields} })) {
												if ($DB{$listname}{fieldtypes}{$k} =~ /^list /) {
													copy("$datadir/$listname/$field.txt","$datadir/$listname/$field-$id.txt") || Error("copy error - $datadir/$listname/$field.txt - $!");
													CopyDir("$datadir/$listname/$field","$datadir/$listname/$field-$id");
												}
												$k ++;
											}
    }
	foreach my $key (keys %Data) { $Data{$key} =~ s/\|/&#124;/g; $Data{$key} =~ s/\#\#/&#35;&#35;/g; }

															# write entry
	%{ $DB{$listname}{$pos} } = %Data;
	SaveDatabase($listname);
	return $id;
}

############################################################# update bidirectional links between two lists ('listentries' fields)
sub UpdateBidirectionalLinks {
	my $selectedIDs = $_[0];
	my $listname = $_[1];
	my $field = $_[2];
	my $fieldtype = $_[3];
	my $myID = $_[4];
	
	my $otherlist;
	
	if ($fieldtype =~ /^listentr(y|ies) (bidirect.*?) (.*?)\./) {     # bidirectional links (with auto-update on the other side)
	   my $identifierCode = $2;
	   $otherlist = $3;
	   LoadDatabase($otherlist);
	   # find corresponding entry field in other list
	   my $otherField = ""; my $otherFieldtype = "";
	   
									for(my $g = 1; $g <= $DB{$otherlist}{d}{numfields}; $g++) {
										if ($DB{$otherlist}{fieldtypes}{$g} =~ /^listentr(y|ies) $identifierCode $listname/ && $DB{$otherlist}{fields}{$g} ne $field) {
											$otherField = $DB{$otherlist}{fields}{$g};
											$otherFieldtype = $DB{$otherlist}{fieldtypes}{$g};
										}
									}
									#if (!$otherField && $fieldtype =~ /^listentr(y|ies) $identifierCode $listname/) { $otherField = $field; $otherFieldtype = $fieldtype; } # self-link
									Debug("Bidirectional link found. Corresponding field '$otherField' in other list '$otherlist' is of type '$otherFieldtype'");
	   if ($otherField ne "") {
		   # walk through other list and update matching entries fields
		   for(my $r =  1; $r <= $DB{$otherlist}{d}{numentries}; $r++) { 
			   my $otherContent = $DB{$otherlist}{$r}{$otherField};
			   my $otherID = $DB{$otherlist}{$r}{'id'};
			   if ($selectedIDs =~ /(=|,)$otherID(,|\])/) { # add myID to this entry in the other list
				   if ($otherContent !~ /(=|,)$myID(,|\])/) {
				   	   if ($otherFieldtype =~ /^listentries/) {		# may have multiple entries on the other side
						   if ($DB{$otherlist}{$r}{$otherField} =~ /\[ids=(.+)\]$/) { # if some links exist already
							   $DB{$otherlist}{$r}{$otherField} = $listname."[ids=".$1.",$myID]";
						   } else {
							   $DB{$otherlist}{$r}{$otherField} = $listname."[ids=$myID]";
						   }
					   } else {                                    # single entry on the other side
						   $DB{$otherlist}{$r}{$otherField} = $listname."[id=$myID]";					   
				   		   # in this special case we have to force that the link on our side is still unique
						   for(my $s =  1; $s <= $DB{$listname}{d}{numentries}; $s++) { 
							   $DB{$listname}{$s}{$field} =~ s/(=|,)$otherID(,|\])/$1/g;
							   $DB{$listname}{$s}{$field} =~ s/=$/=\]/g;
							   $DB{$listname}{$s}{$field} =~ s/,$/\]/g;
						   }
				   	   }
				   }
			   } else {    # remove myID from this entry in the other list
				   $DB{$otherlist}{$r}{$otherField} =~ s/(=|,)$myID(,|\])/$1/g;
				   $DB{$otherlist}{$r}{$otherField} =~ s/=$/=\]/g;
				   $DB{$otherlist}{$r}{$otherField} =~ s/,$/\]/g;
			   }
		   }
		   SaveDatabase($otherlist) if ($otherlist ne $listname);
	   }
	   
	} elsif ($fieldtype =~ /^listentr(y|ies) (.*?)\./) {     # default, non-bidirectional link
	   $otherlist = $2;   
	}
	
	return $otherlist.$selectedIDs;
}

############################################################# delete entry
sub Delete {
	my $listname = $_[0];
	my $pos = $_[1];
	my $try;								# delete files and bidirectional links that belonged to this entry
											while (my ($k, $field) = each(%{ $DB{$listname}{fields} })) {
												my $fieldtype = $DB{$listname}{fieldtypes}{$k};
												my $id = $DB{$listname}{$pos}{'id'};
												if ($fieldtype eq 'file' && exists $DB{$listname}{$pos}{$field}) {
													$try = unlink("$datadir/$listname/$DB{$listname}{$pos}{$field}");
												}
												if ($fieldtype =~ /^list /) {		# delete auto list
													$try = unlink("$datadir/$listname/$field-$id.txt");
													DeleteDir("$datadir/$listname/$field-$id");
												}
												if ($fieldtype =~ /^listentr(y|ies) (bidirect.*?) (.*?)\./) {		# delete bidirectional links that pointed to this entry
													UpdateBidirectionalLinks("", $listname, $field, $fieldtype, $id);
												}
											}
	for(my $r = $pos; $r < $DB{$listname}{d}{numentries}; $r++) { 
		%{ $DB{$listname}{$r} } = %{ $DB{$listname}{$r+1} };
	}
	$DB{$listname}{d}{numentries} --; # num entries
}

############################################################# swap two entries
sub SwapEntries {
	my $listname = $_[0];
	my $pos1 = $_[1];
	my $pos2 = $_[2];
	
	# swap rows
	my %temp = %{ $DB{$listname}{ $pos1 } };
	%{ $DB{$listname}{ $pos1 } } = %{ $DB{$listname}{ $pos2 } };
	%{ $DB{$listname}{ $pos2 } } = %temp;
}


############################################################# receive file upload
sub ReceiveFileUpload {
	my $listname  = $_[0];	
	my $k  = $_[1];	
	my $entryID  = $_[2];	
	my $filename = $cgi->param("field$k");
	my $filehandle = $cgi->upload("field$k");
	my $oldfile = $cgi->param("oldfile$k");
	my $extension;
			
	if ($filename) {
		$filename =~ s/^.*(\\|\/)(.*?)$/$2/; # strip the remote path and keep the filename
		
		if($filename =~ /^([a-zA-Z0-9\.\-\+\_\&\ \[\]\(\)]+)\.([a-zA-Z0-9]{2,5})$/) {
			$filename = $1;
			$extension = lc($2);
			if(!grep(/^$extension$/,@extensions)) { Error("$lang{'filextension1'} \".$extension\" $lang{'filextension2'}<br/><br/><i>$lang{'filextension3'}</i><br/>@extensions<br/> "); }
		} else {
			Error("$lang{'invalidfilename1'} \"$filename\", $lang{'invalidfilename2'}<br/><br/><i>$lang{'filextension3'}</i><br/>@extensions<br/> ");
		}
		if ($DB{$listname}{options}{'unique-upload-filenames'}) {
			$filename = $entryID;
		}
		
		if (not(-e "$datadir/$listname")) { my $try = mkdir("$datadir/$listname",0755); }
		if (-e "$datadir/$listname/$filename.$extension" && "$filename.$extension" ne $oldfile) { Error($lang{'filenameusedyet'}); }
		
		my $try = unlink("$datadir/$listname/$oldfile");
		
		open(LOCAL, ">$datadir/$listname/$filename.$extension") || Error("$lang{'nowriteaccess'} $datadir/$listname/$filename.$extension");
		binmode(LOCAL);
		binmode($filehandle);
		while(<$filehandle>) {
			print LOCAL $_;
		}
		close LOCAL;
		$try = chmod(0644,"$datadir/$listname/$filename.$extension");
		
		return "$filename.$extension";
	} elsif ($cgi->param("remove$k")) {
		my $try = unlink("$datadir/$listname/$oldfile");
		return "";
	} else {
		return $oldfile;
	}
}


############################################################# receive zip file of multiple files
sub ReceiveMultiFileUpload {
	my $listname  = $_[0];
	my $filename = $cgi->param("zipfile");
	my $filehandle = $cgi->upload("zipfile");
	my $extension;
			
	if ($filename) {
		$filename =~ s/^.*(\\|\/)(.*?)$/$2/; # strip the remote path and keep the filename
		
		if($filename =~ /^([a-zA-Z0-9\.\-\+\_\&\ \[\]\(\)]+)\.zip$/) {
			$filename = $1; $extension = "zip";
		} else {
			Error("$lang{'invalidfilename1'} \"$filename\", $lang{'invalidfilename2'}<br/><br/><i>$lang{'filextension3'}</i><br/>.zip<br/> ");
		}
																			# receive zip file
		open(LOCAL, ">$datadir/$filename.$extension") || Error("$lang{'nowriteaccess'} $datadir/$filename.$extension");
		binmode(LOCAL);	binmode($filehandle);
		while(<$filehandle>) { print LOCAL $_; }
		close LOCAL;
		my $dir = "$datadir/tmp"; my $try = mkdir($dir,0755);				# create temporary directory
		
		my $cmd = "unzip -j \'$datadir/$filename.$extension\' -d \'$dir\'";	# the unzip command
		$try = qx($cmd);
		if (!$try) { Debug("Could not unzip the file: $cmd <br/>Either the .zip archive is corrupt, or the Server does not support the unzip operation in a correct way."); }
		
		$try = unlink("$datadir/$filename.$extension");						# delete archive
		
		my $pos; my $myfield; my $myparam; my %Data = ();
		while (my ($k, $field) = each(%{ $DB{$listname}{fields} })) {
			if ($DB{$listname}{fieldtypes}{$k} eq 'file') { $myfield = $field; $myparam = "field$k"; }
		}
																			# add files to list
		opendir(DIR, $dir) || Error("Could not use temporary directory $dir."); my @files = readdir(DIR); closedir(DIR);
		foreach(@files) { if ($_ !~ /^\./) {
			if($_ =~ /^([a-zA-Z0-9\.\-\+\_\&\ \[\]\(\)]+)\.([a-zA-Z0-9]{2,5})$/) {
				$filename = $1; $extension = lc($2);
				if(grep(/^$extension$/,@extensions) && not(-e "$datadir/$listname/$_")) {
					$Form{$myparam} = "$filename.$extension";
					$Form{'action'} = "addit";
					$pos = ++ $DB{$listname}{d}{numentries};
					$entryID = AddItOrSaveIt( $listname, $pos );
					if ($DB{$listname}{options}{'unique-upload-filenames'}) {	# option to automatically rename all received files by their id
						$filename = $entryID;
						$DB{$listname}{$pos}{$myfield} = "$filename.$extension";
					}
					$try = move("$dir/$_","$datadir/$listname/$filename.$extension");
				}
			}
			$try = unlink("$dir/$_");
		} }		
		$try = rmdir($dir);
		SaveDatabase($listname);
	}
}


############################################################# send notification mail to list owner
sub SendNotification {	
	my $listname  = $_[0];			
	my $fromemail = $_[1];
	my $key =       $_[2];
	my $pos =       $_[3];
	
	if (exists $DB{$listname}{options}{notify}) {	
		my $recipient = $DB{$listname}{options}{notify};
		
		open (MAIL, "|$mailprog $recipient") || Error("Can't open mail programm $mailprog!");
		
		print MAIL "From: dynamo\@$sysname (Dynamo - Programm)\n";
		if ($fromemail) {			# if user typed his email adress
			print MAIL "Reply-to: $fromemail\n";
		}
		print MAIL "Subject: $sysname $listname - $lang{'notifysubject'}\n";
		print MAIL "Mime-Version: 1.0\nContent-Type: text/html\n\n";
		print MAIL qq|	<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"> <html xmlns=\"http://www.w3.org/1999/xhtml\"><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></head>
						<body bgcolor=\"#EEEEF0\">
						<p>$lang{'notifytext1'} <b>$listname</b>$lang{'notifytext2'}</p>
						<table align=center border=0>
					 |;
		for(my $k = 1; $k <= values(%{ $DB{$listname}{fields} }) - 4; $k++) { 
			my $field = $DB{$listname}{fields}{$k};
			$field = $lang{'datecreated'} if ($field eq 'created'); $field = $lang{'datechanged'} if ($field eq 'changed'); $field = $lang{'ip'} if ($field eq 'ip');
			print MAIL "<tr><td valign=top><i>$field:</i></td><td>  ".DisplayField($listname,$k,$pos)."</td></tr>\n";
		}
		print MAIL qq|	</table>
						<p><small>$lang{'numentries'} $DB{$listname}{d}{numentries}</small></p>
						<p><a href=\"$script?template=$Form{'template'}&action=admin&list=$listname\"><b>$lang{'managelist1'}</b> $listname <b>$lang{'managelist2'}</b></a>
						<p><a href=\"$script?template=$Form{'template'}&action=delete&list=$listname&key=$key\"><b>$lang{'deleteentry'}</b></a>
						<p align=right><small>dynamo system by <small><a href=\"http://m8j.net\">&lt;]{MJ}[&gt;</a></small></small></p>
						</body></html>
					 |;
		close (MAIL);
	}
}

############################################################# send confirmation (= authorisation to edit) mail
sub SendConfirmation {				
	my $listname = $_[0];			
	my $email =    $_[1];
	my $key =      $_[2];
	my $pos =      $_[3];
	
	if ($email) {			# if user typed his email adress
		open (MAIL, "|$mailprog $email") || Error("Can't open mail programm $mailprog!");
		
		print MAIL "From: dynamo\@$sysname (Dynamo - Programm)\n";
		print MAIL "Subject: $sysname $listname - $lang{'confirmsubject'}\n";
		print MAIL "Mime-Version: 1.0\nContent-Type: text/html\n\n";
		print MAIL qq|	<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"> <html xmlns=\"http://www.w3.org/1999/xhtml\"><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></head>
						<body bgcolor=\"#EEEEF0\">
						<p>$lang{'confirmtext'} <b>$listname</b>:</p>
						<table align=center border=0>
					 |;
		for(my $k = 1; $k <= values(%{ $DB{$listname}{fields} }) - 5; $k++) { 
			my $field = $DB{$listname}{fields}{$k};
			$field = $lang{'datecreated'} if ($field eq 'created'); $field = $lang{'datechanged'} if ($field eq 'changed'); $field = $lang{'ip'} if ($field eq 'ip');
			print MAIL "<tr><td valign=top><i>$field:</i></td><td>  ".DisplayField($listname,$k,$pos)."</td></tr>\n";
		}
		print MAIL qq|	</table>
						<p>$lang{'confirmtext2'}
						<p><a href=\"$script?action=editform&list=$listname&template=$Form{'template'}&key=$key\"><b>$lang{'editmyentry1'}</b> $lang{'editmyentry2'} $listname<b>$lang{'editmyentry3'}</b></a>
						<p><a href=\"$script?action=delete&list=$listname&template=$Form{'template'}&key=$key\"><b>$lang{'deletemyentry'}</b></a>
						<p align=right><small>dynamo system by <small><a href=\"http://m8j.net\">&lt;]{MJ}[&gt;</a></small></small></p>
						</body></html>
					 |;
		close (MAIL);
	}
}


############################################################# render admin list
sub RenderAdminList {			
	my $listname = $_[0];	
	
	my ($pos1, $pos2);
	my $content = ""; my $cols; my $list_entry_layout; my $insert_cursor_layout = "";
	my $var = GetAdminVariable($listname);
	my ($start, $stop) = CalcStartStop($listname);
	
	my $admincodetop = qq|	
			<form method="post" action="$script" id="mainform">
			<input type="hidden" name="list" value="$listname">
			<input type="hidden" name="template" value="$Form{'template'}">
			<input type="hidden" name="action" value="delete">
					\n|;
						
	my $admincodebottom = qq|	
			$bottom<br/>
			<table style="border:0;"><tr><td style="width:300px;">
			<input name="button" type="image" src="$button{'deleteselected'}" border="0" align="absmiddle">
			<script type="text/javascript">
				function toggleChecked() {
				  f = document.getElementById("mainform"); if (f==0) { f = document.all.mainform; }
				  for (var i = 0; i < f.elements.length; i++) {
					var e = f.elements[i];
					if ((e.name != 'chkxall') && (e.type == 'checkbox')) {
						e.checked = f.chkxall.checked;
					}
				  }
				}
			</script>
			<!-- chkxall -->
			</td><td style="text-align:right;">
			<a href="$script?list=$listname&template=$Form{'template'}&action=logout"><img src="$button{'logout'}" border="0" align="absmiddle"></a>
			</td></tr></table>
			</form>
					\n|;			# check/uncheck all functionality
	if ($stop-$start >= 7) { $admincodebottom =~ s/<!-- chkxall -->/&nbsp; &nbsp; <input type="checkbox" name="chkxall" id="chkxall" value="0" onclick="toggleChecked();" \/> <label for="chkxall">$lang{'chkxall'}<\/label>/;}
	
									# multi file upload functionality
	if ($DB{$listname}{options}{'multifileupload'}) {
		$admincodetop = qq|	
			<div class="dynamo-rightcolumn">
			$lang{'uploadzip'}:
			<form method="post" action="$script" enctype="multipart/form-data" onsubmit="document.getElementsByName('zbutton')[0].src = '$button{'wait'}'; document.getElementsByName('zbutton')[0].disabled = true;">
			<input type="hidden" name="list" value="$listname">
			<input type="hidden" name="template" value="$Form{'template'}">
			<input type="hidden" name="action" value="uploadzip">
			<input type="file" name="zipfile" size="20" accept="*/*"><img src="$button{'wait'}" width="1" height="1" border="0"><br/>
			<input name="zbutton" type="image" src="$button{'addit'}" border="0" align="absmiddle">
			</form>
			</div> \n $admincodetop|;
	}

	if ($var) { 					# we use the specified template for administration
		($pos1, $pos2) = ScanNestedTags($template,"{for ","{/for}","{for all entries $var in list $listname");
		$list_entry_layout = substr($template, $pos1, $pos2 - $pos1);
		$list_entry_layout =~ s/^\{.*?\}((?s).*)\{\/.*?\}$/$1/; # only keep content
		
		if ($template =~ s/\{marker insert $var\}((?s).*?)\{\/marker\}//) { $insert_cursor_layout = $1; }
	} else {						# no suitable template available, so we generate standard html
		$list_entry_layout = "<tr class=\"data\">\n  <td>{marker select }</td>\n  <td>{marker edit }</td>\n";
		
		$cols = $DB{$listname}{d}{numfields} + 3;
		$insert_cursor_layout = "<tr class=\"ins\">"; for(my $k = 1; $k <= $cols+1; $k++) { $insert_cursor_layout .= "<td></td>"; }
		$insert_cursor_layout .= "<td align=\"right\">\{insert\}</td></tr>\n\n";
		
		$content = "<table class=\"dynamo-list\" cellspacing=\"0\"><thead>\n<tr><th scope=\"col\"></th><th scope=\"col\"></th>\n";
		for(my $k = 1; $k <= $cols - 3; $k++) { $content .= "<th scope=\"col\">$DB{$listname}{fields}{$k}</th>\n"; }
		$content .= "<th scope=\"col\">$lang{datecreated}</th><th scope=\"col\">$lang{datechanged}</th><th scope=\"col\">$lang{ip}</th>\n";
		$content .= "</tr></thead><tbody>";
	}

	my $r;
	for($r = $start; $r <= $stop; $r++) { 
					my $deleteId = $DB{$listname}{$r}{'id'};
					my $checkbox = "<input type=\"checkbox\" name=\"delete$deleteId\" value=\"true\" title=\"$lang{checkdel}\"> \n"; 
					my $editbut = "<a href=\"$script?list=$listname&template=$Form{'template'}&action=editform&position=$r\" class=\"noline\" title=\"$lang{editthis}\"><img src=\"$button{'edit'}\" border=\"0\"></a> \n";
		my $entrycode = $list_entry_layout;
			$entrycode =~ s/\{marker select $var\}/$checkbox/g;
			$entrycode =~ s/\{marker edit $var\}/$editbut/g;
					my $insertbut = "<a href=\"$script?list=$listname&template=$Form{'template'}&action=addform&position=$r\" class=\"noline\" title=\"$lang{addnew}\">";
		my $cursorcode = $insert_cursor_layout;
			$cursorcode =~ s/\{insert(|-h)\}/$insertbut<img src=\"$button{'insert'}\" border=\"0\"><\/a> \n/g;
			$cursorcode =~ s/\{insert(|-)v\}/$insertbut<img src=\"$button{'insertv'}\" border=\"0\"><\/a> \n/g;
	
		if ($var) { 								# use template to display entry data
			$entrycode = RenderEntry($listname,$r,$entrycode,$var);
		} else {									# generate generic entry data displaying
			for(my $k = 1; $k <= $cols; $k++) {
				$entrycode .= "  <td>".DisplayField($listname,$k,$r,300)."</td>\n";
			}
			$entrycode .= "</tr>\n\n";
		}
		
		$content .= $cursorcode;					# add the generated code for this data row
		$content .= $entrycode;
	}	
	
					my $insertbut = "<a href=\"$script?list=$listname&template=$Form{'template'}&action=addform&position=$r\" class=\"noline\" title=\"$lang{addnew}\">";
		my $cursorcode = $insert_cursor_layout;
			$cursorcode =~ s/\{insert(|-h)\}/$insertbut<img src=\"$button{'insert'}\" border=\"0\"><\/a> \n/g;
			$cursorcode =~ s/\{insert(|-)v\}/$insertbut<img src=\"$button{'insertv'}\" border=\"0\"><\/a> \n/g;
	$content .= $cursorcode;
	
	if ($var) { 									# we use the specified template for administration
		substr($template, $pos1, $pos2 - $pos1) = $content;
		$template =~ s/\{adminmarker $var\}((?s).*?)\{\/adminmarker\}/$admincodetop $1 $admincodebottom/;
	} else {										# generate standard html
		$content .= "</tbody></table>\n\n";
		$template = StandardHTML("$lang{'list'} '$listname'", $admincodetop.$content.$admincodebottom);
	}
}

############################################################# display field data as html
sub DisplayField {					
	my $listname   = $_[0];				
	my $k          = $_[1];				
	my $row        = $_[2];			
	my $truncate   = $_[3];		
	
	my $field = $DB{$listname}{fields}{$k};
	my $fieldtype = $DB{$listname}{fieldtypes}{$k};
	my $value = $DB{$listname}{$row}{$field}; if ($truncate && length($value)>$truncate) { $value = substr($value,0,$truncate)." ..."; $value =~ s/</&lt;/g; $value =~ s/>/&gt;/g; }
	my $text = "";
				
	if      ($fieldtype eq 'checkbox') {
		$text .= $value ? "<input type=\"checkbox\" disabled checked>" : "<input type=\"checkbox\" disabled>";
	} elsif ($fieldtype eq 'file') {
		$text .= $value ? "<a href=\"$dataurl/$listname/$value\" target=\"_blank\"><img src=\"$button{'file'}\" border=\"0\" align=\"absmiddle\"> $value</a>" : "&nbsp;";
	} elsif ($fieldtype eq 'url') {
		$text .= "<a href=\"http://$value\" target=\"_blank\">$value</a>";
	} elsif ($fieldtype =~ /^list /) {
		$text .= "$lang{'list'}<br/><a href=\"$script?list=$value&action=admin\" onclick=\"window.open(this.href,'pup','width=700,height=700,left=300,top=25,menubar=no,location=no,resizable=yes,dependent=yes,scrollbars=yes'); return false;\" target=\"pup\">$value</a>";
	} elsif ($fieldtype =~ /^listentries (.*)\.(.*?)$/) {
		my $otherlist = $1; my $flds = $2; my @fields =  split(/\,/, $flds); $otherlist =~ s/^bidirect.*? //;			
		LoadDatabase($otherlist);
		$text .= "\n    <small>$lang{listentriesof} '$otherlist':</small><br/><ul>\n";
		for(my $r = 1; $r <= $DB{$otherlist}{d}{numentries}; $r++) {
			my $identifier = ""; foreach my $fld (@fields) { $identifier .= $DB{$otherlist}{$r}{$fld}.", "; } chop($identifier);chop($identifier);
			if ($value =~ /(=|,)$DB{$otherlist}{$r}{'id'}(,|\])/) { $text .= "<li><a href=\"$script?list=$otherlist&action=editform&position=$r\" onclick=\"window.open(this.href,'pup','width=700,height=700,left=300,top=25,menubar=no,location=no,resizable=yes,dependent=yes,scrollbars=yes'); return false;\" target=\"pup\">$identifier</a>\n"; }
		}
		$text .= "    </ul>\n  ";
	} elsif ($fieldtype =~ /^listentry (.*)\.(.*)/) {
		my $otherlist = $1; my $flds = $2; my @fields =  split(/\,/, $flds); $otherlist =~ s/^bidirect.*? //;
		LoadDatabase($otherlist);
		$text .= "\n    <small>$lang{listentryof} '$otherlist':</small><br/><ul>\n";
		for(my $r = 1; $r <= $DB{$otherlist}{d}{numentries}; $r++) {
			my $identifier = ""; foreach my $fld (@fields) { $identifier .= $DB{$otherlist}{$r}{$fld}.", "; } chop($identifier);chop($identifier);
			if ($value =~ /(=|,)$DB{$otherlist}{$r}{'id'}(,|\])/) { $text .= "<li><a href=\"$script?list=$otherlist&action=editform&position=$r\" onclick=\"window.open(this.href,'pup','width=700,height=700,left=300,top=25,menubar=no,location=no,resizable=yes,dependent=yes,scrollbars=yes'); return false;\" target=\"pup\">$identifier</a>\n"; }
		}
		$text .= "    </ul>\n  ";
	} elsif ($field eq 'created' || $field eq 'changed') {
		my @date = DatePrint($value);
		$text .= "<small>".$date[0]."</small>";
	} elsif ($field eq 'id' || $field eq 'ip') {
		$text .= "<small>$value</small>";
	} else {
		$text .= $value ? $value : "&nbsp;";
	}
	return $text;
}

############################################################# determine if template supports list administration functionality for the given list
sub GetAdminVariable {							
	my $listname = $_[0];
	my $text = $template;
	
	while ($text =~ s/\{for all entries (.*?) in list (.*?)\}//) {
		my ($var, $ln) = ($1, $2);
		if ($ln =~ /^$listname/ && $text =~ /\{adminmarker $var\}(?s).*\{\/adminmarker\}/) { return $var; }
	}
	if ($text =~ /\{adminmarker (.*?)\}(?s).*\{\/adminmarker\}/) { return $1;
	} else { return ""; }
}

############################################################# returns if viewing this list is allowed
sub CheckIfViewAllowed {	
	my $listname = $_[0];

	my $allowed = (HasPassword($cgi->remote_host(), $DB{$listname}{options}{viewpwd}) || $pwd eq $DB{$listname}{options}{viewpwd});
	if ($allowed) {	
		AddPassword($cgi->remote_host(), $DB{$listname}{options}{viewpwd});
		return 1;
	} elsif (!$DB{$listname}{d}{asked}) { $DB{$listname}{d}{asked} = "true";
													# ask password
		my $text = AskPassword("$lang{'viewpwd1'} \"$listname\"$lang{'viewpwd2'}:");
		my $var = GetAdminVariable($listname);
		if ($var) { $template =~ s/\{adminmarker $var\}(?s).*?\{\/adminmarker\}/$text/;
		} else {    $template = StandardHTML("",$text); }
	}
	return 0;
}

############################################################# whether the IP has entered a (correct) password before
sub HasPassword {
	my $hostname = $_[0];
	my $pwd = $_[1]; return 1 if $pwd eq '';
	
	open(DATA, "$datadir/dynamo-admin-data.txt"); my @rows = <DATA>; close(DATA);
		
	foreach(@rows) {
		return ($_ =~ $pwd) if $_ =~ $hostname;
	}
	return 0;
}
############################################################# add a correct password to an ip
sub AddPassword {								
	my $hostname = $_[0];				
	my $pwd = $_[1];
	my $exists = 0;
	
	if ($pwd) {
		open(DATA, "$datadir/dynamo-admin-data.txt"); my @rows = <DATA>; close(DATA);
			
		open(DATA, ">$datadir/dynamo-admin-data.txt") || Error("$lang{'nowriteaccess'} dynamo-admin-data.txt");
		foreach my $line (@rows) { chomp($line);
			print DATA $line;
			if ($line =~ $hostname) { print DATA $line !~ $pwd ? "|$pwd" : ""; $exists = 1; }
			print DATA "\n";
		}
		unless ($exists) { print DATA "$hostname|$pwd\n"; }
		close(DATA);
	}
}
############################################################# logout ip
sub Logout {								
	my $hostname = $_[0];
	
	open(DATA, "$datadir/dynamo-admin-data.txt"); my @rows = <DATA>; close(DATA);
		
	open(DATA, ">$datadir/dynamo-admin-data.txt") || Error("$lang{'nowriteaccess'} dynamo-admin-data.txt");
	foreach(@rows) {
		unless ($_ =~ $hostname) { print DATA $_; }
	}
	close(DATA);
}
############################################################# ask password
sub AskPassword {							
	my $question = $_[0];	
										
	my $code = qq|		<form method="post" action="$script">                \n|;
	foreach my $key (keys(%Form)) {
		if ($key ne "password") {	$code .=   "<input type=\"hidden\" name=\"$key\" value=\"$Form{$key}\">";	}
	}
	$code .=   qq|		<p>&nbsp;</p>
						<p>$question</p>
						<p><input type="password"  name="password" size="15"></p>
						<p><input name="button" type="image" src="$button{'submit'}" border="0"></p>
						</form>
						\n|;
	return $code;
}


############################################################# copy a directory
sub CopyDir {										
	my $from = $_[0]; my $to = $_[1];	
	my $try = mkdir($to,0755);
	if (-d $from) {
		opendir(DIR,"$from"); my @Entries = readdir(DIR); closedir(DIR);
		foreach (@Entries) {
			if (-d "$from/$_") { CopyDir("$from/$_","$to/$_") if ($_ !~ /^\./); }
			else {				 $try = copy("$from/$_","$to/$_"); }
		}
	}
}
############################################################# delete a directory
sub DeleteDir {										
	my $name = $_[0]; my $try;
	if (-d $name) {
		opendir(DIR,"$name"); my @Entries = readdir(DIR); closedir(DIR);
		foreach (@Entries) {
			if (-d "$name/$_") { DeleteDir("$name/$_") if ($_ !~ /^\./); }
			else {				 $try = unlink("$name/$_"); }
		}
	}
	$try = rmdir($name);
}
############################################################# backup all data files
sub Backup {										
	if ($do_backup ne "never") {
		my $days = 1; $days = 7 if ($do_backup eq "weekly");
		my $backupdir = "$datadir/backup"; my $try; my $cmd;
		
		if (!(-d $backupdir) || (-M $backupdir) >= $days) {
			unless (-d $backupdir) { $try = mkdir($backupdir,0755);	}		# create backup directory if not existent
			
			my $backupname = "backup-".time.".zip"; 
			if ($backup_text_only eq "true") {
				$cmd = "zip -r \'$backupdir/$backupname\' \'$datadir\' -i \*.txt";	# the zip command, for .txt files only
			} else {
				$cmd = "zip -r \'$backupdir/$backupname\' \'$datadir\' -x $backupdir/\*";	# the zip command, for all file types
			}
			$try = qx($cmd);
			if (!$try) {
				Debug("Backup failed: Could not create zip archive for: $cmd <br/>Maybe the Server does not support the zip operation?");
			} else {
				Debug("$try \n Backup succeeded. Results stored in $backupdir/$backupname.");
				opendir(DIR,"$backupdir"); my @Entries = readdir(DIR); closedir(DIR);		# delete old backups
				foreach $filename (@Entries) {
					if ($filename ne $backupname && $filename !~ /^\./) {
						if (!$number_of_backups_to_keep || (-M $backupdir) >= $days*$number_of_backups_to_keep) {
							$try = unlink("$backupdir/$filename");
						}
					}
				}
			}
		}
	}
}

############################################################# encrypt function for IDs etc
sub cryptId {								
	my $id = $_[0];
	return crypt(substr($id,0,8),"dynamo").crypt(substr($id,8,8),"dynamo");
}

############################################################# creates a new ID (natural number, incrementally for each list, starting from 1)
sub GetNextUnusedId {						
	my $listname = $_[0];
	
	$DB{$listname}{options}{'next-unused-id'} = 1 unless ($DB{$listname}{options}{'next-unused-id'});	
	return $DB{$listname}{options}{'next-unused-id'} ++;
}


############################################################# language static values
sub SetLanguage {	
									# language english
	my %en = (
			couldnotloadconfig =>	"Failed to load the Dynamo configuration file (dynamo-config.pl). Please make sure that file is available in the same directory than the script. Error description:",
			deleted1 =>				"Your entry in",
			deleted2 =>				"has been deleted successfully.",
			gotolist =>				"Go to",
			searchresult =>			"The following results were found for your search query:",
			viewpwd1 =>				"Password for viewing",
			viewpwd2 =>				"",
			adminpwd1 =>			"Password for applying changes to",
			adminpwd2 =>			"",
			selectmultiple =>		"Hold down [Control] or [Apple] key to select more than one entry.",
			removefile =>			"Remove file",
			filextension1 =>		"File extension",
			filextension2 =>		"is not allowed, please ask the site administrator if this sounds bad to you.",
			filextension3 =>		"Allowed file extensions:",
			invalidfilename1 =>		"Invalid file name",
			invalidfilename2 =>		"Please don't use any special caracters and use one of the allowed file extension",
			filenameusedyet =>		"This filename is already in use for another file of this list. Please choose another filename.",
			uploadzip =>			"Upload multiple files in a zip file",
			nowriteaccess =>		"No write access for file",
			list =>					"List",
			listentriesof =>		"Listentries from",
			listentryof =>			"Listentry of",
			addentry1 =>			"Adding a new entry to list",
			addentry2 =>			"",
			editentry1 =>			"Edit entry of list",
			editentry2 =>			"",
			editthis =>				"Edit this entry",
			addnew =>				"Add a new entry at this position",
			checkdel =>				"Check box if you want to delete this entry",
			datecreated =>			"Date Created",
			datechanged =>			"Date Changed",
			ip =>					"IP",
			notifysubject =>		"New entry",
			notifytext1 =>			"Someone added a new entry to your list",
			notifytext2 =>			":",
			numentries =>			"Number of entries in the list:",
			managelist1 =>			"Manage list",
			managelist2 =>			"",
			deleteentry =>			"Directly delete this entry",
			confirmsubject =>		"Your entry",
			confirmtext =>			"Your entry in",
			confirmtext2 =>			"Please use the following links if you want to change or delete your personal entry:",
			editmyentry1 =>			"Edit my entry",
			editmyentry2 =>			"in",
			editmyentry3 =>			"",
			deletemyentry =>		"Directly delete my entry",
			couldnotbefoundin1 =>	"could not be found in",
			couldnotbefoundin2 =>	".",
			sectemplates1 =>		"For security reasons (templates may contain perl code) it's not allowed to execute templates in the data directory.<br/><br/>Your current data directory is",
			sectemplates2 =>		"and your base directory is",
			sectemplates3 =>		"Please make sure these are distinct.",
			chkxall =>				"Select all",
			error =>				"Error",
			back =>					"back",
			edit =>					"edit",
			noentryfound =>			"No specified entry was found for list ",
			nospam =>				"Spamfilter blocked this operation. Please do not use direct links through <i>http://</i>.",
			wrongcaptcha =>			"Wrong answer to the security question. Please try again",
			monthnames =>			[ "January","February","March","April","May","June","July","August","September","October","November","December" ],
			weekdaynames =>			[ "Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday" ]
		  ); 
									# language deutsch
	my %de = (
			couldnotloadconfig =>	"Leider konnte die Dynamo-Konfigurationsdatei (dynamo-config.pl) nicht geladen werden. Bitte stellen sie sicher dass sich diese im selben Verzeichnis wie das Skript befindet. Fehlerursache:",
			deleted1 =>				"Ihr Eintrag in",
			deleted2 =>				"wurde erfolgreich gel&ouml;scht.",
			gotolist =>				"Gehe zu",
			searchresult =>			"Zu ihrem Suchbegriff wurden folgende Resultate gefunden:",
			viewpwd1 =>				"Passwort um",
			viewpwd2 =>				" anzuzeigen",
			adminpwd1 =>			"Passwort um &Auml;nderungen an",
			adminpwd2 =>			" vorzunehmen",
			selectmultiple =>		"Zum Ausw&auml;hlen mehrerer Eintr&auml;ge [Strg] oder [Apfel] gedr&uuml;ckt halten.",
			removefile =>			"Datei entfernen",
			filextension1 =>		"Die Dateierweiterung",
			filextension2 =>		"ist nicht erlaubt. Bitte nehmen sie mit dem Administrator Kontakt auf, falls Sie dies &auml;ndern m&ouml;chten.",
			filextension3 =>		"Erlaubte Dateierweiterungen:",
			invalidfilename1 =>		"Illegaler Dateiname",
			invalidfilename2 =>		"Bitte ben&uuml;tzen sie keine Sonderzeichen und verwenden sie eine der erlaubten Dateierweiterungen.",
			filenameusedyet =>		"Es existiert bereits eine andere Datei gleichen Namens in dieser Liste. Bitte verwenden sie einen anderen Dateinamen.",
			uploadzip =>			"Mehrere Dateien als Zip-Datei hochladen",
			nowriteaccess =>		"Kein Schreibzugriff f&uuml;r Datei",
			list =>					"Liste",
			listentriesof =>		"Listeneintr&auml;ge von",
			listentryof =>			"Listeneintrag aus",
			addentry1 =>			"Neuen Eintrag zu Liste",
			addentry2 =>			"hinzuf&uuml;gen",
			editentry1 =>			"Eintrag der Liste",
			editentry2 =>			"bearbeiten",
			editthis =>				"Diesen Eintrag bearbeiten",
			addnew =>				"Neuen Eintrag an dieser Stelle einf&uuml;gen",
			checkdel =>				"Box markieren um diesen Eintrag zu l&ouml;schen",
			datecreated =>			"Erstellungsdatum",
			datechanged =>			"&Auml;nderungsdatum",
			ip =>					"IP",
			notifysubject =>		"Neuer Eintrag",
			notifytext1 =>			"Jemand hat einen neuen Eintrag zu ihrer Liste",
			notifytext2 =>			" hinzugef&uuml;gt:",
			numentries =>			"Anzahl Eintr&auml;ge in der Liste:",
			managelist1 =>			"Liste",
			managelist2 =>			"bearbeiten",
			deleteentry =>			"Diesen Eintrag direkt l&ouml;schen",
			confirmsubject =>		"Ihr Eintrag",
			confirmtext =>			"Ihr Eintrag in",
			confirmtext2 =>			"Mit den folgenden Links k&ouml;nnen Sie ihren Eintrag jederzeit wieder ver&auml;ndern oder l&ouml;schen:",
			editmyentry1 =>			"Meinen Eintrag",
			editmyentry2 =>			"in",
			editmyentry3 =>			" bearbeiten",
			deletemyentry =>		"Eintrag direkt l&ouml;schen",
			couldnotbefoundin1 =>	"konnte nirgens in",
			couldnotbefoundin2 =>	"gefunden werden.",
			sectemplates1 =>		"Aus Sicherheitsgr&uuml;nden (Templates k&ouml;nnen Perl-Code enthalten) ist es nicht erlaubt im Datenverzeichnis Templates auszuf&uuml;hren.<br/><br/>Ihr aktuelles Datenverzeichnis ist",
			sectemplates2 =>		"und ihr Basisverzeichnis ist",
			sectemplates3 =>		"Stellen sie bitte sicher dass diese beiden verschieden sind.",
			chkxall =>				"Alle ausw&auml;hlen",
			error =>				"Fehler",
			back =>					"zur&uuml;ck",
			edit =>					"bearbeiten",
			noentryfound =>			"Es konnte kein Eintrag mit den gegebenen Spezifikationen gefunden werden. Liste: ",
			nospam =>				"Der Spamfilter erlaubt diese Operation nicht. Bitte keine direkten <i>http://</i>-Links verwenden.",
			wrongcaptcha =>			"Falsche Sicherheitsfrage, Bitte versuchen Sie es erneut.",
			monthnames =>			[ "Januar","Februar","M&auml;rz","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember" ],
			weekdaynames =>			[ "Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag","Sonntag" ]
		  ); 
	if ($language eq 'de') {
		%lang = %de;
	} else {
		%lang = %en;
	}
}


############################################################# redirect browser
sub RedirectTo {										
	my $location = $_[0];
	$location = $Form{'thanks'} if ($Form{'thanks'});
	
	if ($debugMode eq 'on' && $status) {   # just for debuging
	   print "Content-type: text/html\n\n";
	   print "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"> <html xmlns=\"http://www.w3.org/1999/xhtml\">";
	   print "<body><p><a href=\"$location\"><b>continue</b></a></p><h3>Dynamo Status:</h3><p>$status</p><p>".PrintScriptParameters()."</p><p><a href=\"$location\"><b>continue</b></a></p></body></html>";
	} else {
	   print "Content-type: text/html\n";  # redirect
	   print "Status: 302 Moved\n";
	   print "Location: $location\n\n";
    }
	exit;
}

############################################################# write header for html output
sub WriteHTML {										
	my $title = $_[0]; my $message = $_[1];

	print "Content-type: text/html\n\n";
	print qq| <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"> <html xmlns=\"http://www.w3.org/1999/xhtml\">
			<head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><title>$title</title><style type="text/css">body,p { font: 14px trebuchet ms,trebuchet,verdana,sans-serif; }</style></head><body>
			<p><p><a href=\"http://m8j.net/dynamo\"><img src=\"http://m8j.net/dynamo/logo.jpg\" align=middle border=0></a> &nbsp; &nbsp; &nbsp;
			<b><big>$title</big></b></p>
			<p>$message</p>
			<p><p><a href=\"javascript:history.back()\">$lang{'back'}</a></p>
			
			<small>$status</small>
			
			<!-- Dynamo v$version   http://www.m8j.net/dynamo -->\n
			</body></html> |;
	exit;
}

############################################################# alert output
sub Alert {				
	WriteHTML("Dynamo:",$_[0]);
}


############################################################# error output
sub Error {					
	WriteHTML("Dynamo $lang{'error'}:",$_[0]);
}

############################################################# debug output
sub Debug {					
	$status .= $_[0]." <br/>\n";
}

############################################################# standard html template with css
sub StandardHTML {			
	my $title = $_[0]; my $content = $_[1];	
	return qq|<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"> <html xmlns=\"http://www.w3.org/1999/xhtml\">
				<head><title>$title</title>
				<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
				<style type="text/css">
				<!--
				body { 
					font: 12px "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
				}
				table.dynamo-list, table.dynamo-listentry {
					font: 11px "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
					clear: both;
					margin: 0;
					border-spacing: 0;
					border-collapse: separate;
					color: #000;
					empty-cells: show;
				}
				table.dynamo-list a {
					color: #2e5169;
					text-decoration: none;
					border-bottom: 1px dotted;
				}
				table.dynamo-list a:visited {
					color: #2e5169;
					font-weight: normal;
				}
				table.dynamo-list a:hover {
					border-bottom-style: solid;
				}
				table.dynamo-list a.noline {
					border-bottom: 0px;
				}
				table.dynamo-list thead th {
					border: 0px;
					padding: 6px 5px 6px 5px;
					font-weight: bold;
					text-align: left;
					background: #e8f1f7;
					border-bottom: 2px solid #8cabc0;
				}
				table.dynamo-list tr.data td {
					padding: 1px 5px 1px 5px;
					background: #e8f1f7;
					text-align: left;
					border: 1px solid #fff;
					border-right: 1px solid #8cabc0;
					border-width: 1px 1px 0px 0;
				}
				table.dynamo-list tr.ins td {
					padding: 0px 0px 0px 5px;
					background: #c5dbea;
					text-align: right;
					border: 1px solid #EBE5D9;
					border-right: 1px solid #8cabc0;
					border-width: 1px 1px 0 0;
				}
				table.dynamo-list tr.data:hover td {
					background: #fff;
				}
				table.dynamo-listentry tr td.data {
					background: #e8f1f7;
					border: 1px solid #fff;
					border-right: 1px solid #8cabc0;
					border-width: 1px 1px 0 0;
				}
				table.dynamo-list tr.data td ul {
					list-style-type: circle;
					margin: 0;
					padding: 0 0 0 15px;
				}
				table.dynamo-list tr.data td ul li {
					margin: 0;
					padding: 0;
					font: 10px "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
				}
				.dynamo-rightcolumn {
					background: #e8f1f7;
					float:right;
					margin:3px;
					padding:2px;
					margin-top:-20px;
				}
				// -->
				</style>
			  </head>
			  <body>
			    <h2>$title</h2>
				$content
				
				<!-- Dynamo status: $status -->
			  </body>
			  </html> 
		     |;
}


###################################
###################################
#              crap               #
###################################
###################################

sub PrintMemory {				# print memory structure:
		my $s .= "\n---- memory structure:\n";
		for my $k1 ( sort keys %DB ) {
			$s .= "$k1 (".keys(%{$DB{ $k1 }}).")\n";
	
			for my $k2 ( sort keys %{$DB{ $k1 }} ) {
				$s .= "    $k2 (".keys(%{$DB{ $k1 }{ $k2 }}).")\n";
	
				for my $k3 ( sort keys %{ $DB{ $k1 }{ $k2 } } ) {
					$s .= "        $k3 => ".$DB{ $k1 }{ $k2 }{ $k3 }."\n";
				}
			}
		}
		$s .= "---- end memory structure\n";
		return $s;
}

sub PrintScriptParameters {		# print script parameters:
		my $s = "Script parameters:\n";
		for my $k1 ( sort keys %Form ) {
			$s .= "    $k1 = ".$Form{$k1}.", \n";
		}
		return $s;
}
