You are here

filetree.module in File Tree 7.2

Same filename and directory in other branches
  1. 6.2 filetree.module
  2. 6 filetree.module
  3. 7 filetree.module

File

filetree.module
View source
<?php

/**
 * Implements hook_theme().
 */
function filetree_theme() {
  return array(
    'filetree' => array(
      'variables' => array(
        'files' => array(),
        'params' => array(),
      ),
    ),
  );
}

/**
 * Implements hook_filter_info().
 */
function filetree_filter_info() {
  $filters['filetree'] = array(
    'title' => t('File Tree'),
    'description' => t('Replaces [filetree dir="some-directory"] with an inline list of files.'),
    'settings callback' => '_filetree_settings',
    'default settings' => array(
      'folders' => '*',
    ),
    'tips callback' => '_filetree_tips',
    'process callback' => '_filetree_process',
  );
  return $filters;
}

/**
 * Implements hook_tokens().
 */
function filetree_tokens($type, $tokens, array $data = array(), array $options = array()) {
  $replacements = array();
  if ($type == 'filetree') {
    foreach ($tokens as $name => $original) {
      switch ($name) {

        // The filename and basename are switched on purpose.
        case 'filename':
          $replacements[$original] = pathinfo($data['filepath'], PATHINFO_BASENAME);
          break;
        case 'basename':
          $replacements[$original] = pathinfo($data['filepath'], PATHINFO_FILENAME);
          break;
        case 'extension':
          $replacements[$original] = pathinfo($data['filepath'], PATHINFO_EXTENSION);
          break;
        case 'size':
          $replacements[$original] = format_size(filesize($data['filepath']));
          break;
        case 'created':
          $replacements[$original] = format_date(filectime($data['filepath']), 'filetree');
          break;
        case 'modified':
          $replacements[$original] = format_date(filemtime($data['filepath']), 'filetree');
          break;
        case 'link':
          if (isset($data['filename'])) {
            $name = isset($data['descriptions'][$data['filename']]) ? $data['descriptions'][$data['filename']] : token_replace($data['params']['filename'], array(
              'filepath' => $data['filepath'],
            ));
            $url = $data['params']['absolute'] ? file_create_url($data['filepath']) : substr(file_create_url($data['filepath']), strlen($GLOBALS['base_url'] . '/'));
            $replacements[$original] = l($name, urldecode($url));
          }
          break;
      }
    }
  }
  return $replacements;
}

/**
 * Implements hook_date_format_types().
 */
function filetree_date_format_types() {
  return array(
    'filetree' => t('File Tree'),
  );
}

/**
 * Implements hook_filter_FILTER_settings().
 */
function _filetree_settings($form, &$form_state, $filter, $format, $defaults, $filters) {
  $filter->settings += $defaults;
  $elements['folders'] = array(
    '#type' => 'textarea',
    '#title' => t('Allowed folder paths'),
    '#description' => t('Enter one folder per line as paths which are allowed to be rendered as a list of files (relative to your <a href="@url">file system path</a>). The "*" character is a wildcard. Example paths are "*", "some-folder", and "some-folder/*".', array(
      '@url' => url('admin/config/media/file-system'),
    )),
    '#default_value' => $filter->settings['folders'],
  );
  return $elements;
}

/**
 * Implements hook_filter_FILTER_tips().
 */
function _filetree_tips($filter, $format, $long = FALSE) {
  $output = t('You may use [filetree dir="some-directory"] to display a list of files inline.');
  if ($long) {
    $output = '<p>' . $output . '</p>';
    $output .= '<p>' . t('Additional options include "multi", "controls", "absolute", "exclude", "dirname", "dirtitle", "filename", "filetitle", and "animation". For example:') . '</p>';
    $output .= '<blockquote>[filetree dir="some-directory" multi="false" controls="false" absolute="false" exclude="CVS; directory1; directory2" dirname="%basename" dirtitle="Click to toggle this folder." filename="%basename" filetitle="Click to download this %extension file." animation="false"]</blockquote>';
    $output .= '<p>' . t('Available tokens for use within the "dirname", "dirtitle", "filename", and "filetitle" options include:') . '</p>';
    $output .= theme('item_list', array(
      'items' => array(
        '%filename: ' . t('Filename, with extension.'),
        '%basename: ' . t('File name, without extension.'),
        '%extension: ' . t('File extension.'),
        '%size: ' . t('Size of the file in human-readable format.'),
        '%created: ' . t('Date the file was created, using the <a href="!url">File Tree format</a>.', array(
          '!url' => url('admin/config/regional/date-time'),
        )),
        '%modified: ' . t('Date the file was modified, using the <a href="!url">File Tree format</a>.', array(
          '!url' => url('admin/config/regional/date-time'),
        )),
      ),
    ));
    $output .= '<p>' . t('You may format the way each file is listed via the "fileformat" option. The tokens above are available, in addition to a "%link" option, which renders the "filename" token. For example:') . '</p>';
    $output .= '<blockquote>[filetree dir="some-directory" fileformat="%link - %size"]</blockquote>';
    $output .= '<p>' . t('You can also read a <a href="http://drupal.org/project/filetree">detailed explanation of these options</a>.') . '</p>';
  }
  return $output;
}

