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

/*	=== Attachtable ===
 *	Copyright 2007 Eemeli Aro <eemeli.aro@tkk.fi>
 *
 *	Adds actions to delete & restore deleted attachments, as 
 *	well as an attachlist replacement to use those actions.
 *
 *	Developed and tested using the PmWiki 2.2.0-beta 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
 */
 
/*
 *	Modified 9 Feb 2008 by Aidin Abedi <fooguru@msn.com>
 *		use UserAuth recipe to support specific page and group auth access
 */

$RecipeInfo['Attachtable']['Version'] = '2008-02-09';

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

SDVA( $AttachTableActions, array( 
	'delattach' => $AttachTableActions['upload'],            # deleting a file from PmWiki while keeping it on disk
	'deldelattach' => $AttachTableActions['upload']       # deleting a file from disk
) );
SDVA( $AttachTableActions, array( 
	'undelattach' => $AttachTableActions['delattach'],       # restoring a file
	'renameattach' => $AttachTableActions['deldelattach']    # renaming a file
) );
SDVA( $AttachTableActions, array( 
	'downloaddeleted' => $AttachTableActions['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' ) );

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


## action to delete attachment
## usage: ?action=delattach&upname=filename
function HandleDeleteAttachment($pagename, $auth = 'upload') {
	global $UploadFileFmt, $LastModFile, $EnableUploadVersions, $Now, $AttachTableActions;
	$upname = $_REQUEST['upname'];
	if ($upname=='') Abort("?no attachment name");
	$deleted = preg_match( '/^(.*?)(,[0-9]+)$/', $upname, $delmatch );
	$delfromdisk = $deleted || !IsEnabled($EnableUploadVersions, 0);
	if ( $delfromdisk ) $auth = $AttachTableActions['deldelattach'];
	//$page = RetrieveAuthPage( $pagename, $auth, true, READPAGE_CURRENT );
	$page = UserAuth($pagename, $auth, false);
	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 ($LastModFile) { touch($LastModFile); fixperms($LastModFile); }
			$result = $r ? 'delsuccess' : ( $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 $AttachTableActions, $UploadFileFmt, $LastModFile;

	## check file
	$upname = $_REQUEST['upname'];
	if ($upname=='') Abort("?no attachment name");
	$deleted = preg_match( '/^(.*?)(,[0-9]+)$/', $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
	$upname = $_REQUEST['newname'];
	if ($upname=='') Abort("?no new attachment name<noscript> - enable JavaScript to prompt for filename or add <code>&newname=[filename]</code> to this page's URL</noscript>");
	$upname = MakeUploadName( $pagename, $upname );

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

	## verify & rename
	if ($upname=='') {
		$upname = $_REQUEST['newname'];
		$result = 'upresult=badname';
	} else {
		$newfilepath = FmtPageName( "$UploadFileFmt/$upname", $pagename );
		$result = UploadVerifyRename( $oldfilepath, $newfilepath );
		if ( $result == '' ) {
			$r = rename( $oldfilepath, $newfilepath );
			if ($LastModFile) { touch($LastModFile); fixperms($LastModFile); }
			$result = $r ? ( $deleted ? 'upresult=undelsuccess' : 'upresult=renamesuccess' ) : 'upresult=renamefail';
		}
	}
	Redirect( $pagename, "{\$PageUrl}?action=upload&uprname=$upname&$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( '/^(.*?)(,[0-9]+)$/', $upname, $delmatch ) )
		Redirect($pagename,"{\$PageUrl}?action=download&upname=$upname");
	//$page = RetrieveAuthPage( $pagename, $auth, true, READPAGE_CURRENT );
	$page = UserAuth($pagename, 'auth', false);
	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();
}


## show attachments
## based on FmtUploadList in upload.php
function FmtAttachtable($pagename, $args) {
	global $UploadDir, $UploadPrefixFmt, $UploadUrlFmt, $TimeFmt, $AttachTableActions, 
		$EnableUploadOverwrite, $EnableUploadVersions, $EnableDirectDownload, 
		$DefaultGroup, $DefaultName, $AttachtableDataFields, $AttachtableFileCheck;

	SDV( $AttachtableDataFields, array( 'size', 'modtime' ) );

	## options
	$opt = ParseArgs($args);
	if (@$opt[''][0]) $pagename = MakePageName($pagename, $opt[''][0]);
	else $pagename = MakePageName("$DefaultGroup.$DefaultName",$pagename);
	if (@$opt['ext']) 
		$matchext = '/\\.(' . implode('|', preg_split('/\\W+/', $opt['ext'], -1, PREG_SPLIT_NO_EMPTY)) . ')(?:,[0-9]+)?$/i';
	if ( array_key_exists( 'data', $opt ) )
		switch( $opt['data'] ) {
			case 'all':
				$data = array( 'size', 'mimetype', 'modtime' );
				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);
	$cols = array();
	foreach( $data as $x ) {
		if ( in_array( $x, $AttachtableDataFields ) ) 
			$cols[$x] = 1;
	}
	$hidedeleted = ( array_key_exists('-',$opt) && in_array( 'deleted', $opt['-'] ) );

	## authorization check
	$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 = $AttachTableActions[$n];
					if (!UserAuth($pagename, $h, false)) $h = '';
					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 );

	## external file check
	if( $extfc = function_exists(@$AttachtableFileCheck) )
		$AttachtableFileCheck( $pagename, '', $uploaddir, $opt );

	## read files
	$dirp = @opendir($uploaddir);
	if (!$dirp) return '(no attached files)';
	$filelist = array();
	while ( ( $file = readdir($dirp) ) !== false ) {
		if ( $file{0} == '.' ) continue;
		if ( @$matchext && !preg_match(@$matchext, $file) ) continue;
		if ( $extfc && !$AttachtableFileCheck( $pagename, $file ) ) continue;
		$filelist[$file] = $file;
	}
	closedir($dirp);
	natcasesort($filelist);

	## output
	$restore = $deldel = $change = $rename = $delete = '';
	$out = array();
	if ( !array_key_exists('-',$opt) || !in_array('titlerow',$opt['-']) ) {
		$s = '<tr class=\'titlerow\'><td></td>';
		foreach( $data as $d )
			switch($d) {
				case 'size':
					$s .= '<td>size (bytes)</td>';
					break;
				case 'mimetype':
					$s .= '<td>MIME type</td>';
					break;
				case 'modtime':
					$s .= '<td>last modified</td>';
			}
		$out[] = $s;
	}
	foreach( $filelist as $file ) {
		$name = PUE("$uploadurl$file");
		$filepath = "$uploaddir/$file";
		$stat = stat($filepath);
		if ( is_dir($filepath) ) {
			if ($EnableDirectDownload) {
				$s = "<tr class='dir'><td align='left'><a href='$name' title='View directory'>$file/</a></td>";
				foreach( $data as $d )
					switch($d) {
						case 'size':
							$s .= '<td></td>'; 
							break;
						case 'mimetype':
							$s .= '<td>directory</td>';
							break;
						case 'modtime':
							$s .= '<td>'.strftime($TimeFmt, $stat['mtime']).'</td>';
					}
				$out[] = $s;
			}
			continue;
		}
		$confirm = "onclick=\"return confirm('Really delete $file?')\"";
		$aopt = "rel='nofollow' class='createlink' href='$pageurl?upname=$file&amp;action";
		if ( preg_match( '/^(.*?),([0-9]+)$/', $file, $delmatch ) ) {
			if ( $hidedeleted ) 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='if(n=prompt(\"Restore $dname (removed $dtime) as:\",\"$dname\")) document.location=\"$pageurl?action=renameattach&upname=$file&newname=\"+n; return false;'" : 
					'' ) . 
				'>&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 ($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='if(n=prompt(\"Rename $file as:\",\"$file\")) document.location=\"$pageurl?action=renameattach&upname=$file&newname=\"+n; return false;'>&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 )
			switch($d) {
				case 'size':
					$s .= '<td>'.number_format($stat['size']).'</td>'; 
					break;
				case 'mimetype':
					$s .= '<td>'.mimetype($filepath).'</td>';
					break;
				case 'modtime':
					$s .= '<td>'.strftime($TimeFmt, $stat['mtime']).'</td>';
			}
		$out[] = $s;
	}
	return '<table style=\'text-align:right;\' class=\'attachtable\'>'.implode("</tr>\n",$out).'</tr></table>';
}

/* a framework to use for defining a custom file check function
$AttachtableFileCheck = 'MyFileCheck';
function MyFileCheck( $pagename, $file, $dir=FALSE, $opt=FALSE ) {
	// the use of the 'skip' parameter is completely optional
	## $file is the file name
	## defined only when initialising, $dir is the files' directory
	## defined only when initialising, $opt is the output of the ParseArgs function
	static $skip = FALSE;
	if ($opt) {
		if ( @$opt['skip'] && ( $skip = $opt['skip'] ) ) { # note assignment
			// $skip here has the string value that was passed to it using (:attachtable skip=...:)
		}
		// more initialisation code
	}

	if ( $skip && $file ) {
		// check that file is ok
	}
	
	return TRUE; ## return indicates whether the file should (TRUE) or should not (FALSE) be listed
}
*/

function mimetype($filename) {
	if ( function_exists('mime_content_type') ) {
		$m = mime_content_type($filename);
	} else if ( function_exists('finfo_open') ) {
		$finfo = finfo_open(FILEINFO_MIME);
		$m = finfo_file($finfo, $filename);
		finfo_close($finfo);
	} else $m = '';
	if ( !$m || ( $m == 'text/plain' ) ) {
		$m = trim( exec( 'file -bi ' . escapeshellarg($filename) ) );
		if ( $n = strpos($m,';') ) $m = substr($m,0,$n);
		if (!$m) {
			global $UploadExts;
			preg_match( '/\\.([^.]+)$/', $filename, $ext );
			if ( @$ext[1] && array_key_exists( $ext[1], $UploadExts ) )
				$m = $UploadExts[$ext[1]];
		}
	}
	return $m;
}