* * 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 * 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"); $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&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 = ''; foreach( $data as $d ) switch($d) { case 'size': $s .= 'size (bytes)'; break; case 'mimetype': $s .= 'MIME type'; break; case 'modtime': $s .= 'last modified'; } $out[] = $s; } foreach( $filelist as $file ) { $name = PUE("$uploadurl$file"); $filepath = "$uploaddir/$file"; $stat = stat($filepath); if ( is_dir($filepath) ) { if ($EnableDirectDownload) { $s = "$file/"; foreach( $data as $d ) switch($d) { case 'size': $s .= ''; break; case 'mimetype': $s .= 'directory'; break; case 'modtime': $s .= ''.strftime($TimeFmt, $stat['mtime']).''; } $out[] = $s; } continue; } $confirm = "onclick=\"return confirm('Really delete $file?')\""; $aopt = "rel='nofollow' class='createlink' href='$pageurl?upname=$file&action"; if ( preg_match( '/^(.*?),([0-9]+)$/', $file, $delmatch ) ) { if ( $hidedeleted ) continue; $dname = $delmatch[1]; $dtime = strftime( $TimeFmt, $delmatch[2] ); if ($auth['undelattach']) $restore = " R '; if ($auth['deldelattach']) $deldel = " X "; if ($auth['downloaddeleted']) $dname = "$dname"; $s = "$dname $restore $deldel"; } else { if ($auth['upload']) $change = " Δ "; if ($auth['renameattach']) $rename = " R "; if ($auth['delattach']) $delete = ( $confirmdel ? " X " : " X " ); $s = "$file $change $rename $delete"; } foreach( $data as $d ) switch($d) { case 'size': $s .= ''.number_format($stat['size']).''; break; case 'mimetype': $s .= ''.mimetype($filepath).''; break; case 'modtime': $s .= ''.strftime($TimeFmt, $stat['mtime']).''; } $out[] = $s; } return ''.implode("\n",$out).'
'; } /* 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; }