webmaster@0: 'admin/settings/filearchiver', webmaster@0: 'title' => t('File archiver'), webmaster@0: 'access' => user_access('administer filearchiver'), webmaster@0: 'callback' => 'drupal_get_form', webmaster@0: 'callback arguments' => array('filearchiver_settings_form') webmaster@0: ); webmaster@0: // The "File has been archived" page webmaster@0: $items[] = array( webmaster@0: 'path' => 'filearchiver/archived', webmaster@0: 'title' => t('File archived'), webmaster@0: 'access' => user_access('access content'), webmaster@0: 'callback' => 'filearchiver_archived_page' webmaster@0: ); webmaster@0: // The status page webmaster@0: $items[] = array( webmaster@0: 'path' => 'filearchiver', webmaster@0: 'title' => 'File Archiver', webmaster@0: 'callback' => 'filearchiver_dry_run', webmaster@0: 'access' => user_access('administer filearchiver') webmaster@0: ); webmaster@0: // Overwrite system default file callback webmaster@0: // This is needed to be able to act on non existing files, webmaster@0: // even though it's really suboptimal. webmaster@0: $items[] = array( webmaster@0: 'path' => 'system/files', webmaster@0: 'title' => t('File download'), webmaster@0: 'callback' => 'filearchiver_download', webmaster@0: 'access' => TRUE, webmaster@0: 'type' => MENU_CALLBACK webmaster@0: ); webmaster@0: } webmaster@0: return $items; webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Implementation of hook_perm(). webmaster@0: */ webmaster@0: function filearchiver_perm() { webmaster@0: return array('administer filearchiver', 'restore archives'); webmaster@0: } webmaster@0: webmaster@0: webmaster@0: /** webmaster@0: * This function filter the call to non-exising archived files. webmaster@0: * For existing files, it's a downright copy & paste of file_download webmaster@0: * in file.inc, which should be faster than calling it. webmaster@0: */ webmaster@0: function filearchiver_download() { webmaster@0: // Merge remainder of arguments from GET['q'], into relative file path. webmaster@0: $args = func_get_args(); webmaster@0: $filepath = implode('/', $args); webmaster@0: webmaster@0: // Maintain compatibility with old ?file=paths saved in node bodies. webmaster@0: if (isset($_GET['file'])) { webmaster@0: $filepath = $_GET['file']; webmaster@0: } webmaster@0: webmaster@0: if (file_exists(file_create_path($filepath))) { webmaster@0: $headers = module_invoke_all('file_download', $filepath); webmaster@0: if (in_array(-1, $headers)) { webmaster@0: return drupal_access_denied(); webmaster@0: } webmaster@0: if (count($headers)) { webmaster@0: file_transfer($filepath, $headers); webmaster@0: } webmaster@0: } webmaster@0: else { webmaster@0: // Check if the file has been archived webmaster@0: $count = db_result(db_query(" webmaster@0: SELECT COUNT(fid) webmaster@0: FROM {filearchiver_files} webmaster@0: WHERE archive='%s'", file_directory_path() .'/'. $filepath)); webmaster@0: if ($count) { webmaster@0: return filearchiver_archived_page(); webmaster@0: } webmaster@0: } webmaster@0: return drupal_not_found(); webmaster@0: } webmaster@0: /** webmaster@0: * The "File has been archived page". webmaster@0: * This is the landing page in case of a file archived webmaster@0: * and no longer in the filesystem. webmaster@0: */ webmaster@0: function filearchiver_archived_page() { webmaster@0: drupal_set_title('File archived'); webmaster@0: return "

Sorry, this file has been archived and can't be seen webmaster@0: online at the moment. If you need to access it, please contact the webmaster@0: support who will be able to put it back online.

"; webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Dry-run callback implementation. webmaster@0: * This should be quite usefull to test that everything's alright. webmaster@0: */ webmaster@0: function filearchiver_dry_run() { webmaster@0: $rows = _filearchiver_archive(TRUE); webmaster@0: $output = theme('table', array('nid', 'filename', 'source', 'status', 'target'), $rows); webmaster@0: $output .= drupal_get_form('filearchiver_archive_form'); webmaster@0: return $output; webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Form located on the dry run page and allowing users webmaster@0: * to start the archiving process. webmaster@0: */ webmaster@0: function filearchiver_archive_form() { webmaster@0: $form = array(); webmaster@0: $form[] = array( webmaster@0: '#type' => 'submit', webmaster@0: '#title' => t('Archive now'), webmaster@0: '#value' => t('Archive now') webmaster@0: ); webmaster@0: return $form; webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Callback of the "Archive now" form that actually starts the process. webmaster@0: */ webmaster@0: function filearchiver_archive_form_submit() { webmaster@0: _filearchiver_archive(FALSE); webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Implementation of hook_cron(). webmaster@0: */ webmaster@0: function filearchiver_cron() { webmaster@0: if (variable_get('filearchiver_run_on_cron', FALSE)) { webmaster@0: _filearchiver_archive(FALSE); webmaster@0: } webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Do the real archiving work. webmaster@0: */ webmaster@0: function _filearchiver_archive($dry_run = TRUE) { webmaster@0: $rows = array(); webmaster@0: // First of all, get the list of our sources webmaster@0: $sources = variable_get('filearchiver_sources', array()); webmaster@0: foreach ($sources as $vid => $active) { webmaster@0: if ($active) { webmaster@0: /* If the view is checked as a provider for the file archiver, webmaster@0: then we're going to ask view to get all the nodes that match, webmaster@0: in a convenient items array. */ webmaster@0: $view = views_get_view($vid); webmaster@0: $result = views_build_view('items', $view); webmaster@0: foreach ($result['items'] as $item) { webmaster@0: // Then for each of those nodes, we'll load it webmaster@0: $node = node_load($item->nid); webmaster@0: // And deal with the files directly attached to the node webmaster@0: foreach ($node->files as $file) { webmaster@0: if ($dry_run) { webmaster@0: $rows[] = _filearchiver_get_infos($node, $file, FILEARCHIVER_SOURCE_NODE); webmaster@0: } webmaster@0: else { webmaster@0: _filearchiver_archive_node_file($node, $file); webmaster@0: } webmaster@0: } webmaster@0: /* Then we need to take care of all the files that might be webmaster@0: attached to the comments if the Comment Upload module is webmaster@0: active */ webmaster@0: if (db_table_exists('{comment_upload_files}') && webmaster@0: function_exists('_comment_upload_load_files')) { webmaster@0: $result = db_query("SELECT DISTINCT(cid) FROM {comment_upload_files} WHERE nid=%d", webmaster@0: $node->nid); webmaster@0: while ($row = db_fetch_object($result)) { webmaster@0: $files = _comment_upload_load_files($row->cid, $item->nid); webmaster@0: foreach ($files as $file) { webmaster@0: if ($dry_run) { webmaster@0: $rows[] = _filearchiver_get_infos($node, $file, FILEARCHIVER_SOURCE_COMMENT); webmaster@0: } webmaster@0: else { webmaster@0: _filearchiver_archive_comment_file($node, $row->cid, $file); webmaster@0: } webmaster@0: } webmaster@0: } webmaster@0: } webmaster@0: } webmaster@0: } webmaster@0: } webmaster@0: return $rows; webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Callback for the settings form. webmaster@0: */ webmaster@0: function filearchiver_settings_form() { webmaster@0: $form = array(); webmaster@0: // Choose if the archiving should be done on cron runs webmaster@0: $form['filearchiver_run_on_cron'] = array( webmaster@0: '#type' => 'checkbox', webmaster@0: '#title' => t('Run on cron'), webmaster@0: '#description' => t('If checked, files will be automatically webmaster@0: archived on cron when any of the views selected webmaster@0: below match the node to which it is attached'), webmaster@0: '#default_value' => variable_get('filearchiver_run_on_cron', FALSE) webmaster@0: ); webmaster@0: // Build the list of all available views webmaster@0: $form['filearchiver_sources'] = array( webmaster@0: '#type' => 'checkboxes', webmaster@0: '#title' => t('Sources'), webmaster@0: '#description' => t('Please select the views corresponding to the nodes webmaster@0: whose files you want to archive'), webmaster@0: '#default_value' => variable_get('filearchiver_sources', array()) webmaster@0: ); webmaster@0: $views = array(); webmaster@0: $result = db_query("SELECT vid, name, description FROM {view_view}"); webmaster@0: while ($view = db_fetch_object($result)) { webmaster@0: $views[$view->vid] = theme('filearchiver_show_view', $view); webmaster@0: } webmaster@0: $form['filearchiver_sources']['#options'] = $views; webmaster@0: // Let the user choose where the files will be archived webmaster@0: $form['filearchiver_path'] = array( webmaster@0: '#title' => t('Pattern for the archive path'), webmaster@0: '#type' => 'textfield', webmaster@0: '#description' => t('Specify the pattern to prefix to file names that will be webmaster@0: archived. It will be appended after the site files directory (!directory) but webmaster@0: before the file name itself. Do not include a leading or trailing slash. webmaster@0: Spaces will be converted to underscores to avoid file system issues.', webmaster@0: array('!directory' => file_directory_path())), webmaster@0: '#default_value' => variable_get('filearchiver_path', 'archives/[yyyy]/[mm]') webmaster@0: ); webmaster@0: // Build the token help webmaster@0: $form['token_help'] = array( webmaster@0: '#title' => t('Replacement patterns'), webmaster@0: '#type' => 'fieldset', webmaster@0: '#collapsible' => TRUE, webmaster@0: '#collapsed' => TRUE, webmaster@0: '#description' => t('Prefer raw-text replacements for text to avoid problems with HTML entities!'), webmaster@0: ); webmaster@0: $form['token_help']['help'] = array( webmaster@0: '#value' => theme('token_help', 'node'), webmaster@0: ); webmaster@0: webmaster@0: return system_settings_form($form); webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Callback for the settings form submission. webmaster@0: */ webmaster@0: function filearchiver_settings_form_submit($form_id, &$form_values) { webmaster@0: variable_set('filearchiver_sources', $form_values['filearchiver_sources']); webmaster@0: variable_set('filearchiver_path', $form_values['filearchiver_path']); webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Theme the view name and it's description for display in the view list. webmaster@0: */ webmaster@0: function theme_filearchiver_show_view($view) { webmaster@0: return "{$view->name}, {$view->description}"; webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Check if the file has already been archived. webmaster@0: */ webmaster@0: function _filearchiver_file_is_archived($fid, $source = FILEARCHIVER_SOURCE_NODE) { webmaster@0: return db_result(db_query(" webmaster@0: SELECT COUNT(fid) webmaster@0: FROM {filearchiver_files} webmaster@0: WHERE webmaster@0: fid=%d AND source=%d", $fid, $source)); webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Get the archive name from the filename webmaster@0: */ webmaster@0: function _filearchiver_get_filepath($node, $filepath) { webmaster@0: $basename = basename($filepath); webmaster@0: //$dirname = dirname($filepath); webmaster@0: $timestamp = max($node->last_comment_timestamp, $node->revision_date); webmaster@0: // First get the template from the configuration screen webmaster@0: $tpl = variable_get('filearchiver_path', 'archives/[yyyy]/[mm]'); webmaster@0: // Then replace the tokens in it webmaster@0: $archive = '/'. token_replace($tpl, 'node', $node); webmaster@0: // Finally get rid of the whitespaces that may not work all that well on filenames webmaster@0: $archive = str_replace(array(' ', "\n", "\t"), '_', $archive); webmaster@0: $path = _filearchiver_ensure_path_exists($archive); webmaster@0: return $path .'/'. $basename; webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Ensure that a specific path actually exists webmaster@0: * @param A path relative to the file_directory_path. webmaster@0: * This path will be sanitized. webmaster@0: * @return Full path for the directory webmaster@0: */ webmaster@0: function _filearchiver_ensure_path_exists($archive) { webmaster@0: $dirs = explode('/', $archive); webmaster@0: $directory = file_directory_path(); webmaster@0: while (count($dirs)) { webmaster@0: $current = array_shift($dirs); webmaster@0: /* Ban '.' and '..': those values might be contained in the input webmaster@0: * provided by the user and transfered in the archive path by the webmaster@0: * token module webmaster@0: */ webmaster@0: if (!in_array($current, array('.', '..'))) { webmaster@0: $directory .= '/'. $current; webmaster@0: file_check_directory($directory, FILE_CREATE_DIRECTORY); webmaster@0: } webmaster@0: } webmaster@0: return $directory; webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Archive a file attached directly to the node. webmaster@0: */ webmaster@0: function _filearchiver_archive_node_file($node, $file) { webmaster@0: $archived = _filearchiver_file_is_archived($file->fid, FILEARCHIVER_SOURCE_NODE); webmaster@0: if (!$archived) { webmaster@0: // If this file isn't archived yet, we'll deal with it webmaster@0: $target = _filearchiver_get_filepath($node, $file->filepath); webmaster@0: // Move the file over there webmaster@0: file_move($file, $target, FILE_EXISTS_RENAME); webmaster@0: // Update the table webmaster@0: db_query(" webmaster@0: UPDATE {files} webmaster@0: SET filepath='%s' webmaster@0: WHERE nid=%d AND fid=%d", webmaster@0: $file->filepath, webmaster@0: $node->nid, webmaster@0: $file->fid); webmaster@0: // Insert into our table to track that we've archived this file webmaster@0: db_query("INSERT INTO {filearchiver_files}(fid, source, archive) VALUES(%d, %d, '%s')", webmaster@0: $file->fid, webmaster@0: FILEARCHIVER_SOURCE_NODE, webmaster@0: $target); webmaster@0: } webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Archive a file attached to a comment of the node. webmaster@0: * This happen if the module 'Comment Upload' is in webmaster@0: * use. webmaster@0: */ webmaster@0: function _filearchiver_archive_comment_file($node, $cid, $file) { webmaster@0: $archived = _filearchiver_file_is_archived($file->fid, FILEARCHIVER_SOURCE_COMMENT); webmaster@0: if (!$archived) { webmaster@0: // Get the destination path webmaster@0: $target = _filearchiver_get_filepath($node, $file->filepath); webmaster@0: // Move the file over there webmaster@0: file_move($file, $target, FILE_EXISTS_RENAME); webmaster@0: // Update the table webmaster@0: db_query(" webmaster@0: UPDATE {comment_upload_files} webmaster@0: SET filepath='%s' webmaster@0: WHERE nid=%d AND cid=%d AND fid=%d", webmaster@0: $file->filepath, webmaster@0: $node->nid, webmaster@0: $cid, webmaster@0: $file->fid); webmaster@0: // Insert into our table to track that we've archived this file webmaster@0: db_query("INSERT INTO {filearchiver_files}(fid, source, archive) VALUES(%d, %d, '%s')", webmaster@0: $file->fid, webmaster@0: FILEARCHIVER_SOURCE_COMMENT, webmaster@0: $target); webmaster@0: } webmaster@0: } webmaster@0: webmaster@0: /** webmaster@0: * Extract the information that will be presented about the file webmaster@0: * on a dry-run. webmaster@0: */ webmaster@0: function _filearchiver_get_infos($node, $file, $source) { webmaster@0: $archived = _filearchiver_file_is_archived($file->fid, $source); webmaster@0: $from = ($source == FILEARCHIVER_SOURCE_NODE) ? t('node') : t('comment'); webmaster@0: $status = ($archived) ? t('archived') : t('live'); webmaster@0: $target = _filearchiver_get_filepath($node, $file->filepath); webmaster@0: return array(l($node->nid, 'node/'. $node->nid), $file->filename, $from, $status, $target); webmaster@0: }