<?php if (!defined('PmWiki')) exit();

/*	=== Attachtable ===
 *	Copyright 2007-2009 Eemeli Aro <eemeli@gmail.com>
 *
 *	Adds actions to delete & restore deleted attachments, as
 *	well as an attachlist replacement to use those actions,
 *	show file types and list attachment references.
 *
 *	Developed and tested using the PmWiki 2.2 series.
 *
 *	To install, add the following line to your configuration file :
		include_once("$FarmD/cookbook/attachtable.php");
 *
 *	For more information, please see the online documentation at
 *		http://www.pmwiki.org/wiki/Cookbook/Attachtable
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License, Version 2, as
 *	published by the Free Software Foundation.
 *	http://www.gnu.org/copyleft/gpl.html
 */

$RecipeInfo['Attachtable']['Version'] = '2009-04-20';

if ( !IsEnabled($EnableUpload,0) ) return;

## set a few values that we depend on in case upload.php hasn't been included yet
SDVA( $HandleAuth, array( 'upload' => 'upload', 'download' => 'read') );
SDV( $HandleAuth['postupload'], $HandleAuth['upload'] );

## set the default Attachtable authorization levels -- order is important for proper inheritance
SDVA( $HandleAuth, array(
	'delattach' => $HandleAuth['upload'],		# deleting a file from PmWiki while keeping it on disk
	'deldelattach' => $HandleAuth['attr'],		# deleting a file from disk
) );
SDVA( $HandleAuth, array(
	'undelattach' => $HandleAuth['delattach'],	# restoring a file
	'renameattach' => $HandleAuth['delattach']	# renaming a file
) );
SDVA( $HandleAuth, array(
	'downloaddeleted' => $HandleAuth['undelattach']	# download a deleted file
) );

XLSDV( 'en', array(
	'ULdelsuccess' => 'successfully deleted',
	'ULundelsuccess' => 'successfully restored',
	'ULrenamesuccess' => 'successfully renamed',
	'ULisdir' => 'can\'t act on directory',
	'ULdelfail' => 'file delete error',
	'ULrenamefail' => 'file rename error' ) );

SDVA( $HandleActions, array(
	'delattach' => 'HandleDeleteAttachment',
	'renameattach' => 'HandleRenameAttachment',
	'downloaddeleted' => 'HandleDownloadDeleted' ) );

