You are here

filebrowser.module in Filebrowser 5

File

filebrowser.module
View source
<?php

/**
 * Implementation of hook_help().
 */
function filebrowser_help($section = NULL, $sethelp = NULL) {
  static $pagehelp = '';
  if (!isset($section)) {
    $pagehelp = $sethelp;
  }
  elseif ($section == $_GET['q'] && $pagehelp) {
    return $pagehelp;
  }
}

/**
 * Form for settings menu callback
 */
function filebrowser_admin_settings() {
  $form = array(
    'filebrowser_root' => array(
      '#type' => 'textfield',
      '#title' => t('Root directory'),
      '#default_value' => variable_get('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".'),
    ),
    'filebrowser_icons' => array(
      '#type' => 'textfield',
      '#title' => t('Icon directory'),
      '#default_value' => variable_get('filebrowser_icons', ''),
      '#maxlength' => '100',
      '#size' => '70',
      '#description' => t('Name of directory, where file type icons are stored. Files should be named "file-txt.png", "file-gif.png", etc. The default icon is "file-default.png".'),
    ),
    'filebrowser_hide_description_files' => array(
      '#type' => 'radios',
      '#title' => t('Display of description files'),
      '#default_value' => variable_get('filebrowser_hide_description_files', 0),
      '#options' => array(
        t('Show'),
        t('Hide'),
      ),
      '#description' => t('Whether to show or hide description files from directory listings.'),
    ),
  );
  return system_settings_form($form);
}

/**
 * Implementation of hook_perm().
 */
function filebrowser_perm() {
  return array(
    'access filebrowser',
  );
}

/**
 * Implementation of hook_menu().
 */
function filebrowser_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'filebrowser',
      'title' => t('Filebrowser'),
      'access' => user_access('access filebrowser'),
      'callback' => 'filebrowser_page',
      'type' => MENU_SUGGESTED_ITEM,
    );
    $items[] = array(
      'path' => 'admin/settings/filebrowser',
      'title' => t('Filebrowser'),
      'description' => t('Set filebrowser root folder and display properties.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'filebrowser_admin_settings',
      ),
      'access' => user_access('administer site configuration'),
    );
  }
  return $items;
}

/**
 * Prints a folder layout
 */
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));

  // Set page title
  drupal_set_title(t('@dirname directory', array(
    "@dirname" => $dirname,
  )));

  // 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.'));
    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());

  // 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'] == '') {
      $folders[] = $filei;
      unset($files[$rnum]);
    }
  }
  usort($files, 'filebrowser_sort_table');
  $files = array_merge($folders, $files);

  // Drop attributes only used for sorting, so
  // these will not end up in the HTML output
  foreach ($files as $rnum => $filei) {
    foreach ($filei as $cnum => $cell) {
      if (is_array($cell)) {
        unset($filei[$cnum]['sv']);
      }
    }
    $files[$rnum] = $filei;
  }

  // Allow modules to inject content above the table
  $pre = join('', module_invoke_all('filebrowser_pre', $subfolder));

  // Allow for themeing of the table
  return theme('filebrowser_page', $files, $headers, $pre);
}

/**
 * Theme a filebrowser page, if files are available or not.
 * Here you have some possibility to reformat the data or the table layout.
 */
function theme_filebrowser_page(&$files, $header = NULL, $pre = NULL) {
  if ($files) {

    // CSS can hook on this ID to style table elements differently
    return '<div id="filebrowser-page">' . $pre . theme("table", $header, $files) . '</div>';
  }
  else {
    return '';
  }
}

/**
 * Returns a list of files in a subfolder under the admin
 * 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;
  $folder = filebrowser_safe_folder($subfolder);
  $inroot = $folder == variable_get('filebrowser_root', '');

  // Signal error in case of bogus directory name
  if (!(file_exists($folder) && is_dir($folder) && ($dir = opendir($folder)))) {
    return FALSE;
  }

  // Collect folders and files separately and check for a metainfo file
  $files = $folders = array();
  $infofile = FALSE;
  while (($entry = readdir($dir)) !== FALSE) {
    if (is_dir("{$folder}/{$entry}")) {

      // Skip version control system folders
      if (!in_array($entry, array(
        ".svn",
        "CVS",
      ))) {
        $folders[] = $entry;
      }
    }
    else {

      // Skip .htaccess file from being displayed
      if ($entry == '.htaccess') {
        continue;
      }

      // Grab information file, decide whether it should be shown or not
      if (in_array(strtolower($entry), array(
        "descript.ion",
        "files.bbs",
      ))) {
        $infofile = $entry;
        if (!variable_get('filebrowser_hide_description_files', 0)) {
          $files[] = $entry;
        }
      }
      else {
        $files[] = $entry;
      }
    }
  }
  closedir($dir);

  // Order folders first, then files
  sort($folders);
  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;

    // 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 = "<a href=\"{$base_path}{$completepath}\">{$icon} {$file}</a>";
          $size = format_size($stat['size']);
        }
        $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);
      }
    }
    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} &lt;{$up}&gt;", 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);
    }
  }
  return $details;
}

/**
 * Loads file metainformation from the specified file. Also
 * allows the file to specify a *callback* with which the
 * descriptions are parsed, so more metainformation can be
 * presented on the output.
 */
