Index: modules/filebrowser/filebrowser.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/filebrowser/filebrowser.module,v retrieving revision 1.7 diff -u -F -r1.7 filebrowser.module --- modules/filebrowser/filebrowser.module 28 Jun 2006 12:29:58 -0000 1.7 +++ modules/filebrowser/filebrowser.module 28 Aug 2006 21:38:35 -0000 @@ -1,5 +1,5 @@ array( '#type' => 'textfield', '#title' => t('Root directory'), - '#default_value' => variable_get('filebrowser_root', ''), + '#default_value' => filebrowser_root(), '#maxlength' => '100', '#size' => '70', - '#description' => t('Root directory used to present the filebrowser interface. Users will not be able to go up from this folder. Only a directory name under the Drupal root is accepted. Example: "public/files".'), + '#description' => t('Root directory used to present the filebrowser interface. Users will not be able to go up from this folder. Only a directory name under the Drupal root is accepted. Example: "public/files". No trailing slash. For super-user access to all web files, enter "." (at your own risk) '), ), 'filebrowser_icons' => array( '#type' => 'textfield', @@ -85,45 +85,47 @@ */ function filebrowser_page() { // Build breadcrumb list for uplevel folders - $subfolder = preg_replace("!^filebrowser/*!", "", $_GET['q']); - $parts = explode("/", $subfolder); - $breadcrumb = array(); - $dirname = t('root'); - if ($subfolder) { - $dirname = array_pop($parts); - while (count($parts)) { - $breadcrumb[] = l($parts[count($parts) -1], filebrowser_proper_path(join("/", $parts))); - array_pop($parts); - } - $breadcrumb[] = l(t('Filebrowser root'), 'filebrowser'); - } - $breadcrumb[] = l(t('Home'), NULL); - drupal_set_breadcrumb(array_reverse($breadcrumb)); + $parts = func_get_args(); + $subfolder = join('/',$parts); + + // set context for later filebrowser links + $filebrowser_base = filebrowser_base($subfolder); + filebrowser_proper_path('', $filebrowser_base); + + filebrowser_set_breadcrumbs($parts); // Set page title drupal_set_title(t('%dirname directory', array("%dirname" => $dirname))); + // Default list of columns to render + $cols = array('icon','name','size','age','info'); + + if($_REQUEST['edit']['cols']){$cols = $_REQUEST['edit']['cols'];} + filebrowser_set_context($filebrowser_base, $cols); + + // LIST FETCHED HERE // No files to list, return with an empty page - if (!($files = filebrowser_get_list($subfolder))) { - drupal_set_message(t('Unable to get files for this directory')); + if (!($files = filebrowser_get_list($subfolder, $cols))) { + drupal_set_message(t("Unable to get files for this directory '%subfolder'",array('%subfolder'=>$subfolder))); return theme("filebrowser_page", ''); } // Prepare headers for display - $headers = array_merge( - array( - array('data' => t("Name"), 'field' => 1), - array('data' => t("Size"), 'field' => 2), - array('data' => t("Last modified"), 'field' => 3), - ), filebrowser_get_fileinfo() - ); + $headers = array_map( + create_function( + '$colname', + '$func = "filebrowser_get_cell_".$colname; if(function_exists($func))return $func(""); ' + ), + $cols + ); + // Set sorting criteria eg. array(the 'field' key's associated value, asc/desc) filebrowser_sort_table(array(tablesort_get_order($headers), tablesort_get_sort($headers))); // Protect folders from being resorted $folders = array(); foreach ($files as $rnum => $filei) { - if ($filei[1]['data'] == '') { + if ($filei['size']['data'] == '') { $folders[] = $filei; unset($files[$rnum]); } @@ -156,7 +158,7 @@ function theme_filebrowser_page(&$files, $header = NULL, $pre = NULL) { if ($files) { // CSS can hook on this ID to style table elements differently - return '
' . $pre . theme("table", $header, $files) . '
'; + return '
' . $pre . theme("table", $header, $files) . '
'; } else { return ''; @@ -168,18 +170,25 @@ * specified filebrowser root. File system details (size, last * modification) is added, plus a metafile is parsed to gather * more information, if available. - */ -function filebrowser_get_list($subfolder = '') { - global $base_path; + * @param $subfolder the folder to scan. Must be under and relative to the + * system files dir unless $unsafe is set + * @param $columns a list of column identifiers to show + * @param $unsafe If set, the listing will NOT prepend the file path to the + * subfolder. File listings from anywhere in the server become possible! + * @return an array of table cells + */ +function filebrowser_get_list($subfolder = '', $cols = array('expander','icon','name','size','age','info'), $unsafe=FALSE) { + $folder = ($unsafe)?$subfolder:filebrowser_safe_folder($subfolder); + if(is_file($folder)){$folder = dirname($folder);} + $inroot = ($folder == filebrowser_root() ); - $folder = filebrowser_safe_folder($subfolder); - $inroot = ($folder == variable_get('filebrowser_root', '')); - + if(!$folder){$folder='.';} // Signal error in case of bogus directory name - if (!(file_exists($folder) && is_dir($folder) && ($dir = opendir($folder)))) { + if (!(is_dir($folder) && ($dir = opendir($folder)))) { + drupal_set_message($folder." was not readable"); return FALSE; } - + // Collect folders and files separately and check for a metainfo file $files = $folders = array(); $infofile = FALSE; @@ -194,6 +203,7 @@ // Grab information file, decide whether it should be shown or not if (in_array(strtolower($entry), array("descript.ion", "files.bbs"))) { $infofile = $entry; + filebrowser_get_cell_info("$folder/$infofile"); // prefetch the info if (!variable_get('filebrowser_hide_description_files', 0)) { $files[] = $entry; } @@ -211,61 +221,135 @@ sort($files); $files = array_merge($folders, $files); - // Get metainformation about files, and construct table spaceholder - // for files, which have no metainformation in that file - if ($infofile) { - $info = filebrowser_get_fileinfo("$folder/$infofile", $subfolder); - $count = count(filebrowser_get_fileinfo()); - $emptyinfo = ($count ? array_fill(0, $count, '') : array()); - } - else { - $info = $emptyinfo = array(); - } // Build detailed list of files $details = array(); foreach ($files as $file) { - $extrainfo = (isset($info[$file]) ? $info[$file] : $emptyinfo); + if ( $file == '.'){continue;} + if ($inroot && $file == '..'){continue;} + // no up from the top, but it's allowed the rest of the time. + // Some real folder or file - if (!in_array($file, array(".", ".."))) { - $completepath = "$folder/$file"; - if ($stat = stat($completepath)) { - $icon = filebrowser_get_icon($completepath); - $age = time() - $stat['mtime']; - if (is_dir($completepath)) { - $link = l("$icon $file", filebrowser_proper_path("$subfolder/$file"), array(), NULL, NULL, FALSE, TRUE); - $size = ''; - } - else { - $link = "$icon $file"; - $size = format_size($stat['size']); + $completepath = ($folder && $folder != '.') ? "$folder/$file" : $file; + if ($stat = stat($completepath)) { + $row = array(); + // populate the columns + foreach($cols as $colname){ + $func = 'filebrowser_get_cell_'.$colname; + if(function_exists($func)){ + $row[$colname] = $func($completepath); } - $details[] = array_merge( - array( - array('data' => $link, 'class' => 'filename', 'sv' => $file), - array('data' => $size, 'sv' => ($size ? $stat['size'] : 0)), - array('data' => format_interval($age), 'sv' => $age) - ), $extrainfo); } - } - // The special one-up folder not in the root folder - elseif ($file == ".." && !$inroot) { - $icon = filebrowser_get_icon(NULL, 'folder'); - $link = "$icon $file"; - $parts = explode("/", $subfolder); - array_pop($parts); - $up = t('up'); - $link = l("$icon $file <$up>", filebrowser_proper_path(join("/", $parts)), array(), NULL, NULL, FALSE, TRUE); - $details[] = array_merge(array( - array('data' => $link, 'class' => 'filename', 'sv' => $file), - array('data' => '', 'sv' => 0), - array('data' => '', 'sv' => 0) - ), $extrainfo); + $details[] = $row; } } return $details; } + +function filebrowser_build_table($files,$folder,$cols){ + $inroot = ($folder == filebrowser_root()); + // Build detailed list of files + $details = array(); + foreach ($files as $file) { + if ( $file == '.'){continue;} + if ($inroot && $file == '..'){continue;} + // the up link is now treated almost normally + + // Some real folder or file + $completepath = "$folder/$file"; + if ($stat = stat($completepath)) { + $row = array(); + // populate the columns + foreach($cols as $colname){ + $func = 'filebrowser_get_cell_'.$colname; + $row[$colname] = (function_exists($func)) ? $func($completepath) : ''; + } + $details[] = $row; + } + } + return $details; +} + + +/** + * Callback to render just one cell of data - the name link + * A filebrowser_get_cell_hook() + */ +function filebrowser_get_cell_name($completepath){ + if(! $completepath){return array('data' => t("Name"), 'field' => 'name'); } + $file = basename($completepath); + if (is_dir($completepath)) { + // for this link I need to prefix the filebrowser relink, BUT chop the filebrowser directory part + $relpath = filebrowser_trim_root($completepath); + $href = filebrowser_proper_path($relpath); + } + else { + $href = $completepath; + } + if ($file == ".." ) { $file = ".. up " ;} + + $link = l($file, $href); + return array('data' => $link, 'class' => 'filename', 'sv' => $file); +} + + +/** + * Return the formatted size of this file. + * A filebrowser_get_cell_hook() + */ +function filebrowser_get_cell_size($completepath){ + if(! $completepath){return array('data' => t("Size"), 'field' => 'size'); } + if(is_dir($completepath)){return array();} + $size = filesize($completepath); + return array('data' => $size?format_size($size):'', 'class' => 'size', 'sv' => ($size ? $size : 0)); +} + +/** + * Return the formatted age of this file. + * A filebrowser_get_cell_hook() + */ +function filebrowser_get_cell_age($completepath){ + if(! $completepath){return array('data' => t("Age"), 'field' => 'age'); } + $age = time() - filemtime($completepath); + return array('data' => format_interval($age), 'class' => 'age', 'sv' => $age); +} + +/** + * Return the deduced description or extended info for this file. + * A filebrowser_get_cell_hook() + */ +function filebrowser_get_cell_info($completepath){ + if(! $completepath){return array('data' => t("Info")); } + static $info; + + if(! isset($info)){ + // The first time this cell is requested, look hard into the current directory + // cache results + if(!is_file($completepath)){ + $subfolder = (is_dir($completepath))?$completepath:dirname($completepath); + $completepath = $subfolder.'/descript.ion'; + if(! file_exists($completepath)){ + $completepath = $subfolder.'/files.bbs'; + } + } + + if(is_file($completepath)){ + $info = filebrowser_get_fileinfo($completepath, dirname($completepath)); + } + else { + $info = array(); + } + } + + $file = basename($completepath); + // each info returned was an array. - not sure how to deal with that in one cell + $detail = (is_array($info[$file])) ? array_pop($info[$file]) : $info[$file] ; + return array('data' => $detail, 'class' => 'info'); +} + + + /** * Loads file metainformation from the specified file. Also * allows the file to specify a *callback* with which the @@ -320,7 +404,7 @@ * a file, based on the extension of the file. A specific icon * can also be requested with the second parameter. */ -function filebrowser_get_icon($fullpath = NULL, $iconname = NULL) { +function filebrowser_get_cell_icon($fullpath = NULL, $iconname = NULL) { if (isset($fullpath)) { $iconname = (is_dir($fullpath) ? 'folder' : preg_replace("!^.+\\.([^\\.]+)$!", "\\1", $fullpath)); } @@ -333,7 +417,7 @@ ); foreach ($iconfiles as $icon) { if (file_exists($icon)) { - return theme("image", $icon); + return theme("image", $icon, $iconname." file", $iconname." file"); } } return ''; @@ -345,7 +429,7 @@ * Also prevent from accessing version control system folders. */ function filebrowser_safe_folder($subfolder) { - $folder = variable_get('filebrowser_root', '') . "/$subfolder"; + $folder = filebrowser_root() . ($subfolder?"/$subfolder":''); while (TRUE) { $safer = str_replace(array("\\", "../", "/.svn", "/CVS", ".."), array("/", "", "", "", ""), $folder); if ($safer !== $folder) { @@ -356,26 +440,37 @@ } } $folder = preg_replace("!^/*([^/].+[^/])/*$!", "\\1", $folder); + // handle the case when superuser has set files folder to '.' + $folder = preg_replace("!^./!", "", $folder); return preg_replace("!/+!", "/", $folder); } /** - * Allows easy path generation even if $path has a leading slash. - */ -function filebrowser_proper_path($path) { - return str_replace("//", "/", "filebrowser/$path"); + * Prepend the filebrowser location to the path. + * Allows easy path generation even if $path has a leading slash. Setting the + * second parameter allows filebrowser to run from different contexts. + * @param $path + * @param $filebrowser_base + * Define where filebrowser is run from. Requires trailing slash. + */ +function filebrowser_proper_path($path, $filebrowser_base=NULL) { + static $base; if(!$base){$base='filebrowser/';} + if($filebrowser_base){$base=$filebrowser_base;} + return preg_replace("|//+|", "/", $base.'/'.$path); } /** * Does the custom sorting of the table contents based on the * sort values (sv) added previously. - **/ + * Note this sorts by named columns, not field numbers as tablesort usually + * does. + */ function filebrowser_sort_table($a, $b = NULL) { static $orderby = 0; static $sort = ''; // Set sorting criteria if (!isset($b)) { - $orderby = ((int) $a[0]['sql']) - 1; // number of field to sort with + $orderby = $a[0]['sql']; // NAME of field to sort with $sort = $a[1]; // asc/desc } // Sort two values with the sorting criteria @@ -391,3 +486,69 @@ } } } + +function filebrowser_set_breadcrumbs($parts){ + $breadcrumb = array(); + $dirname = t('root'); + if ($parts) { + $breadcrumb[] = $dirname = array_pop($parts); + while (count($parts)) { + $breadcrumb[] = l($parts[count($parts) -1], filebrowser_proper_path(join("/", $parts))); + array_pop($parts); + } + $breadcrumb[] = l(t('Filebrowser root'), filebrowser_proper_path('')); + } else { + $breadcrumb[] = t('Filebrowser'); + } + $breadcrumb[] = l(t('Home'), NULL); + drupal_set_breadcrumb(array_reverse($breadcrumb)); +} + +/** + * Returns the base of the allowed area for filebrowsing. + * May be independant of the files directory, and different for different users. + */ +function filebrowser_root(){ + return variable_get('filebrowser_root', file_directory_path()); +} +/** + * Chop the filebrowser root directory (not the url base) off the start of a + * path - translate from a filepath to a relative path. + * + * May operate by reference. + */ +function filebrowser_trim_root(&$path){ + $fbr = filebrowser_root(); + if(substr($path,0,strlen($fbr)) == $fbr){ + $path = substr($path,strlen($fbr)+1); // trim slash also + } + return $path; +} + +/** + * Separate the request from the function. + * Custom filebrowsers can be invoked from any path on the server, and their + * relative subfolders are tacked onto the end of the URL. + * + * [admin/custom/filebrowser/][my/special/files] + * + * Find where to split this request. + * @return the base filebrowser path for the current job. Contains trailing + * slash. + */ +function filebrowser_base($subfolder){ + $filebrowser_base = $subfolder ? substr($_GET['q'],0,strpos($_GET['q'],$subfolder)) : $_GET['q'].'/'; + return $filebrowser_base; +} + +/** + * Tell the system to remember what cols to display when this lookup is called + * again by ajax. An internal memo, so the page which is called asynchronously + * knows what its parent expects to see. + */ +function filebrowser_set_context($context, $cols){ + $filebrowser_contexts = variable_get('filebrowser_contexts',array()); + $filebrowser_contexts[$context] = $cols; + variable_set('filebrowser_contexts',$filebrowser_contexts); +} +