/**
 * Implements hook_filter_FILTER_process().
 */
function _filetree_process($text, $filter, $format, $langcode, $cache, $cache_id) {

  // Look for our special [filetree] token.
  if (!preg_match_all('/(?:<p>)?\\[filetree\\s*(.*?)\\](?:<\\/p>)?/s', $text, $matches)) {
    return $text;
  }

  // Setup our default parameters.
  $default_params = array(
    'dir' => NULL,
    'multi' => TRUE,
    'controls' => TRUE,
    'absolute' => TRUE,
    'exclude' => array(
      'CVS',
    ),
    'dirname' => '%filename',
    'dirtitle' => '%filename',
    'filename' => '%filename',
    'filetitle' => '%filename',
    'fileformat' => '%link',
    'animation' => TRUE,
  );

  // The token might be present multiple times; loop through each instance.
  foreach ($matches[1] as $key => $passed_params) {

    // Load the defaults.
    $params[$key] = $default_params;

    // Parse the parameters (but only the valid ones).
    preg_match_all('/(\\w*)=(?:\\"|&quot;)(.*?)(?:\\"|&quot;)/', $passed_params, $matches2[$key]);
    foreach ($matches2[$key][1] as $param_key => $param_name) {
      if (in_array($param_name, array_keys($default_params))) {

        // If default param is a boolean, convert the passed param to boolean.
        // Note: "false" (as a string) is considered TRUE by PHP, so there's a
        // special check for it.
        if (is_bool($default_params[$param_name])) {
          $params[$key][$param_name] = $matches2[$key][2][$param_key] == "false" ? FALSE : (bool) $matches2[$key][2][$param_key];
        }
        else {
          if ($param_name == 'exclude') {
            $params[$key][$param_name] = array_filter(array_map('trim', explode(';', $matches2[$key][2][$param_key])));
          }
          else {
            $params[$key][$param_name] = $matches2[$key][2][$param_key];
          }
        }
      }
    }

    // Make sure that "dir" was provided,
    if (!$params[$key]['dir'] || !drupal_match_path($params[$key]['dir'], $filter->settings['folders']) || !($params[$key]['uri'] = file_build_uri($params[$key]['dir'])) || !file_prepare_directory($params[$key]['uri'])) {
      continue;
    }

    // Flatten "exclude" array.
    $params[$key]['exclude'] = implode("\n", $params[$key]['exclude']);

    // Convert params containing token values.
    foreach (array(
      'dirname',
      'dirtitle',
      'filename',
      'filetitle',
      'fileformat',
    ) as $token_param) {
      $params[$key][$token_param] = preg_replace('/%(\\w+)/', '[filetree:$1]', $params[$key][$token_param]);
    }

    // Render tree.
    $files = _filetree_list_files($params[$key]['uri'], $params[$key]);
    $rendered = theme('filetree', array(
      'files' => $files,
      'params' => $params[$key],
    ));

    // Replace token with rendered tree.
    $text = str_replace($matches[0][$key], $rendered, $text);
  }
  return $text;
}

/**
 * Recursively lists folders and files in this directory.
 * 
 * Similar to file_scan_directory(), except that we need the hierarchy.
 * Returns a sorted list which is compatible with theme('item_list') or
 * theme('filetree'), folders first, then files.
 */
function _filetree_list_files($dir, $params) {
  $list = array();
  if (is_dir($dir) && ($handle = opendir($dir))) {
    $folders = $files = array();

    // Parse .descript.ion file descriptions.
    $descriptions = _filetree_parse_description($dir);
    while (FALSE !== ($filename = readdir($handle))) {

      // Exclude certain paths:
      // - those which start with a period (".", "..", and hidden files),
      // - additional paths from the "exclude" param
      if ($filename[0] != '.' && !drupal_match_path($filename, $params['exclude'])) {
        $filepath = "{$dir}/{$filename}";
        $token_args = array(
          'filename' => $filename,
          'filepath' => $filepath,
          'descriptions' => $descriptions,
          'params' => $params,
        );

        // It's a folder.
        if (is_dir($filepath)) {
          $folders[$filename] = array(
            'data' => isset($descriptions[$filename]) ? $descriptions[$filename] : token_replace($params['dirname'], $token_args),
            'children' => _filetree_list_files($filepath, $params),
            'title' => token_replace($params['dirtitle'], $token_args),
            'class' => array(
              'folder',
            ),
          );
        }
        else {
          $files[$filename] = array(
            'data' => token_replace($params['fileformat'], $token_args),
            'title' => token_replace($params['filetitle'], $token_args),
            'class' => array(
              _filetree_icon(pathinfo($filename, PATHINFO_EXTENSION)),
            ),
          );
        }
      }
    }
    closedir($handle);

    // Sort.
    asort($folders);
    asort($files);
    $list += $folders;
    $list += $files;
  }
  return $list;
}