function filebrowser_get_fileinfo($fullpath = NULL, $subfolder = '') {
  static $metacols = array();

  // Return (previously generated) meta column list
  if (!isset($fullpath)) {
    return $metacols;
  }

  // Build meta information list
  $metainfo = array();
  if (is_readable($fullpath) && ($file = file($fullpath))) {
    foreach ($file as $line) {

      // Skip empty and commented lines
      if (trim($line) == '' || strpos(trim($line), '#') === 0) {
        continue;
      }
      list($name, $description) = explode(" ", $line, 2);
      if (isset($metainfo[$name])) {
        $metainfo[$name] .= trim($description) . " ";
      }
      else {
        $metainfo[$name] = trim($description) . " ";
      }
    }
    $callback = FALSE;
    if (isset($metainfo['*callback*']) && function_exists(trim($metainfo['*callback*']))) {
      $callback = trim($metainfo['*callback*']);
      unset($metainfo['*callback*']);
    }
    if (isset($metainfo['.'])) {
      filebrowser_help(NULL, $metainfo['.']);
      unset($metainfo['.']);
    }
    foreach ($metainfo as $name => $description) {
      $metainfo[$name] = $callback ? $callback(trim($description), $subfolder, $name) : array(
        trim($description),
      );
    }
    $metacols = $callback ? $callback() : array(
      t('Description'),
    );
  }
  return $metainfo;
}

/**
 * Returns the appropriate HTML code for an icon representing
 * 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) {
  if (isset($fullpath)) {
    $iconname = is_dir($fullpath) ? 'folder' : preg_replace("!^.+\\.([^\\.]+)\$!", "\\1", $fullpath);
  }
  elseif (!isset($iconname)) {
    $iconname = 'default';
  }
  $iconfiles = array(
    variable_get('filebrowser_icons', '') . "/file-{$iconname}.png",
    variable_get('filebrowser_icons', '') . "/file-default.png",
  );
  foreach ($iconfiles as $icon) {
    if (file_exists($icon)) {
      return theme("image", $icon);
    }
  }
  return '';
}

/**
 * Convert windows path values to use slashes, prevent slashes used 
 * repeatedly, and try to catch and eliminate path walkback attemepts.
 * Also prevent from accessing version control system folders.
 */
function filebrowser_safe_folder($subfolder) {
  $folder = variable_get('filebrowser_root', '') . "/{$subfolder}";
  while (TRUE) {
    $safer = str_replace(array(
      "\\",
      "../",
      "/.svn",
      "/CVS",
      "..",
    ), array(
      "/",
      "",
      "",
      "",
      "",
    ), $folder);
    if ($safer !== $folder) {
      $folder = $safer;
    }
    else {
      break;
    }
  }
  $folder = preg_replace("!^/*([^/].+[^/])/*\$!", "\\1", $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}");
}

/**
 * Does the custom sorting of the table contents based on the
 * sort values (sv) added previously.
 **/
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
    $sort = $a[1];

    // asc/desc
  }
  elseif (is_array($a) && isset($a[$orderby]) && isset($a[$orderby]['sv'])) {
    if ($a[$orderby]['sv'] == $b[$orderby]['sv']) {
      return 0;
    }
    if ($sort == 'asc') {
      return $a[$orderby]['sv'] > $b[$orderby]['sv'];
    }
    else {
      return $a[$orderby]['sv'] < $b[$orderby]['sv'];
    }
  }
}

Functions

Namesort descending Description
filebrowser_admin_settings Form for settings menu callback
filebrowser_get_fileinfo Loads file metainformation from the specified file. Also allows the file to specify a *callback* with which the descriptions are parsed, so more metainformation can be presented on the output.
filebrowser_get_icon Returns the appropriate HTML code for an icon representing a file, based on the extension of the file. A specific icon can also be requested with the second parameter.
filebrowser_get_list Returns a list of files in a subfolder under the admin specified filebrowser root. File system details (size, last modification) is added, plus a metafile is parsed to gather more information, if available.
filebrowser_help Implementation of hook_help().
filebrowser_menu Implementation of hook_menu().
filebrowser_page Prints a folder layout
filebrowser_perm Implementation of hook_perm().
filebrowser_proper_path Allows easy path generation even if $path has a leading slash.
filebrowser_safe_folder Convert windows path values to use slashes, prevent slashes used repeatedly, and try to catch and eliminate path walkback attemepts. Also prevent from accessing version control system folders.
filebrowser_sort_table Does the custom sorting of the table contents based on the sort values (sv) added previously.
theme_filebrowser_page Theme a filebrowser page, if files are available or not. Here you have some possibility to reformat the data or the table layout.