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 } |