<?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/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-29';

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' ) );

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

SDV( $AttachtablePubDirUrl, "$PubDirUrl/attachtable" );
SDV( $AttachtableCSS, "$AttachtablePubDirUrl/attachtable.css" );
if (!empty( $AttachtableCSS )) $HTMLHeaderFmt['attachtable-css'] = "\n<link rel='stylesheet' type='text/css' href='$AttachtableCSS' />";


include_once('attachtable-util.php');


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
		$PubDirUrl, $UploadDir, $UploadPrefixFmt, $UploadUrlFmt, $TimeFmt, $HandleAuth,
		$EnableUploadOverwrite, $EnableUploadVersions, $EnableDirectDownload,
		$EnableAttachtableSortable, $EnableAttachtableFileInfo,
		$AttachtableDataFields, $AttachtableShowRows, $AttachtableDefaultFilter,
		$AttachtableReferences, $AttachtablePubDirUrl;

	SDV( $AttachtableDataFields, array( 'fileinfo', 'name', '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';
	}

	## (dis)allow re-sorting table
	$sortable = ( IsEnabled( $EnableAttachtableSortable, 0 ) && !in_array( 'sortable', (array)$opt['-'] ) );

	## data columns to show
	if ( array_key_exists('data',$opt) )
		switch( $opt['data'] ) {
			case 'all':
				$data = array( 'fileinfo', 'name', 'size', 'references', 'modtime', 'mimetype', 'filetype' );
				break;
			case 'none':
				$data = array('name');
				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);
	if ( !IsEnabled( $EnableAttachtableFileInfo, 0 ) && ( ( $key = array_search('fileinfo',$data) ) !== FALSE ) ) unset( $data[$key] );
	if ( count($data) && !in_array('name',$data) ) {
		if ( ( $k=array_search('fileinfo',$data) ) !== FALSE ) array_splice( $data, $k+1, 0, 'name' );
		else array_unshift( $data, 'name' );
	}

	## 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'>";
					foreach( $data as $d ) {
						$d_opt = $d_txt = '';
						switch($d) {
							case 'fileinfo':
								$d_txt = "<div class='fileinfo'></div>";
								break;
							case 'name':
								$d_opt = " align='left'";
								$d_txt = "<a href='$name' title='$[View directory]'>$file/</a>";
								break;
							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;
			}


			if (preg_match( '/^(.*?),([0-9]+)$/', $file, $delmatch )) ++$delcount;

			$r_opt = $s = '';
			foreach( $data as $d ) {
				$d_opt = $d_txt = '';
				switch($d) {
					case 'fileinfo':
						$d_txt = "<a class='fileinfo' id='$pagename:"
							. str_replace(',',':',$file)
							. "' href='$pageurl?action=fileinfo&amp;upname=$file' title='$[More information...]'>+</a>";
						break;
					case 'name':
						$d_opt = " align='left'";
						$confirm = "onclick=\"return confirm('$[Permanently delete] $file?')\"";
						$aopt = "rel='nofollow' class='createlink' href='$pageurl?upname=$file&amp;action";
						if ($delmatch) {
							if ( !in_array('deleted',$show) ) continue;
							$r_opt = " class='del' title='$[File removed] $dtime'";
							$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>";
							$d_txt = "$dname $restore $deldel";
						} 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>" );
							$d_txt = "<a href='$name' title='$[View]'>$file</a> $change $rename $delete";
						}
						break;
					case 'size':
						$totalsize += $stat['size'];
						if ($sortable) $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 ($sortable) $d_opt = " sortval='{$stat['mtime']}'";
						$d_txt = strftime( $TimeFmt, $stat['mtime'] );
						break;
				}
				$s .= "<td$d_opt>$d_txt</td>";
			}
			$out[] = "<tr$r_opt>$s";
		}
	} else {
		$s = '<tr>';
		foreach( $data as $d ) {
			if ( $d == 'name' ) $s .= "<td align='left'>($[no attached files])</td>";
			else $s .= '<td></td>';
		}
		$out[] = $s;
	}

	if ( in_array('linkonly',$show) && !empty($AttachtableReferences) ) {
		uksort( $AttachtableReferences, 'AttachtableSortNameCmp' );
		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]'>";
			foreach( $data as $d ) switch($d) {
				case 'name':
					$s .= "<td align='left' class='missing'>$file</td>";
					break;
				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>';
		foreach( $data as $d ) {
			$d_opt = $d_txt = '';
			switch($d) {
				case 'fileinfo':
					$d_opt = " class='unsortable'";
					break;
				case 'name':
					$d_opt = " style='text-align:left;'";
					$d_txt = XL('file name');
					break;
				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>';
		foreach( $data as $d ) {
			$d_txt = '';
			switch($d) {
				case 'name':
					$d_txt = '$[total] '
						. ( $dircount ? "$dircount $[director".($dircount==1?'y':'ies').'], ' : '' )
						. "$fc $[file".($fc==1?']':'s]');
					break;
				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";
	}

	$jshead = array();
	if ( $auth['undelattach'] || $auth['renameattach'] )
		$jshead[] = "<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;}\n</script>";
	if ( in_array('fileinfo',$data) ) {
		$jshead[] = "<script type='text/javascript'>var pageurl = '".PageVar($pagename,'$PageUrl')."';</script>
			<script type='text/javascript' src='$AttachtablePubDirUrl/util.js'></script>
			<script type='text/javascript' src='$AttachtablePubDirUrl/fileinfo.js'></script>";
	}
	if ($sortable) $jshead[] = "<script type='text/javascript' src='$AttachtablePubDirUrl/sortable.js'></script>";

	return preg_replace( '/\\$\\[(?>([^\\]]+))\\]/e', "XL(PSS('$1'))",
		implode("\n",$jshead)
		. "\n<table cellspacing='0' style='text-align:right;' class='attachtable sortable'>\n$thead$tfoot<tbody>\n"
		. implode("</tr>\n",$out)
		. "</tr>\n</tbody></table>" );
}