SDV( $HTMLStylesFmt['attachtable'], "
.attachtable td, .attachtable th { padding-right: 0.8em; white-space: nowrap; }
.attachtable th {
	color: #999;
	font-style: italic;
	font-weight: normal;
	font-size: 0.8em;
	text-align: right;
}
.attachtable th a { color: #999; }
.attachtable a { text-decoration: none !important; }
.attachtable a:hover { text-decoration: underline !important; }
.attachtable .del, .attachtable .del a { color: #ccc; }
.attachtable tr:hover { background-color: #eee; }
.attachtable thead tr:hover th, .attachtable tfoot tr:hover th,
	.attachtable .del:hover, .attachtable .del:hover a { color: #666; }
.attachtable .orphan { color: #c00; font-weight: bold; }
.attachtable .missing { font-style: italic; }
.attachtable .refbox {
	display: none;
	position: absolute;
	margin: -0.75em 0 0 1.5em;
	padding: 0.25em;
	background-color: #eee;
	color: #000;
	border: 1px solid #ccc;
	text-align: left;
}
.attachtable .refbox ul, .attachtable .refbox li { padding: 0; margin: 0 0 0 1em; }
.attachtable .reftitle:hover .refbox { display: block; }
" );

Markup( 'attachtable', 'directives', '/\\(:attachtable\\s*(.*?):\\)/ei', "Keep(FmtAttachtable(\$pagename,PSS('$1')))");

/*
	$AttachtableLogFmt = array(
		'$SiteGroup.AllRecentChanges' =>
			'* [[Attach:{$Group}/$upname]]  . . . $CurrentTime by $AuthorLink: $upreport',
		'$Group.RecentChanges' =>
			'* [[Attach:$upname]]  . . . $CurrentTime by $AuthorLink: $upreport'
	);
*/


function AttachtableLog( $pagename, $upname, $upreport ) {
	global $RecentChangesFmt, $IsPagePosted, $FmtPV, $AttachtableLogFmt;
	if (empty( $AttachtableLogFmt )) return;
	$FmtPV['$upname'] = "'$upname'";
	$FmtPV['$upreport'] = "'$upreport'";
	$rc = $RecentChangesFmt;
	$RecentChangesFmt = $AttachtableLogFmt;
	$IsPagePosted = 1;
	$p = array();
	PostRecentChanges($pagename, $p, $p);
	$IsPagePosted = 0;
	$RecentChangesFmt = $rc;
}


## action to delete attachment
## usage: ?action=delattach&upname=filename
function HandleDeleteAttachment($pagename, $auth = 'upload') {
	global $UploadFileFmt, $LastModFile, $EnableUploadVersions, $Now, $HandleAuth;
	$upname = $_REQUEST['upname'];
	if ($upname=='') Abort("?no attachment name");
	$deleted = preg_match( '/^(.*?)(,\d+)$/', $upname, $delmatch );
	$delfromdisk = $deleted || !IsEnabled($EnableUploadVersions, 0);
	if ( $delfromdisk ) $auth = $HandleAuth['deldelattach'];
	$page = RetrieveAuthPage( $pagename, $auth, true, READPAGE_CURRENT );
	if (!$page) Abort("?cannot delete attachment from $pagename");
	$upname = $deleted ?
		MakeUploadName( $pagename, $delmatch[1] ) . $delmatch[2] :
		MakeUploadName( $pagename, $upname );
	$filepath = FmtPageName("$UploadFileFmt/$upname",$pagename);
	if ( file_exists($filepath) ) {
		if ( is_dir($filepath) ) $result = 'isdir';
		else {
			$r = $delfromdisk ?
				unlink($filepath) :
				rename($filepath, "$filepath,$Now");
			if ($r) {
				if ($LastModFile) { touch($LastModFile); fixperms($LastModFile); }
				AttachtableLog( $pagename, $upname, $delfromdisk ? XL('deleted from disk') : XL('deleted') );
				$result = 'delsuccess';
			} else
				$result = $delfromdisk ? 'delfail' : 'renamefail';
		}
	} else
		$result = 'badname';
	Redirect($pagename,"{\$PageUrl}?action=upload&uprname=$upname&upresult=$result");
}


## action to rename attachment
## usage: ?action=renameattach&upname=filename&newname=filename
function HandleRenameAttachment($pagename, $auth = 'upload') {
	global $HandleAuth, $UploadFileFmt, $LastModFile, $TimeFmt;

	## check file
	$upname = $_REQUEST['upname'];
	if ($upname=='') Abort("?no attachment name");
	$deleted = preg_match( '/^(.*?),(\d+)$/', $upname, $delmatch );
	if ($deleted) {
		$dname = MakeUploadName( $pagename, $delmatch[1] );
		$upname = "$dname,{$delmatch[2]}";
	} else {
		$upname = MakeUploadName( $pagename, $upname );
	}
	$oldfilepath = FmtPageName( "$UploadFileFmt/$upname", $pagename );
	if ( !file_exists($oldfilepath) ) Abort("?no such attachment: $upname");
	if ( is_dir($oldfilepath) ) {
		Redirect( $pagename, "{\$PageUrl}?action=upload&uprname=$upname&upresult=isdir" );
		return;
	}

	## new name
	$newname = $_REQUEST['newname'];
	if ($newname=='') Abort("?no new attachment name<noscript> - enable JavaScript to prompt for filename or add <code>&newname=[filename]</code> to this page's URL</noscript>");
	$newname = MakeUploadName( $pagename, $newname );

	## check authorization
	if ($deleted) {
		if ( !RetrieveAuthPage( $pagename, $HandleAuth['undelattach'], TRUE, READPAGE_CURRENT ) )
			Abort("?cannot restore attachment from $pagename");
		if ( ( $dname != $newname ) && !RetrieveAuthPage( $pagename, $HandleAuth['renameattach'], TRUE, READPAGE_CURRENT ) )
			Abort("?cannot restore attachment from $pagename with a different name");
	} else {
		if ( !RetrieveAuthPage( $pagename, $HandleAuth['renameattach'], TRUE, READPAGE_CURRENT ) )
			Abort("?cannot rename attachment from $pagename");
	}

	## verify & rename
	if ($newname=='') {
		$newname = $_REQUEST['newname'];
		$result = 'upresult=badname';
	} else {
		$newfilepath = FmtPageName( "$UploadFileFmt/$newname", $pagename );
		$result = UploadVerifyRename( $oldfilepath, $newfilepath );
		if ( $result == '' ) {
			$r = rename( $oldfilepath, $newfilepath );
			if ($r) {
				if ($LastModFile) { touch($LastModFile); fixperms($LastModFile); }
				AttachtableLog( $pagename, $newname,
					$deleted ?
						XL('restored').' ('.XL('was')." $dname, ".XL('deleted').' ' . strftime( $TimeFmt, $delmatch[2] ) . ')' :
						XL('renamed').' ('.XL('was')." $upname)" );
				$result = $deleted ? 'upresult=undelsuccess' : 'upresult=renamesuccess';
			} else
				$result = 'upresult=renamefail';
		}
	}
	Redirect( $pagename, "{\$PageUrl}?action=upload&uprname=$newname&$result" );
}

function UploadVerifyRename( $oldfilepath, $newfilepath ) {
	global $UploadExtSize;
	if ( file_exists( $newfilepath ) ) return 'upresult=exists';
	preg_match( '/\\.([^.\\/]+)$/', $newfilepath, $match ); $ext = @$match[1];
	$maxsize = $UploadExtSize[$ext];
	if ( $maxsize <= 0 ) return "upresult=badtype&upext=$ext";
	$size = filesize($oldfilepath);
	if ( $size > $maxsize ) return "upresult=toobigext&upext=$ext&upmax=$maxsize";
/* for now, can't rename across directories so no point in checking $UploadPrefixQuota or $UploadDirQuota
	global $UploadPrefixQuota;
	$filedirs = preg_replace( '#/[^/]*$#', '', array( $oldfilepath, $newfilepath ) );
	if ( $UploadPrefixQuota && ( $filedirs[0] != $filedirs[1] ) && ( ( dirsize($filedirs[1]) + $size ) > $UploadPrefixQuota ) )
		return 'upresult=pquota';
*/
	return '';
}


## action to download deleted (ie. renamed) attachment
## usage: ?action=downloaddeleted&upname=filename,timestamp
function HandleDownloadDeleted( $pagename, $auth = 'upload' ) {
	global $UploadFileFmt, $UploadExts, $DownloadDisposition;
	SDV($DownloadDisposition, "inline");
	$upname = $_REQUEST['upname'];
	if ( !preg_match( '/^(.*?)(,\d+)$/', $upname, $delmatch ) )
		Redirect($pagename,"{\$PageUrl}?action=download&upname=$upname");
	$page = RetrieveAuthPage( $pagename, $auth, true, READPAGE_CURRENT );
	if (!$page) Abort("?cannot read $pagename");
	$upname = MakeUploadName( $pagename, $delmatch[1] ) . $delmatch[2];
	$filepath = FmtPageName("$UploadFileFmt/$upname", $pagename);
	if ( !$upname || !file_exists($filepath) ) {
		header("HTTP/1.0 404 Not Found");
		Abort("?requested file not found");
		exit();
	}
	preg_match( '/\\.([^.]+)$/', $delmatch[1], $match );
	if ( $UploadExts[@$match[1]] ) header( "Content-Type: {$UploadExts[@$match[1]]}" );
	header( "Content-Length: ".filesize($filepath) );
	header( "Content-disposition: $DownloadDisposition; filename={$delmatch[1]}" );
	$fp = fopen( $filepath, "r" );
	if ($fp) {
		while (!feof($fp)) echo fread($fp, 4096);
		fclose($fp);
	}
	exit();
}


function AttachtableSortNameCmp( $a, $b ) {
	$ax = explode(',',$a);
	$bx = explode(',',$b);
	if ( ($ax[0]==$bx[0]) && (count($ax)>1) && (count($bx)>1) )
		return strcmp($bx[1],$ax[1]);
	else
		return strnatcasecmp($a,$b);
}

function AttachFilesizeString( $size ) {
	global $AttachtablePrettyFilesize;
	$raw = number_format( $size );
	if ( !IsEnabled( $AttachtablePrettyFilesize, 1 ) ) return $raw;

	$units = array('','K','M','G','T','P');
	$c = 0;
	while ( $size >= 1000 ) { ++$c; $size /= 1024; } ## size ends up with at most 3 digits
	return "<span title='$raw'>".number_format( $size, ( ($size<10) ? 1 : 0 ) ) . $units[$c].'</span>';
}

function AttachtableCountUploadLinks( $pagename, $imap, $path, $alt='', $txt='', $fmt=NULL ) {
	global $UploadFileFmt, $AttachtableReferences;

	if (preg_match( '!^(.*)/([^/]+)$!', $path, $match )) {
		$pn_upload = MakePageName( $pagename, $match[1] );
		$path = $match[2];
	} else $pn_upload = $pagename;

	$upname = MakeUploadName( $pn_upload, $path );
	$filepath = FmtPageName( "$UploadFileFmt/$upname", $pn_upload );

	$AttachtableReferences[$filepath][] = $pagename;

	return '';
}


## show attachments
## based on FmtUploadList in upload.php
function FmtAttachtable($pagename, $args) {
	global $UploadDir, $UploadPrefixFmt, $UploadUrlFmt, $TimeFmt, $HandleAuth,
		$EnableUploadOverwrite, $EnableUploadVersions, $EnableDirectDownload,
		$AttachtableDataFields, $AttachtableShowRows, $AttachtableDefaultFilter, $AttachtableSortableJS,
		$AttachtableReferences;

	SDV( $AttachtableDataFields, array( 'size', 'references', 'modtime', 'mimetype' ) );
	SDV( $AttachtableShowRows, array( 'header', 'normal', 'deleted', 'linkonly', 'footer' ) );

	$opt = ParseArgs($args);

	## page to list attachments for
	if (@$opt[''][0]) $pagename = MakePageName($pagename, $opt[''][0]);

	## filter to inc/exclude files based on their name
	$filter = empty($opt['filter']) ? @$AttachtableDefaultFilter
		: str_replace( '$', '(?:,\d+)?$', $opt['filter'] );

	## filter to include files based on their extension
	if (!empty( $opt['ext'] )) {
		$filter = (array)$filter;
		$filter[] = '/\\.('
			. implode( '|', preg_split('/\\W+/', $opt['ext'], -1, PREG_SPLIT_NO_EMPTY) )
			. ')(?:,[0-9]+)?$/i';
	}

	## data columns to show
	if ( array_key_exists('data',$opt) )
		switch( $opt['data'] ) {
			case 'all':
				$data = array( 'size', 'references', 'modtime', 'mimetype', 'filetype' );
				break;
			case 'none':
				$data = array();
				break;
			case 'default':
				$data = array_unique($AttachtableDataFields);
				break;
			default:
				$data = array_unique( preg_split( '/\\W+/', $opt['data'], -1, PREG_SPLIT_NO_EMPTY ) );
		}
	else $data = array_unique($AttachtableDataFields);

	## data rows to show
	if ( array_key_exists('show',$opt) )
		switch( $opt['show'] ) {
			case 'all':
				$show = array( 'header', 'normal', 'deleted', 'linkonly', 'footer' );
				break;
			case 'default':
				$show = array_unique($AttachtableShowRows);
				break;
			default:
				$show = array_unique( preg_split( '/\\W+/', $opt['show'], -1, PREG_SPLIT_NO_EMPTY ) );
		}
	else $show = array_unique($AttachtableShowRows);

	## lookup attachment references, if necessary
	## FIXME: these really ought to be cached
	$AttachtableReferences = array();
	if ( in_array('references',$data) ) {
		global
			$LinkFunctions, $SearchPatterns, $UrlExcludeChars,
			$AttachtableProperReferenceLookupMaxPages, $AttachtableReferenceListPatterns;

		SDV( $AttachtableProperReferenceLookupMaxPages, 8 );
		SDVA( $AttachtableReferenceListPatterns, array(
			'limit' => '/^'.substr( $pagename, 0, strcspn($pagename,'./') ).'\./',
			'recent' => $SearchPatterns['normal']['recent']
		) );
		$ls = ListPages( $AttachtableReferenceListPatterns );

		if ( count($ls) <= $AttachtableProperReferenceLookupMaxPages ) {
			$prev_LinkFunctions_Attach = $LinkFunctions['Attach:'];
			$LinkFunctions['Attach:'] = 'AttachtableCountUploadLinks';
			foreach( $ls as $pn ) {
				 $pg = RetrieveAuthPage( $pn, 'read', FALSE, READPAGE_CURRENT );
				 $html = MarkupToHTML( $pn, $pg['text'] );
			}
			$LinkFunctions['Attach:'] = $prev_LinkFunctions_Attach;
		} else {
			foreach( $ls as $pn ) {
				$pg = RetrieveAuthPage( $pn, 'read', FALSE, READPAGE_CURRENT );
				if ($pg) $txt = preg_replace(
					array(
						"/(\n[^\\S\n]*)?\\[([=@])(.*?)\\2\\]/s",	## preserved text
						'/\[\[[^\]]*?\bAttach:([^\]\|]*)/e',		## links to attachments
						"/\\bAttach:([^\\s$UrlExcludeChars]*[^\\s.,?!$UrlExcludeChars])/e" ),	## raw attachments
					array(
						' ',
						"'[['.AttachtableCountUploadLinks(\$pn,'','$1')",
						"AttachtableCountUploadLinks(\$pn,'','$1')" ),
					htmlspecialchars( $pg['text'], ENT_NOQUOTES ) );	## assumes $MarkupFrame[0]['escape'] == 1
			}
		}
	}

	## check authorization
	$auth = array(
		'upload' => 0,
		'delattach' => 0,
		'undelattach' => 0,
		'renameattach' => 0,
		'deldelattach' => 0,
		'downloaddeleted' => 0
	);
	switch( @$opt['actions'] ) {
		case 'all':
			foreach( $auth as $n => $a ) $auth[$n] = 1;
		case 'none':
			break;
		default:
			if ( @$opt[''][0] != '*' ) {
				$ac = array();
				foreach( $auth as $n => $a ) {
					$h =& $HandleAuth[$n];
					if ( !array_key_exists($h,$ac) ) $ac[$h] = CondAuth( $pagename, $h );
					$auth[$n] = $a || $ac[$h];
				}
			}
	}
	if ( !$EnableUploadOverwrite ) $auth['upload'] = 0;
	$confirmdel = !IsEnabled( $EnableUploadVersions, 0 );
	if ( $confirmdel ) $auth['delattach'] = $auth['deldelattach'];

	## locations
	$pageurl = FmtPageName( '$PageUrl', $pagename );
	$uploaddir = FmtPageName( "$UploadDir$UploadPrefixFmt", $pagename );
	$uploadurl = FmtPageName(
		IsEnabled($EnableDirectDownload, 1) ? "$UploadUrlFmt$UploadPrefixFmt/" : "\$PageUrl?action=download&amp;upname=",
		$pagename );

	## read files
	$filelist = array();
	if ( $dirp = @opendir($uploaddir) ) {
		while ( ( $file = readdir($dirp) ) !== FALSE ) {
			if ( $file[0] == '.' ) continue;
			$filelist[$file] = $file;
		}
		closedir($dirp);
	}
	if ($filter) $filelist = MatchPageNames( $filelist, $filter );

	## output
	$restore = $deldel = $change = $rename = $delete = '';
	$totalsize = $lastmod = $delcount = $dircount = 0;
	$out = array();

	if (count( $filelist )) {
		usort($filelist,'AttachtableSortNameCmp');
		$showfiles = in_array('normal',$show) || in_array('deleted',$show);
		foreach( $filelist as $file ) {
			$name = PUE("$uploadurl$file");
			$filepath = "$uploaddir/$file";
			if (!$showfiles) {
				$totalsize += filesize($filepath);
				continue;
			}
			$stat = stat($filepath);

			if ( is_dir($filepath) ) {
				++$dircount;
				if ($EnableDirectDownload) {
					$s = "<tr class='dir'><td align='left'><a href='$name' title='$[View directory]'>$file/</a></td>";
					foreach( $data as $d ) {
						$d_opt = $d_txt = '';
						switch($d) {
							case 'filetype':
							case 'mimetype':
								$d_opt = " align='left'";
								$d_txt = XL('directory');
								break;
							case 'modtime':
								$lastmod = max( $lastmod, $stat['mtime'] );
								$d_txt = strftime($TimeFmt, $stat['mtime']);
								break;
						}
						$s .= "<td$d_opt>$d_txt</td>";
					}
					$out[] = $s;
				}
				continue;
			}

			$confirm = "onclick=\"return confirm('$[Permanently delete] $file?')\"";
			$aopt = "rel='nofollow' class='createlink' href='$pageurl?upname=$file&amp;action";

			if ( preg_match( '/^(.*?),([0-9]+)$/', $file, $delmatch ) ) {
				++$delcount;
				if ( !in_array('deleted',$show) ) continue;
				$dname = $delmatch[1];
				$dtime = strftime( $TimeFmt, $delmatch[2] );
				if ($auth['undelattach']) $restore = "<a $aopt=renameattach&newname=$dname' title='$[Restore] ($[removed] $dtime)'"
					. ( $auth['renameattach'] ? " onclick=\"return atr('$file')\"" : '' )
					. '>&nbsp;R&nbsp;</a>';
				if ($auth['deldelattach']) $deldel = "<a $aopt=delattach' title='$[Delete from disk] ($[removed] $dtime)' $confirm>&nbsp;X&nbsp;</a>";
				if ($auth['downloaddeleted']) $dname = "<a rel='nofollow' href='$pageurl?action=downloaddeleted&amp;upname=$file' title='$[Download] ($[removed] $dtime)'>$dname</a>";
				$s = "<tr class='del' title='$[File removed] $dtime'><td align='left'>$dname $restore $deldel</td>";
			} else {
				if ( !in_array('normal',$show) ) continue;
				if ($auth['upload']) $change = "<a $aopt=upload' title='$[Upload new version]'>&nbsp;&Delta;&nbsp;</a>";
				if ($auth['renameattach']) $rename = "<a $aopt=renameattach' class='' title='$[Rename]' onclick=\"return atr('$file')\">&nbsp;R&nbsp;</a>";
				if ($auth['delattach']) $delete = ( $confirmdel
					? "<a $aopt=delattach' title='$[Delete from disk]' $confirm>&nbsp;X&nbsp;</a>"
					: "<a $aopt=delattach' title='$[Delete]'>&nbsp;X&nbsp;</a>" );
				$s = "<tr><td align='left'><a href='$name' title='$[View]'>$file</a> $change $rename $delete</td>";
			}

			foreach( $data as $d ) {
				$d_opt = $d_txt = '';
				switch($d) {
					case 'size':
						$totalsize += $stat['size'];
						if (!empty($AttachtableSortableJS)) $d_opt = " sortval='{$stat['size']}'";
						$d_txt = AttachFilesizeString($stat['size']);
						break;
					case 'references':
						if ($delmatch) break;
						$c = count( @$AttachtableReferences[$filepath] );
						if ($c) {
							$ls = array();
							foreach( array_unique($AttachtableReferences[$filepath]) as $pn )
								$ls[] = Keep(MakeLink( $pagename, $pn ),'L');
							$d_opt = " class='reftitle'";
							$d_txt = "$c<div class='refbox'>$[Referring pages:]<ul><li>"
								. implode( "</li><li>", $ls )
								. "</li></ul></div>";
							unset( $AttachtableReferences[$filepath] );
						} else {
							$d_opt = " class='orphan'";
							$d_txt = '0';
						}
						break;
					case 'filetype':
					case 'mimetype':
						$d_opt = " align='left'";
						$d_txt = AttachFiletype( $filepath, ($d=='mimetype') );
						break;
					case 'modtime':
						$lastmod = max( $lastmod, $stat['mtime'] );
						if (!empty($AttachtableSortableJS)) $d_opt = " sortval='{$stat['mtime']}'";
						$d_txt = strftime( $TimeFmt, $stat['mtime'] );
						break;
				}
				$s .= "<td$d_opt>$d_txt</td>";
			}
			$out[] = $s;
		}
	} else $out[] = "<tr><td align='left'>($[no attached files])</td>";

	if ( in_array('linkonly',$show) && !empty($AttachtableReferences) ) {
		foreach( $AttachtableReferences as $filepath => $refs ) {
			if ( dirname($filepath) != $uploaddir ) continue;
			if (file_exists( $filepath )) continue;
			$refcount = count( $refs ); if ( !$refcount ) continue;
			$file = basename($filepath);
			if ( $filter && !MatchPageNames( $file, $filter ) ) continue;
			$name = PUE("$uploadurl$file");
			$aopt = "rel='nofollow' title='$[Upload missing file]' href='$pageurl?upname=$file&amp;action=upload'";
			if ($auth['upload']) $file = "<a $aopt>$file</a> <a $aopt class='createlink'>&nbsp;U&nbsp;</a>";
			$s = "<tr title='$[Missing file]'><td align='left' class='missing'>$file</td>";
			foreach( $data as $d ) switch($d) {
				case 'references':
					$ls = array();
					foreach( array_unique($refs) as $pn ) $ls[] = Keep(MakeLink( $pagename, $pn ),'L');
					$s .= "<td class='reftitle'><span class='orphan'>$refcount</span><div class='refbox'>$[Referring pages:]<ul><li>"
						. implode( "</li><li>", $ls )
						. "</li></ul></div></td>";
					break;
				default:
					$s .= '<td></td>';
			}
			$out[] = $s;
		}
	}

	$thead = $tfoot = '';
	if ( in_array('header',$show) ) {
		$thead = '<thead><tr><th style=\'text-align:left;\'>$[file name]</th>';
		foreach( $data as $d ) {
			$d_opt = $d_txt = '';
			switch($d) {
				case 'size':
					$d_txt = XL('size (bytes)');
					break;
				case 'references':
					$d_opt = " class='reftitle' title='$[references to this file, NOTE: may be incomplete]'";
					$d_txt = XL('refs');
					break;
				case 'filetype':
					$d_opt = " style='text-align:left;'";
					$d_txt = XL('file type');
					break;
				case 'mimetype':
					$d_opt = " style='text-align:left;'";
					$d_txt = XL('MIME type');
					break;
				case 'modtime':
					$d_txt = XL('last modified');
					break;
			}
			$thead .= "<th$d_opt>$d_txt</th>";
		}
		$thead .= "</tr></thead>\n";
	}

	if ( in_array('footer',$show) ) {
		$fc = count($filelist) - $dircount;
		if ( !in_array('deleted',$show) ) $fc -= $delcount;
		if ($fc<0) $fc = 0;
		$tfoot = '<tfoot><tr><th>$[total] '
			. ( $dircount ? "$dircount $[director".($dircount==1?'y':'ies').'], ' : '' )
			. "$fc $[file".($fc==1?']':'s]')
			. '</th>';
		foreach( $data as $d ) {
			$d_txt = '';
			switch($d) {
				case 'size':
					if ($totalsize) $d_txt = AttachFilesizeString($totalsize);
					break;
				case 'modtime':
					if ($lastmod) $d_txt = strftime( $TimeFmt, $lastmod );
					break;
			}
			$tfoot .= "<th>$d_txt</th>";
		}
		$tfoot .= "</tr></tfoot>\n";
	}
	return preg_replace( '/\\$\\[(?>([^\\]]+))\\]/e', "XL(PSS('$1'))",
		"<script type='text/javascript'>function atr(fn){
var ar,nn=fn,rt='$[Rename] '+fn+' $[as]:';
if(ar=fn.match(/^(.*?),([0-9]+)$/)){nn = ar[1];rt = '$[Restore] '+ar[1]+' ($[removed] '+(new Date(ar[2]*1000)).toUTCString()+') $[as]:';}
if(n=prompt(rt,nn)) document.location='$pageurl?action=renameattach&upname='+fn+'&newname='+n;
return false;}</script>\n"
		. ( empty($AttachtableSortableJS) ? '' : "<script type='text/javascript' src='$AttachtableSortableJS'></script>\n" )
		. "<table cellspacing='0' style='text-align:right;' class='attachtable sortable'>\n$thead$tfoot<tbody>\n"
		. implode("</tr>\n",$out)
		. "</tr>\n</tbody></table>" );
}


function AttachFiletype( $filename, $mime ) {
	global $UploadExts;

	if (!$mime) return trim( exec( 'file -b ' . escapeshellarg($filename) ) );

	preg_match( '/\.([^.]+)$/', $filename, $match ); $ext = @$match[1];

	if ( function_exists('finfo_open') ) {
		$finfo = finfo_open(FILEINFO_MIME);
		$m = finfo_file($finfo, $filename);
		finfo_close($finfo);
	} else if ( function_exists('mime_content_type') ) {
		$m = mime_content_type($filename);
	} else $m = '';

	if ($m) $m = substr( $m, 0, strcspn($m,'; ') );

	switch($m) {
		case 'application/msword':
			if ( isset($UploadExts[$ext]) ) return $UploadExts[$ext];
			switch($ext) {
				case 'ppt': return 'application/vnd.ms-powerpoint';
				case 'xls': return 'application/vnd.ms-excel';
			}
			break;
		case '':
		case 'text/plain':
			$m = trim( exec( 'file -bi ' . escapeshellarg($filename) ) );
			if ($m) $m = substr( $m, 0, strcspn($m,'; ') );
			if ( !$m && $ext && array_key_exists( $ext, $UploadExts ) )
				$m = $UploadExts[$ext];
	}
	return $m;
}