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