changeset 0:ddfec30e1789 tip

Import public
author Franck Deroche <webmaster@defr.org>
date Mon, 01 Sep 2008 19:27:32 +0200
parents
children
files filearchiver.info filearchiver.install filearchiver.module
diffstat 3 files changed, 449 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/filearchiver.info	Mon Sep 01 19:27:32 2008 +0200
@@ -0,0 +1,6 @@
+; $Id: ows.info,v 1.1 2007/08/02 10:16:21 julien Exp $
+name = File Archiver
+package = "OWS"
+description = "Archive file associated to nodes"
+version = "5.x-dev"
+dependencies = views token
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/filearchiver.install	Mon Sep 01 19:27:32 2008 +0200
@@ -0,0 +1,35 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * File Archiver install file
+ *
+ * This file allows the creation of the tables needed by File Archiver.
+ */
+function filearchiver_install() {
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      db_query("CREATE TABLE {filearchiver_files}(
+        fid int not null default '0',
+        source int not null default '1',
+        archive varchar(250),
+        PRIMARY KEY(fid, source)
+      );");
+      break;
+    case 'pgsql':
+      db_query("CREATE TABLE {filearchiver_files}(
+        fid int not null default '0',
+        source int not null default '1',
+        archive varchar(50),
+        PRIMARY KEY(fid, source)
+      ");
+      break;
+  }
+}
+
+function filearchiver_uninstall() {
+  db_query('DROP TABLE {filearchiver_files}');
+  variable_del('filearchiver_sources');
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/filearchiver.module	Mon Sep 01 19:27:32 2008 +0200
@@ -0,0 +1,408 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * File Archiver module
+ *
+ * This module provides a mechanism to move files associated to nodes to
+ * other directories, for example to archive them.
+ */
+define('FILEARCHIVER_SOURCE_NODE', 1);
+define('FILEARCHIVER_SOURCE_COMMENT', 2);
+
+/**
+ * Implementation of hook_help().
+ */
+
+function filearchiver_help($section) {
+  switch ($section) {
+    case 'admin/settings/filearchiver':
+      return t('This section allows you to choose the views defining which nodes,
+      if any, should see its files archived. In practice, the archiving is done
+      by moving the files of those nodes to a directory mentionning the date
+      and time of the last modification on this node. You are then able to
+      move away this folder if you want to, and then simply restore it should
+      you need to see the attached files afterwards.');
+    break;
+  }
+}
+
+/**
+ * Implementation of hook_menu().
+ * The only page we need to expose is the configuration page
+ * of the module.
+ */
+function filearchiver_menu($may_cache) {
+  $items = array();
+  if ($may_cache) {
+    // The settings form
+    $items[] = array(
+      'path'               => 'admin/settings/filearchiver',
+      'title'              => t('File archiver'),
+      'access'             => user_access('administer filearchiver'),
+      'callback'           => 'drupal_get_form',
+      'callback arguments' => array('filearchiver_settings_form')
+    );
+    // The "File has been archived" page
+    $items[] = array(
+      'path'               => 'filearchiver/archived',
+      'title'              => t('File archived'),
+      'access'             => user_access('access content'),
+      'callback'           => 'filearchiver_archived_page'
+    );
+    // The status page
+    $items[] = array(
+      'path'               => 'filearchiver',
+      'title'              => 'File Archiver',
+      'callback'           => 'filearchiver_dry_run',
+      'access'             => user_access('administer filearchiver')
+    );
+    // Overwrite system default file callback
+    // This is needed to be able to act on non existing files,
+    // even though it's really suboptimal.
+    $items[] = array(
+      'path'               => 'system/files',
+      'title'              => t('File download'),
+      'callback'           => 'filearchiver_download',
+      'access'             => TRUE,
+      'type'               => MENU_CALLBACK
+    );
+  }
+  return $items;
+}
+
+/**
+ * Implementation of hook_perm().
+ */
+function filearchiver_perm() {
+  return array('administer filearchiver', 'restore archives');
+}
+
+
+/**
+ * This function filter the call to non-exising archived files.
+ * For existing files, it's a downright copy & paste of file_download
+ * in file.inc, which should be faster than calling it.
+ */
+function filearchiver_download() {
+  // Merge remainder of arguments from GET['q'], into relative file path.
+  $args = func_get_args();
+  $filepath = implode('/', $args);
+
+  // Maintain compatibility with old ?file=paths saved in node bodies.
+  if (isset($_GET['file'])) {
+    $filepath =  $_GET['file'];
+  }
+
+  if (file_exists(file_create_path($filepath))) {
+    $headers = module_invoke_all('file_download', $filepath);
+    if (in_array(-1, $headers)) {
+        return drupal_access_denied();
+    }
+    if (count($headers)) {
+        file_transfer($filepath, $headers);
+    }
+  }
+  else {
+    // Check if the file has been archived
+    $count = db_result(db_query("
+      SELECT COUNT(fid)
+      FROM {filearchiver_files}
+      WHERE archive='%s'", file_directory_path() .'/'. $filepath));
+    if ($count) {
+      return filearchiver_archived_page();
+    }
+  }
+  return drupal_not_found();
+}
+/**
+ * The "File has been archived page".
+ * This is the landing page in case of a file archived
+ * and no longer in the filesystem.
+ */
+function filearchiver_archived_page() {
+  drupal_set_title('File archived');
+  return "<p>Sorry, this file has been archived and can't be seen
+  online at the moment. If you need to access it, please contact the
+  support who will be able to put it back online.</p>";
+}
+
+/**
+ * Dry-run callback implementation.
+ * This should be quite usefull to test that everything's alright.
+ */
+function filearchiver_dry_run() {
+  $rows = _filearchiver_archive(TRUE);
+  $output = theme('table', array('nid', 'filename', 'source', 'status', 'target'), $rows);
+  $output .= drupal_get_form('filearchiver_archive_form');
+  return $output;
+}
+
+/**
+ * Form located on the dry run page and allowing users
+ * to start the archiving process.
+ */
+function filearchiver_archive_form() {
+  $form = array();
+  $form[] = array(
+    '#type'  => 'submit',
+    '#title' => t('Archive now'),
+    '#value' => t('Archive now')
+  );
+  return $form;
+}
+
+/**
+ * Callback of the "Archive now" form that actually starts the process.
+ */
+function filearchiver_archive_form_submit() {
+  _filearchiver_archive(FALSE);
+}
+
+/**
+ * Implementation of hook_cron().
+ */
+function filearchiver_cron() {
+  if (variable_get('filearchiver_run_on_cron', FALSE)) {
+    _filearchiver_archive(FALSE);
+  }
+}
+
+/**
+ * Do the real archiving work.
+ */
+function _filearchiver_archive($dry_run = TRUE) {
+  $rows = array();
+  // First of all, get the list of our sources
+  $sources = variable_get('filearchiver_sources', array());
+  foreach ($sources as $vid => $active) {
+    if ($active) {
+      /* If the view is checked as a provider for the file archiver,
+         then we're going to ask view to get all the nodes that match,
+         in a convenient items array. */
+      $view = views_get_view($vid);
+      $result = views_build_view('items', $view);
+      foreach ($result['items'] as $item) {
+        // Then for each of those nodes,  we'll load it
+        $node = node_load($item->nid);
+        // And deal with the files directly attached to the node
+        foreach ($node->files as $file) {
+          if ($dry_run) {
+            $rows[] = _filearchiver_get_infos($node, $file, FILEARCHIVER_SOURCE_NODE);
+          }
+          else {
+            _filearchiver_archive_node_file($node, $file);
+          }
+        }
+        /* Then we need to take care of all the files that might be
+           attached to the comments if the Comment Upload module is
+           active */
+        if (db_table_exists('{comment_upload_files}') &&
+            function_exists('_comment_upload_load_files')) {
+          $result = db_query("SELECT DISTINCT(cid) FROM {comment_upload_files} WHERE nid=%d",
+                              $node->nid);
+          while ($row = db_fetch_object($result)) {
+            $files = _comment_upload_load_files($row->cid, $item->nid);
+            foreach ($files as $file) {
+              if ($dry_run) {
+                $rows[] = _filearchiver_get_infos($node, $file, FILEARCHIVER_SOURCE_COMMENT);
+              }
+              else {
+                _filearchiver_archive_comment_file($node, $row->cid, $file);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  return $rows;
+}
+
+/**
+ * Callback for the settings form.
+ */
+function filearchiver_settings_form() {
+  $form = array();
+  // Choose if the archiving should be done on cron runs
+  $form['filearchiver_run_on_cron'] = array(
+    '#type'          => 'checkbox',
+    '#title'         => t('Run on cron'),
+    '#description'   => t('If checked, files will be automatically
+                           archived on cron when any of the views selected
+                           below match the node to which it is attached'),
+    '#default_value' => variable_get('filearchiver_run_on_cron', FALSE)
+  );
+  // Build the list of all available views
+  $form['filearchiver_sources'] = array(
+    '#type'          => 'checkboxes',
+    '#title'         => t('Sources'),
+    '#description'   => t('Please select the views corresponding to the nodes
+                           whose files you want to archive'),
+    '#default_value' => variable_get('filearchiver_sources', array())
+  );
+  $views = array();
+  $result = db_query("SELECT vid, name, description FROM {view_view}");
+  while ($view = db_fetch_object($result)) {
+    $views[$view->vid] = theme('filearchiver_show_view', $view);
+  }
+  $form['filearchiver_sources']['#options'] = $views;
+  // Let the user choose where the files will be archived
+  $form['filearchiver_path'] = array(
+    '#title'       => t('Pattern for the archive path'),
+    '#type'        => 'textfield',
+    '#description' => t('Specify the pattern to prefix to file names that will be
+    archived.  It will be appended after the site files directory (!directory) but
+    before the file name itself.  Do not include a leading or trailing slash.
+    Spaces will be converted to underscores to avoid file system issues.',
+                        array('!directory' => file_directory_path())),
+    '#default_value' => variable_get('filearchiver_path', 'archives/[yyyy]/[mm]')
+  );
+  // Build the token help
+  $form['token_help'] = array(
+    '#title' => t('Replacement patterns'),
+    '#type' => 'fieldset',
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#description' => t('Prefer raw-text replacements for text to avoid problems with HTML entities!'),
+  );
+  $form['token_help']['help'] = array(
+    '#value' => theme('token_help', 'node'),
+  );
+
+  return system_settings_form($form);
+}
+
+/**
+ * Callback for the settings form submission.
+ */
+function filearchiver_settings_form_submit($form_id, &$form_values) {
+  variable_set('filearchiver_sources', $form_values['filearchiver_sources']);
+  variable_set('filearchiver_path', $form_values['filearchiver_path']);
+}
+
+/**
+ * Theme the view name and it's description for display in the view list.
+ */
+function theme_filearchiver_show_view($view) {
+  return "<strong>{$view->name}</strong>, {$view->description}";
+}
+
+/**
+ * Check if the file has already been archived.
+ */
+function _filearchiver_file_is_archived($fid, $source = FILEARCHIVER_SOURCE_NODE) {
+  return db_result(db_query("
+    SELECT COUNT(fid)
+    FROM {filearchiver_files}
+    WHERE
+      fid=%d AND source=%d", $fid, $source));
+}
+
+/**
+ * Get the archive name from the filename
+ */
+function _filearchiver_get_filepath($node, $filepath) {
+  $basename = basename($filepath);
+  //$dirname = dirname($filepath);
+  $timestamp = max($node->last_comment_timestamp, $node->revision_date);
+  // First get the template from the configuration screen
+  $tpl = variable_get('filearchiver_path', 'archives/[yyyy]/[mm]');
+  // Then replace the tokens in it
+  $archive = '/'. token_replace($tpl, 'node', $node);
+  // Finally get rid of the whitespaces that may not work all that well on filenames
+  $archive = str_replace(array(' ', "\n", "\t"), '_', $archive);
+  $path = _filearchiver_ensure_path_exists($archive);
+  return $path .'/'. $basename;
+}
+
+/**
+ * Ensure that a specific path actually exists
+ * @param A path relative to the file_directory_path.
+ *        This path will be sanitized.
+ * @return Full path for the directory
+ */
+function _filearchiver_ensure_path_exists($archive) {
+  $dirs = explode('/', $archive);
+  $directory = file_directory_path();
+  while (count($dirs)) {
+    $current = array_shift($dirs);
+    /* Ban '.' and '..': those values might be contained in the input
+     * provided by the user and transfered in the archive path by the
+     * token module
+     */
+    if (!in_array($current, array('.', '..'))) {
+      $directory .= '/'. $current;
+      file_check_directory($directory, FILE_CREATE_DIRECTORY);
+    }
+  }
+  return $directory;
+}
+
+/**
+ * Archive a file attached directly to the node.
+ */
+function _filearchiver_archive_node_file($node, $file) {
+  $archived = _filearchiver_file_is_archived($file->fid, FILEARCHIVER_SOURCE_NODE);
+  if (!$archived) {
+    // If this file isn't archived yet, we'll deal with it
+    $target = _filearchiver_get_filepath($node, $file->filepath);
+    // Move the file over there
+    file_move($file, $target, FILE_EXISTS_RENAME);
+    // Update the table
+    db_query("
+      UPDATE {files}
+      SET filepath='%s'
+      WHERE nid=%d AND fid=%d",
+      $file->filepath,
+      $node->nid,
+      $file->fid);
+    // Insert into our table to track that we've archived this file
+    db_query("INSERT INTO {filearchiver_files}(fid, source, archive) VALUES(%d, %d, '%s')",
+              $file->fid,
+              FILEARCHIVER_SOURCE_NODE,
+              $target);
+  }
+}
+
+/**
+ * Archive a file attached to a comment of the node.
+ * This happen if the module 'Comment Upload' is in
+ * use.
+ */
+function _filearchiver_archive_comment_file($node, $cid, $file) {
+  $archived = _filearchiver_file_is_archived($file->fid, FILEARCHIVER_SOURCE_COMMENT);
+  if (!$archived) {
+    // Get the destination path
+    $target = _filearchiver_get_filepath($node, $file->filepath);
+    // Move the file over there
+    file_move($file, $target, FILE_EXISTS_RENAME);
+    // Update the table
+    db_query("
+      UPDATE {comment_upload_files}
+      SET filepath='%s'
+      WHERE nid=%d AND cid=%d AND fid=%d",
+      $file->filepath,
+      $node->nid,
+      $cid,
+      $file->fid);
+    // Insert into our table to track that we've archived this file
+    db_query("INSERT INTO {filearchiver_files}(fid, source, archive) VALUES(%d, %d, '%s')",
+              $file->fid,
+              FILEARCHIVER_SOURCE_COMMENT,
+              $target);
+  }
+}
+
+/**
+ * Extract the information that will be presented about the file
+ * on a dry-run.
+ */
+function _filearchiver_get_infos($node, $file, $source) {
+  $archived = _filearchiver_file_is_archived($file->fid, $source);
+  $from = ($source == FILEARCHIVER_SOURCE_NODE) ? t('node') : t('comment');
+  $status = ($archived) ? t('archived') : t('live');
+  $target = _filearchiver_get_filepath($node, $file->filepath);
+  return array(l($node->nid, 'node/'. $node->nid), $file->filename, $from, $status, $target);
+}