/**
 * Parse .descript.ion file descriptions.
 */
function _filetree_parse_description($dir) {
  $descriptions = array();
  if (is_readable("{$dir}/.descript.ion") && ($file = file("{$dir}/.descript.ion"))) {
    foreach ($file as $line) {
      $line = trim($line);

      // Skip empty and commented lines
      if ($line == '' || strpos($line, '#') === 0) {
        continue;
      }
      $matches = array();

      // File names may be encapsulated in quotations.
      if (strpos($line, '"') === 0) {
        preg_match('/^"([^"]+)"\\s+(.*)$/', $line, $matches);
      }
      else {
        preg_match('/^(\\S+)\\s+(.*)$/', $line, $matches);
      }
      list(, $name, $description) = $matches;
      if (isset($descriptions[$name])) {
        $descriptions[$name] .= ' ' . trim($description);
      }
      else {
        $descriptions[$name] = trim($description);
      }
    }
  }
  return $descriptions;
}

/**
 * Determines which icon should be displayed, based on file extension.
 */
function _filetree_icon($extension) {
  $extension = strtolower($extension);
  $icon = 'file';
  $map = array(
    'application' => array(
      'exe',
    ),
    // 'code' => array(''),
    'css' => array(
      'css',
    ),
    'db' => array(
      'sql',
    ),
    'doc' => array(
      'doc',
      'docx',
    ),
    'film' => array(
      'avi',
      'mov',
    ),
    'flash' => array(
      'flv',
      'swf',
    ),
    'html' => array(
      'htm',
      'html',
    ),
    // 'java' => array(''),
    // 'linux' => array(''),
    'music' => array(
      'mp3',
      'aac',
    ),
    'pdf' => array(
      'pdf',
    ),
    'php' => array(
      'php',
    ),
    'image' => array(
      'jpg',
      'jpeg',
      'gif',
      'png',
      'bmp',
    ),
    'ppt' => array(
      'ppt',
    ),
    'psd' => array(
      'psd',
    ),
    // 'ruby' => array(''),
    'script' => array(
      'asp',
    ),
    'txt' => array(
      'txt',
    ),
    'xls' => array(
      'xls',
      'xlsx',
    ),
    'zip' => array(
      'zip',
    ),
  );
  foreach ($map as $key => $values) {
    foreach ($values as $value) {
      if ($extension == $value) {
        $icon = $key;
      }
    }
  }
  return $icon;
}

/**
 * Renders filetree.
 */
function theme_filetree($variables) {
  $files = $variables['files'];
  $params = $variables['params'];
  $output = '';

  // Render controls (but only if multiple folders is enabled, and only if
  // there is at least one folder to expand/collapse).
  if ($params['multi'] && $params['controls']) {
    $has_folder = FALSE;
    foreach ($files as $file) {
      if (isset($file['children'])) {
        $has_folder = TRUE;
        break;
      }
    }
    if ($has_folder) {
      $controls = array(
        '<a href="#" class="expand">' . t('expand all') . '</a>',
        '<a href="#" class="collapse">' . t('collapse all') . '</a>',
      );
      $output .= theme('item_list', array(
        'items' => $controls,
        'title' => NULL,
        'type' => 'ul',
        'attributes' => array(
          'class' => 'controls',
        ),
      ));
    }
  }

  // Render files.
  $output .= theme('item_list', array(
    'items' => $files,
    'title' => NULL,
    'type' => 'ul',
    'attributes' => array(
      'class' => 'files',
    ),
  ));

  // Generate classes and unique ID for wrapper div.
  $id = drupal_clean_css_identifier(uniqid('filetree-'));
  $classes = array(
    'filetree',
  );
  if ($params['multi']) {
    $classes[] = 'multi';
  }

  // If using animation, add class.
  if ($params['animation']) {
    $classes[] = 'filetree-animation';
  }
  return '<div id="' . $id . '" class="' . implode(' ', $classes) . '">' . $output . '</div>';
}

Functions

Namesort descending Description
filetree_date_format_types Implements hook_date_format_types().
filetree_filter_info Implements hook_filter_info().
filetree_theme Implements hook_theme().
filetree_tokens Implements hook_tokens().
theme_filetree Renders filetree.
_filetree_icon Determines which icon should be displayed, based on file extension.
_filetree_list_files Recursively lists folders and files in this directory.
_filetree_parse_description Parse .descript.ion file descriptions.
_filetree_process Implements hook_filter_FILTER_process().
_filetree_settings Implements hook_filter_FILTER_settings().
_filetree_tips Implements hook_filter_FILTER_tips().