annotate filearchiver.module @ 0:ddfec30e1789 tip

Import public
author Franck Deroche <webmaster@defr.org>
date Mon, 01 Sep 2008 19:27:32 +0200
parents
children
rev   line source
webmaster@0 1 <?php
webmaster@0 2 // $Id$
webmaster@0 3
webmaster@0 4 /**
webmaster@0 5 * @file
webmaster@0 6 * File Archiver module
webmaster@0 7 *
webmaster@0 8 * This module provides a mechanism to move files associated to nodes to
webmaster@0 9 * other directories, for example to archive them.
webmaster@0 10 */
webmaster@0 11 define('FILEARCHIVER_SOURCE_NODE', 1);
webmaster@0 12 define('FILEARCHIVER_SOURCE_COMMENT', 2);
webmaster@0 13
webmaster@0 14 /**
webmaster@0 15 * Implementation of hook_help().
webmaster@0 16 */
webmaster@0 17
webmaster@0 18 function filearchiver_help($section) {
webmaster@0 19 switch ($section) {
webmaster@0 20 case 'admin/settings/filearchiver':
webmaster@0 21 return t('This section allows you to choose the views defining which nodes,
webmaster@0 22 if any, should see its files archived. In practice, the archiving is done
webmaster@0 23 by moving the files of those nodes to a directory mentionning the date
webmaster@0 24 and time of the last modification on this node. You are then able to
webmaster@0 25 move away this folder if you want to, and then simply restore it should
webmaster@0 26 you need to see the attached files afterwards.');
webmaster@0 27 break;
webmaster@0 28 }
webmaster@0 29 }
webmaster@0 30
webmaster@0 31 /**
webmaster@0 32 * Implementation of hook_menu().
webmaster@0 33 * The only page we need to expose is the configuration page
webmaster@0 34 * of the module.
webmaster@0 35 */
webmaster@0 36 function filearchiver_menu($may_cache) {
webmaster@0 37 $items = array();
webmaster@0 38 if ($may_cache) {
webmaster@0 39 // The settings form
webmaster@0 40 $items[] = array(
webmaster@0 41 'path' => 'admin/settings/filearchiver',
webmaster@0 42 'title' => t('File archiver'),
webmaster@0 43 'access' => user_access('administer filearchiver'),
webmaster@0 44 'callback' => 'drupal_get_form',
webmaster@0 45 'callback arguments' => array('filearchiver_settings_form')
webmaster@0 46 );
webmaster@0 47 // The "File has been archived" page
webmaster@0 48 $items[] = array(
webmaster@0 49 'path' => 'filearchiver/archived',
webmaster@0 50 'title' => t('File archived'),
webmaster@0 51 'access' => user_access('access content'),
webmaster@0 52 'callback' => 'filearchiver_archived_page'
webmaster@0 53 );
webmaster@0 54 // The status page
webmaster@0 55 $items[] = array(
webmaster@0 56 'path' => 'filearchiver',
webmaster@0 57 'title' => 'File Archiver',
webmaster@0 58 'callback' => 'filearchiver_dry_run',
webmaster@0 59 'access' => user_access('administer filearchiver')
webmaster@0 60 );
webmaster@0 61 // Overwrite system default file callback
webmaster@0 62 // This is needed to be able to act on non existing files,
webmaster@0 63 // even though it's really suboptimal.
webmaster@0 64 $items[] = array(
webmaster@0 65 'path' => 'system/files',
webmaster@0 66 'title' => t('File download'),
webmaster@0 67 'callback' => 'filearchiver_download',
webmaster@0 68 'access' => TRUE,
webmaster@0 69 'type' => MENU_CALLBACK
webmaster@0 70 );
webmaster@0 71 }
webmaster@0 72 return $items;
webmaster@0 73 }
webmaster@0 74
webmaster@0 75 /**
webmaster@0 76 * Implementation of hook_perm().
webmaster@0 77 */
webmaster@0 78 function filearchiver_perm() {
webmaster@0 79 return array('administer filearchiver', 'restore archives');
webmaster@0 80 }
webmaster@0 81
webmaster@0 82
webmaster@0 83 /**
webmaster@0 84 * This function filter the call to non-exising archived files.
webmaster@0 85 * For existing files, it's a downright copy & paste of file_download
webmaster@0 86 * in file.inc, which should be faster than calling it.
webmaster@0 87 */
webmaster@0 88 function filearchiver_download() {
webmaster@0 89 // Merge remainder of arguments from GET['q'], into relative file path.
webmaster@0 90 $args = func_get_args();
webmaster@0 91 $filepath = implode('/', $args);
webmaster@0 92
webmaster@0 93 // Maintain compatibility with old ?file=paths saved in node bodies.
webmaster@0 94 if (isset($_GET['file'])) {
webmaster@0 95 $filepath = $_GET['file'];
webmaster@0 96 }
webmaster@0 97
webmaster@0 98 if (file_exists(file_create_path($filepath))) {
webmaster@0 99 $headers = module_invoke_all('file_download', $filepath);
webmaster@0 100 if (in_array(-1, $headers)) {
webmaster@0 101 return drupal_access_denied();
webmaster@0 102 }
webmaster@0 103 if (count($headers)) {
webmaster@0 104 file_transfer($filepath, $headers);
webmaster@0 105 }
webmaster@0 106 }
webmaster@0 107 else {
webmaster@0 108 // Check if the file has been archived
webmaster@0 109 $count = db_result(db_query("
webmaster@0 110 SELECT COUNT(fid)
webmaster@0 111 FROM {filearchiver_files}
webmaster@0 112 WHERE archive='%s'", file_directory_path() .'/'. $filepath));
webmaster@0 113 if ($count) {
webmaster@0 114 return filearchiver_archived_page();
webmaster@0 115 }
webmaster@0 116 }
webmaster@0 117 return drupal_not_found();
webmaster@0 118 }
webmaster@0 119 /**
webmaster@0 120 * The "File has been archived page".
webmaster@0 121 * This is the landing page in case of a file archived
webmaster@0 122 * and no longer in the filesystem.
webmaster@0 123 */
webmaster@0 124 function filearchiver_archived_page() {
webmaster@0 125 drupal_set_title('File archived');
webmaster@0 126 return "<p>Sorry, this file has been archived and can't be seen
webmaster@0 127 online at the moment. If you need to access it, please contact the
webmaster@0 128 support who will be able to put it back online.</p>";
webmaster@0 129 }
webmaster@0 130
webmaster@0 131 /**
webmaster@0 132 * Dry-run callback implementation.
webmaster@0 133 * This should be quite usefull to test that everything's alright.
webmaster@0 134 */
webmaster@0 135 function filearchiver_dry_run() {
webmaster@0 136 $rows = _filearchiver_archive(TRUE);
webmaster@0 137 $output = theme('table', array('nid', 'filename', 'source', 'status', 'target'), $rows);
webmaster@0 138 $output .= drupal_get_form('filearchiver_archive_form');
webmaster@0 139 return $output;
webmaster@0 140 }
webmaster@0 141
webmaster@0 142 /**
webmaster@0 143 * Form located on the dry run page and allowing users
webmaster@0 144 * to start the archiving process.
webmaster@0 145 */
webmaster@0 146 function filearchiver_archive_form() {
webmaster@0 147 $form = array();
webmaster@0 148 $form[] = array(
webmaster@0 149 '#type' => 'submit',
webmaster@0 150 '#title' => t('Archive now'),
webmaster@0 151 '#value' => t('Archive now')
webmaster@0 152 );
webmaster@0 153 return $form;
webmaster@0 154 }
webmaster@0 155
webmaster@0 156 /**
webmaster@0 157 * Callback of the "Archive now" form that actually starts the process.
webmaster@0 158 */
webmaster@0 159 function filearchiver_archive_form_submit() {
webmaster@0 160 _filearchiver_archive(FALSE);
webmaster@0 161 }
webmaster@0 162
webmaster@0 163 /**
webmaster@0 164 * Implementation of hook_cron().
webmaster@0 165 */
webmaster@0 166 function filearchiver_cron() {
webmaster@0 167 if (variable_get('filearchiver_run_on_cron', FALSE)) {
webmaster@0 168 _filearchiver_archive(FALSE);
webmaster@0 169 }
webmaster@0 170 }
webmaster@0 171
webmaster@0 172 /**
webmaster@0 173 * Do the real archiving work.
webmaster@0 174 */
webmaster@0 175 function _filearchiver_archive($dry_run = TRUE) {
webmaster@0 176 $rows = array();
webmaster@0 177 // First of all, get the list of our sources
webmaster@0 178 $sources = variable_get('filearchiver_sources', array());
webmaster@0 179 foreach ($sources as $vid => $active) {
webmaster@0 180 if ($active) {
webmaster@0 181 /* If the view is checked as a provider for the file archiver,
webmaster@0 182 then we're going to ask view to get all the nodes that match,
webmaster@0 183 in a convenient items array. */
webmaster@0 184 $view = views_get_view($vid);
webmaster@0 185 $result = views_build_view('items', $view);
webmaster@0 186 foreach ($result['items'] as $item) {
webmaster@0 187 // Then for each of those nodes, we'll load it
webmaster@0 188 $node = node_load($item->nid);
webmaster@0 189 // And deal with the files directly attached to the node
webmaster@0 190 foreach ($node->files as $file) {
webmaster@0 191 if ($dry_run) {
webmaster@0 192 $rows[] = _filearchiver_get_infos($node, $file, FILEARCHIVER_SOURCE_NODE);
webmaster@0 193 }
webmaster@0 194 else {
webmaster@0 195 _filearchiver_archive_node_file($node, $file);
webmaster@0 196 }
webmaster@0 197 }
webmaster@0 198 /* Then we need to take care of all the files that might be
webmaster@0 199 attached to the comments if the Comment Upload module is
webmaster@0 200 active */
webmaster@0 201 if (db_table_exists('{comment_upload_files}') &&
webmaster@0 202 function_exists('_comment_upload_load_files')) {
webmaster@0 203 $result = db_query("SELECT DISTINCT(cid) FROM {comment_upload_files} WHERE nid=%d",
webmaster@0 204 $node->nid);
webmaster@0 205 while ($row = db_fetch_object($result)) {
webmaster@0 206 $files = _comment_upload_load_files($row->cid, $item->nid);
webmaster@0 207 foreach ($files as $file) {
webmaster@0 208 if ($dry_run) {
webmaster@0 209 $rows[] = _filearchiver_get_infos($node, $file, FILEARCHIVER_SOURCE_COMMENT);
webmaster@0 210 }
webmaster@0 211 else {
webmaster@0 212 _filearchiver_archive_comment_file($node, $row->cid, $file);
webmaster@0 213 }
webmaster@0 214 }
webmaster@0 215 }
webmaster@0 216 }
webmaster@0 217 }
webmaster@0 218 }
webmaster@0 219 }
webmaster@0 220 return $rows;
webmaster@0 221 }
webmaster@0 222
webmaster@0 223 /**
webmaster@0 224 * Callback for the settings form.
webmaster@0 225 */
webmaster@0 226 function filearchiver_settings_form() {
webmaster@0 227 $form = array();
webmaster@0 228 // Choose if the archiving should be done on cron runs
webmaster@0 229 $form['filearchiver_run_on_cron'] = array(
webmaster@0 230 '#type' => 'checkbox',
webmaster@0 231 '#title' => t('Run on cron'),
webmaster@0 232 '#description' => t('If checked, files will be automatically
webmaster@0 233 archived on cron when any of the views selected
webmaster@0 234 below match the node to which it is attached'),
webmaster@0 235 '#default_value' => variable_get('filearchiver_run_on_cron', FALSE)
webmaster@0 236 );
webmaster@0 237 // Build the list of all available views
webmaster@0 238 $form['filearchiver_sources'] = array(
webmaster@0 239 '#type' => 'checkboxes',
webmaster@0 240 '#title' => t('Sources'),
webmaster@0 241 '#description' => t('Please select the views corresponding to the nodes
webmaster@0 242 whose files you want to archive'),
webmaster@0 243 '#default_value' => variable_get('filearchiver_sources', array())
webmaster@0 244 );
webmaster@0 245 $views = array();
webmaster@0 246 $result = db_query("SELECT vid, name, description FROM {view_view}");
webmaster@0 247 while ($view = db_fetch_object($result)) {
webmaster@0 248 $views[$view->vid] = theme('filearchiver_show_view', $view);
webmaster@0 249 }
webmaster@0 250 $form['filearchiver_sources']['#options'] = $views;
webmaster@0 251 // Let the user choose where the files will be archived
webmaster@0 252 $form['filearchiver_path'] = array(
webmaster@0 253 '#title' => t('Pattern for the archive path'),
webmaster@0 254 '#type' => 'textfield',
webmaster@0 255 '#description' => t('Specify the pattern to prefix to file names that will be
webmaster@0 256 archived. It will be appended after the site files directory (!directory) but
webmaster@0 257 before the file name itself. Do not include a leading or trailing slash.
webmaster@0 258 Spaces will be converted to underscores to avoid file system issues.',
webmaster@0 259 array('!directory' => file_directory_path())),
webmaster@0 260 '#default_value' => variable_get('filearchiver_path', 'archives/[yyyy]/[mm]')
webmaster@0 261 );
webmaster@0 262 // Build the token help
webmaster@0 263 $form['token_help'] = array(
webmaster@0 264 '#title' => t('Replacement patterns'),
webmaster@0 265 '#type' => 'fieldset',
webmaster@0 266 '#collapsible' => TRUE,
webmaster@0 267 '#collapsed' => TRUE,
webmaster@0 268 '#description' => t('Prefer raw-text replacements for text to avoid problems with HTML entities!'),
webmaster@0 269 );
webmaster@0 270 $form['token_help']['help'] = array(
webmaster@0 271 '#value' => theme('token_help', 'node'),
webmaster@0 272 );
webmaster@0 273
webmaster@0 274 return system_settings_form($form);
webmaster@0 275 }
webmaster@0 276
webmaster@0 277 /**
webmaster@0 278 * Callback for the settings form submission.
webmaster@0 279 */
webmaster@0 280 function filearchiver_settings_form_submit($form_id, &$form_values) {
webmaster@0 281 variable_set('filearchiver_sources', $form_values['filearchiver_sources']);
webmaster@0 282 variable_set('filearchiver_path', $form_values['filearchiver_path']);
webmaster@0 283 }
webmaster@0 284
webmaster@0 285 /**
webmaster@0 286 * Theme the view name and it's description for display in the view list.
webmaster@0 287 */
webmaster@0 288 function theme_filearchiver_show_view($view) {
webmaster@0 289 return "<strong>{$view->name}</strong>, {$view->description}";
webmaster@0 290 }
webmaster@0 291
webmaster@0 292 /**
webmaster@0 293 * Check if the file has already been archived.
webmaster@0 294 */
webmaster@0 295 function _filearchiver_file_is_archived($fid, $source = FILEARCHIVER_SOURCE_NODE) {
webmaster@0 296 return db_result(db_query("
webmaster@0 297 SELECT COUNT(fid)
webmaster@0 298 FROM {filearchiver_files}
webmaster@0 299 WHERE
webmaster@0 300 fid=%d AND source=%d", $fid, $source));
webmaster@0 301 }
webmaster@0 302
webmaster@0 303 /**
webmaster@0 304 * Get the archive name from the filename
webmaster@0 305 */
webmaster@0 306 function _filearchiver_get_filepath($node, $filepath) {
webmaster@0 307 $basename = basename($filepath);
webmaster@0 308 //$dirname = dirname($filepath);
webmaster@0 309 $timestamp = max($node->last_comment_timestamp, $node->revision_date);
webmaster@0 310 // First get the template from the configuration screen
webmaster@0 311 $tpl = variable_get('filearchiver_path', 'archives/[yyyy]/[mm]');
webmaster@0 312 // Then replace the tokens in it
webmaster@0 313 $archive = '/'. token_replace($tpl, 'node', $node);
webmaster@0 314 // Finally get rid of the whitespaces that may not work all that well on filenames
webmaster@0 315 $archive = str_replace(array(' ', "\n", "\t"), '_', $archive);
webmaster@0 316 $path = _filearchiver_ensure_path_exists($archive);
webmaster@0 317 return $path .'/'. $basename;
webmaster@0 318 }
webmaster@0 319
webmaster@0 320 /**
webmaster@0 321 * Ensure that a specific path actually exists
webmaster@0 322 * @param A path relative to the file_directory_path.
webmaster@0 323 * This path will be sanitized.
webmaster@0 324 * @return Full path for the directory
webmaster@0 325 */
webmaster@0 326 function _filearchiver_ensure_path_exists($archive) {
webmaster@0 327 $dirs = explode('/', $archive);
webmaster@0 328 $directory = file_directory_path();
webmaster@0 329 while (count($dirs)) {
webmaster@0 330 $current = array_shift($dirs);
webmaster@0 331 /* Ban '.' and '..': those values might be contained in the input
webmaster@0 332 * provided by the user and transfered in the archive path by the
webmaster@0 333 * token module
webmaster@0 334 */
webmaster@0 335 if (!in_array($current, array('.', '..'))) {
webmaster@0 336 $directory .= '/'. $current;
webmaster@0 337 file_check_directory($directory, FILE_CREATE_DIRECTORY);
webmaster@0 338 }
webmaster@0 339 }
webmaster@0 340 return $directory;
webmaster@0 341 }
webmaster@0 342
webmaster@0 343 /**
webmaster@0 344 * Archive a file attached directly to the node.
webmaster@0 345 */
webmaster@0 346 function _filearchiver_archive_node_file($node, $file) {
webmaster@0 347 $archived = _filearchiver_file_is_archived($file->fid, FILEARCHIVER_SOURCE_NODE);
webmaster@0 348 if (!$archived) {
webmaster@0 349 // If this file isn't archived yet, we'll deal with it
webmaster@0 350 $target = _filearchiver_get_filepath($node, $file->filepath);
webmaster@0 351 // Move the file over there
webmaster@0 352 file_move($file, $target, FILE_EXISTS_RENAME);
webmaster@0 353 // Update the table
webmaster@0 354 db_query("
webmaster@0 355 UPDATE {files}
webmaster@0 356 SET filepath='%s'
webmaster@0 357 WHERE nid=%d AND fid=%d",
webmaster@0 358 $file->filepath,
webmaster@0 359 $node->nid,
webmaster@0 360 $file->fid);
webmaster@0 361 // Insert into our table to track that we've archived this file
webmaster@0 362 db_query("INSERT INTO {filearchiver_files}(fid, source, archive) VALUES(%d, %d, '%s')",
webmaster@0 363 $file->fid,
webmaster@0 364 FILEARCHIVER_SOURCE_NODE,
webmaster@0 365 $target);
webmaster@0 366 }
webmaster@0 367 }
webmaster@0 368
webmaster@0 369 /**
webmaster@0 370 * Archive a file attached to a comment of the node.
webmaster@0 371 * This happen if the module 'Comment Upload' is in
webmaster@0 372 * use.
webmaster@0 373 */
webmaster@0 374 function _filearchiver_archive_comment_file($node, $cid, $file) {
webmaster@0 375 $archived = _filearchiver_file_is_archived($file->fid, FILEARCHIVER_SOURCE_COMMENT);
webmaster@0 376 if (!$archived) {
webmaster@0 377 // Get the destination path
webmaster@0 378 $target = _filearchiver_get_filepath($node, $file->filepath);
webmaster@0 379 // Move the file over there
webmaster@0 380 file_move($file, $target, FILE_EXISTS_RENAME);
webmaster@0 381 // Update the table
webmaster@0 382 db_query("
webmaster@0 383 UPDATE {comment_upload_files}
webmaster@0 384 SET filepath='%s'
webmaster@0 385 WHERE nid=%d AND cid=%d AND fid=%d",
webmaster@0 386 $file->filepath,
webmaster@0 387 $node->nid,
webmaster@0 388 $cid,
webmaster@0 389 $file->fid);
webmaster@0 390 // Insert into our table to track that we've archived this file
webmaster@0 391 db_query("INSERT INTO {filearchiver_files}(fid, source, archive) VALUES(%d, %d, '%s')",
webmaster@0 392 $file->fid,
webmaster@0 393 FILEARCHIVER_SOURCE_COMMENT,
webmaster@0 394 $target);
webmaster@0 395 }
webmaster@0 396 }
webmaster@0 397
webmaster@0 398 /**
webmaster@0 399 * Extract the information that will be presented about the file
webmaster@0 400 * on a dry-run.
webmaster@0 401 */
webmaster@0 402 function _filearchiver_get_infos($node, $file, $source) {
webmaster@0 403 $archived = _filearchiver_file_is_archived($file->fid, $source);
webmaster@0 404 $from = ($source == FILEARCHIVER_SOURCE_NODE) ? t('node') : t('comment');
webmaster@0 405 $status = ($archived) ? t('archived') : t('live');
webmaster@0 406 $target = _filearchiver_get_filepath($node, $file->filepath);
webmaster@0 407 return array(l($node->nid, 'node/'. $node->nid), $file->filename, $from, $status, $target);
webmaster@0 408 }