/[drupal]/contributions/modules/filemanager/filemanager.module
ViewVC logotype

Contents of /contributions/modules/filemanager/filemanager.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.21 - (show annotations) (download) (as text)
Thu Jul 5 04:44:20 2007 UTC (2 years, 4 months ago) by drewish
Branch: MAIN
CVS Tags: HEAD
Branch point for: DRUPAL-5
Changes since 1.20: +18 -12 lines
File MIME type: text/x-php
jrt and alinraz's patch for #99016 "Update for Drupal 5.0"
1 <?php
2 // $Id: filemanager.module,v 1.20 2006/11/19 02:36:51 drewish Exp $
3
4 /**
5 * @defgroup filemanager File Upload/Download Manager
6 * Functions for modules to use the managed file repository. Modules using
7 * this repository don't have to deal with filenames, directory, size limits,
8 * or creating download URLs.
9 *
10 * File Manager supports both private(access controlled) and public files.
11 * When a module adds a file to the file manager they specify which type of
12 * file it is. A module may use both public and private files at the same
13 * time, but an individual may only be public or private.
14 *
15 * @{
16 */
17
18 /**
19 * Create the URL to download a file in the filestore.
20 *
21 * @param $file
22 * Filestore file to create the URL for
23 * @param $working
24 * If this is true and there is a working copy of the file then a URL to the
25 * working copy will be returned, otherwise the URL will point to the active
26 * copy.
27 * @param $absolute
28 * Whether to force the output to be an absolute URL (beginning with http:).
29 * Useful for URLs that will be displayed outside the site, such as in an RSS
30 * feed.
31 * @return
32 * a string containing the URL to the given path.
33 */
34 function filemanager_url($file, $working = FALSE, $absolute = FALSE) {
35 global $base_url;
36
37 // Load file info if we only got a $fid
38 $file = filemanager_get_file_info($file);
39
40 $subdir = ($file->working && $working) ? 'working' : 'active';
41
42 if ($file->private) {
43 return url('filemanager/' . $subdir, 'fid=' . $file->fid, NULL, $absolute);
44 }
45 else {
46 return variable_get('filemanager_public_url', 'files') . "/$subdir/{$file->directory}/{$file->filename}";
47 }
48 }
49
50 /**
51 * Create a link to download a file in the filestore.
52 *
53 * @param $text
54 * The text to be enclosed with the anchor tag.
55 * @param $file
56 * Filestore file to create the URL for
57 * @param $working
58 * If this is true and there is a working copy of the file then a link
59 * to the working copy will be returned, otherwise the link will point to
60 * the active copy.
61 * @param $attributes
62 * An associative array of HTML attributes to apply to the anchor tag.
63 * @param $absolute
64 * Whether to force the output to be an absolute link (beginning with http:).
65 * Useful for links that will be displayed outside the site, such as in an RSS feed.
66 * @return
67 * an HTML string containing a link to the given path.
68 */
69 function filemanager_l($text, $file, $working = FALSE, $attributes = array(), $absolute = FALSE) {
70 return '<a href="' . filemanager_url($file, $working, $absolute) . '"' . drupal_attributes($attributes) . '>' . $text . '</a>';
71 }
72
73 /**
74 * Returns the path to a file in the filestore
75 *
76 * @param $file
77 * Filestore file or id to create the path for
78 * @param $working
79 * If this is true return the path to where the working copy
80 * would reside. This does not guarantee a working copy exists.
81 */
82 function filemanager_create_path($file, $working = FALSE) {
83 $file = filemanager_get_file_info($file);
84 return filemanager_create_directory_path($file->private, $working, $file->directory) . '/' . $file->filename;
85 }
86
87 /**
88 * Returns the path to a the directory where this file is located.
89 *
90 * @param $private
91 * If true creates a path to a private directory
92 * @param $working
93 * If this is true return the path to where the working copy
94 * would reside. This does not guarantee a working copy exists.
95 * @param $subdir
96 * Subdirectory underneath the root which you want
97 */
98 function filemanager_create_directory_path($private = FALSE, $working = FALSE, $subdir = FALSE) {
99 return ($private ? variable_get('filemanager_private_path', 'private') : variable_get('filemanager_public_path', 'files')) . '/' . ($working ? 'working' : 'active') . ($subdir !== FALSE ? '/' . $subdir : '');
100 }
101
102 /**
103 * Saves a file uploaded into a the file store as a working copy.
104 *
105 * @param $source
106 * Name of the fileupload field to check
107 * @param $area
108 * Name of the area where the file resides which will usually be the name of the module
109 * managing the file. Ignored if over if you supply a file to overwrite
110 * @param $private
111 * True if you want this file to be a private download. This is ignored if you
112 * supply a file to overwrite.
113 * @param $file
114 * Filestore file which this upload should replace
115 * @return File object containing file information or 0 if there
116 * was an error during save.
117 */
118 function filemanager_add_upload($source, $area, $private = FALSE, $file = FALSE) {
119 $upload = file_check_upload($source);
120 return filemanager_add_file($area, $upload->filepath, $upload->filename, $upload->filemime, FALSE, $private, $file);
121 }
122
123 /**
124 * Adds a file into the repository as a working copy
125 *
126 * @param $area
127 * Name of the area where the file resides which will usually be the name of the module
128 * managing the file. Ignored if over if you supply a file to overwrite
129 * @param $path
130 * Path to the file to be added to the
131 * @param $filename
132 * @param $mimetype
133 * Mime type that should be used for the HTTP header when downloading this file.
134 * Ignored if over if you supply a file to overwrite
135 * @param $remove
136 * If true the original file will be removed once it is put into the repository.
137 * @param $private
138 * True if you want this file to be a private download. This is ignored if you
139 * supply a file to overwrite.
140 * @param $file
141 * Filestore file which this upload should replace
142 * @return
143 * Fileobject on success, otherwise FALSE
144 */
145 function filemanager_add_file($area, $path, $filename, $mimetype = 'application/unknown', $remove = TRUE, $private = FALSE, $file = FALSE) {
146 if(variable_get('filemanager_force_private_' . $area, 0)) {
147 $private = TRUE;
148 }
149
150 $size = filesize($path);
151
152 // Lock on a lock file to prevent naming conflict race conditions
153 $lock = _filemanager_lock();
154
155 if ($file === FALSE) {
156
157 if ($area == '') {
158 $area = 'general';
159 }
160
161 // This is a net new file find a directory for it and setup the object
162 $file->fid = db_next_id('{file}_fid');
163 $file->area = $area;
164 $file->filename = $filename;
165 $file->active = FALSE;
166 $file->working = FALSE;
167 $file->private = $private;
168 $file->size = $size;
169 $file = _filemanager_find_directory($file);
170 $update = false;
171 }
172 else {
173
174 // We're replacing a file so delete it's existing working version
175 $file = filemanager_get_file_info($file);
176 if ($file->working) {
177 file_delete(filemanager_create_path($file, TRUE));
178 }
179 $file->working = FALSE;
180 $update = true;
181 // We don't update the size for existing files until they are promoted
182 }
183
184 // Purge out any old working files, we do this every time since
185 // not all sites have cron support and we don't one upload to have
186 // to delete several weeks worth of build up
187 filemanager_purge_orphans();
188
189
190 // Verify we are not going to exceed our working directory size
191 $sizelimit = variable_get('filemanager_working_sizelimit', '10') * 1024 * 1024;
192 if ($sizelimit > -1 && $size + filemanager_get_working_size() > $sizelimit) {
193 drupal_set_message(t('Filestore add failed: Working space limit has been reached.'), 'error');
194 _filemanager_unlock($lock);
195 return FALSE;
196 }
197
198 // Verify working directory exists
199 filemanager_create_directory(filemanager_create_directory_path($file->private, TRUE));
200 filemanager_create_directory(filemanager_create_directory_path($file->private, TRUE, $file->directory));
201
202 // Move upload file to new location and name
203 $orig_path = $path;
204 if (_filemanager_copy($path, filemanager_create_path($file, TRUE), FILE_EXISTS_ERROR)) {
205
206 $file->working = TRUE;
207 $file->mimetype = $mimetype;
208
209 // Save database record
210 if ($update) {
211 db_query("UPDATE {file} SET working='%s' WHERE fid=%d", $file->working, $file->fid);
212 }
213 else {
214 db_query("INSERT INTO {file} (fid, area, directory, filename, mimetype, size, active, working, private) VALUES (%d,'%s',%d,'%s','%s',%d,'%s','%s','%s')",
215 $file->fid, $file->area, $file->directory, $file->filename, $file->mimetype, $file->size, $file->active, $file->working, $file->private);
216 }
217
218 if ($remove) {
219 file_delete($orig_path);
220 }
221 } else {
222 $file = FALSE;
223 }
224
225 _filemanager_unlock($lock);
226 return $file;
227 }
228
229 /**
230 * Renames an existing file in the filestore
231 *
232 * @param $file
233 * file or fid to rename
234 * @param $name
235 * new name for the file, use NULL or blank to retain old name
236 * @return
237 * the renamed file object on success or false on failure
238 */
239 function filemanager_rename($file, $name) {
240 $file = filemanager_get_file_info($file);
241
242 // Exit immediately if the rename does nothing
243 if (! $file || $name == $file->filename) {
244 return $file;
245 }
246
247 // Begin rename operation
248 $oldworking = filemanager_create_path($file, true);
249 $oldactive = filemanager_create_path($file, false);
250 $lock = _filemanager_lock();
251
252 $file->filename = $name;
253
254 $updated = _filemanager_update_file($file, $oldworking, $oldactive);
255
256 if ($file != false) {
257 db_query("UPDATE {file} SET filename = '%s', directory = '%d' WHERE fid=%d", $file->filename, $file->directory, $file->fid);
258 }
259 _filemanager_unlock($lock);
260 return $file;
261 }
262
263 /**
264 * Moves the file from public to private or vice versa
265 *
266 * @param $file
267 * file or fid to modify
268 * @param $private
269 * new private state for the file
270 * @return
271 * the modified file object on success or false on failure.
272 */
273 function filemanager_set_private($file, $private) {
274 $file = filemanager_get_file_info($file);
275
276 // The private column is a char, adjust the flag to match
277 if ($private) {
278 $private = '1';
279 }
280 else {
281 $private = '0';
282 }
283
284 // Exit immediately if file is already in the right state
285 if (! $file || $file->private == $private) {
286 return $file;
287 }
288
289 // Begin set_private operation
290 $oldworking = filemanager_create_path($file, true);
291 $oldactive = filemanager_create_path($file, false);
292 $lock = _filemanager_lock();
293
294 $file->private = $private;
295
296 $updated = _filemanager_update_file($file, $oldworking, $oldactive);
297
298 if ($file != false) {
299 db_query("UPDATE {file} SET private='%s', directory = '%d' WHERE fid=%d", $file->private, $file->directory, $file->fid);
300 }
301 _filemanager_unlock($lock);
302 return $file;
303 }
304
305 /**
306 * helper function for filemanager_rename and filemanager_set_private
307 * does the actual file moving from $oldactive and $oldworking to the
308 * values set in the $file object
309 *
310 * @param $file
311 * file or fid containing new name/directory/private info
312 * @param $oldworking
313 * the path to the current working file
314 * @param $oldactive
315 * the path to the current active file
316 * @return
317 * true on success and false on failure
318 */
319 function _filemanager_update_file(&$file, $oldworking, $oldactive) {
320 // Using the new file object find/create an appropiate area for this file
321 $file = _filemanager_find_directory($file);
322 $newworking = filemanager_create_path($file, true);
323 $newactive = filemanager_create_path($file, false);
324 if (file_exists($oldworking)) {
325 filemanager_create_directory(dirname(dirname($newworking)));
326 filemanager_create_directory(dirname($newworking));
327 if (!_filemanager_move($oldworking, $newworking, FILE_EXISTS_ERROR)) {
328 drupal_set_message("file exists: {$file->filename}", 'error');
329 return false;
330 }
331 }
332 if (file_exists($oldactive)) {
333 filemanager_create_directory(dirname(dirname($newactive)));
334 filemanager_create_directory(dirname($newactive));
335 if (!_filemanager_move($oldactive, $newactive, FILE_EXISTS_ERROR)) {
336 drupal_set_message("file exists: {$file->filename}", 'error');
337 return false;
338 }
339 }
340
341 return true;
342 }
343
344 /**
345 * Gets a working copy of the active version of a file in the filestore
346 * @param $file
347 * file or fid to copy into working stage area
348 * @param $overwrite
349 * if true, create a new copy, overwriting an existing working copy
350 * otherwise, only return the new copy if an existing working copy doesn't exist
351 * @return
352 * a file object on success or false on failure
353 */
354 function filemanager_get_working_copy($file, $overwrite=false) {
355 $file = filemanager_get_file_info($file);
356 if ($file->working && !$overwrite) {
357 return $file;
358 }
359
360 // Purge out any old working files, we do this every time since
361 // not all sites have cron support and we don't one upload to have
362 // to delete several weeks worth of build up
363 filemanager_purge_orphans();
364
365 // Verify we are not going to exceed our working directory size
366 $sizelimit = variable_get('filemanager_working_sizelimit', '10') * 1024 * 1024;
367 if ($sizelimit > -1 && $file->size + filemanager_get_working_size() > $sizelimit) {
368 drupal_set_message(t('Filestore get working copy failed: Working space limit has been reached.'), 'error');
369 return;
370 }
371
372 $active_path = filemanager_create_path($file, false);
373 $working_path = filemanager_create_path($file, true);
374 if (_filemanager_copy($active_path, $working_path, FILE_EXISTS_REPLACE)) {
375 $file->working = 1;
376 db_query("UPDATE {file} SET working=%d WHERE fid=%d", $file->working, $file->fid);
377 } else {
378 $file = false;
379 }
380 return $file;
381 }
382
383 /**
384 * Returns the total size in bytes of all current active files.
385 */
386 function filemanager_get_size() {
387 $size = db_fetch_object(db_query("SELECT SUM(size) AS size FROM {file} WHERE working = 0"));
388 return $size->size;
389 }
390
391 /**
392 * Returns the total size in bytes of all the current working files.
393 * We have to use the filesystem since sizes of working files will
394 * not be accurate in the database.
395 */
396 function filemanager_get_working_size() {
397 $size = 0;
398
399 $files = file_scan_directory(filemanager_create_directory_path(TRUE, TRUE), '.*');
400 foreach($files as $file) {
401 $size += filesize($file->filename);
402 }
403
404 $files = file_scan_directory(filemanager_create_directory_path(FALSE, TRUE), '.*');
405 foreach($files as $file) {
406 $size += filesize($file->filename);
407 }
408
409 return $size;
410 }
411
412 /**
413 * Returns statistics object about a given filestore area. The object contains
414 * the following fields:
415 * - size - the total size of all files in the area
416 * - filecount - which is the number of files in the area.
417 * - sizelimit - the size limit for this area
418 *
419 * @param $area
420 * Name of the area where the file resides which will usually be the name of the module
421 * managing the file.
422 */
423 function filemanager_get_area_info($area) {
424 if ($area == '') {
425 $area = 'general';
426 }
427 $result = db_query("SELECT SUM(size) AS size, COUNT(1) AS filecount FROM {file} WHERE area = '%s'", $area);
428
429 $area_info = db_fetch_object($result);
430 $area_info->sizelimit = variable_get("filemanager_area_limit_" . $area, '-1');
431 return $area_info;
432 }
433
434 /**
435 * Returns a filestore object containing information about a given file.
436 * Passing in a filestore object will just pass the same object back out.
437 *
438 * @param $file
439 * File id which you want information about.
440 */
441 function filemanager_get_file_info($file) {
442 if (is_object($file)) {
443 return $file;
444 }
445 $result = db_query("SELECT fid, area, directory, filename, mimetype, size, active, working, private FROM {file} WHERE fid = %d", $file);
446 return db_fetch_object($result);
447 }
448
449 /**
450 * Promotes a working copy of a file to the active copy
451 *
452 * @param $file
453 * File object or file id you want to promote
454 * @return
455 * Updated $file object for promoted file
456 */
457 function filemanager_promote_working($file) {
458 $file = filemanager_get_file_info($file);
459
460 if ($file->working) {
461 $size = filesize(filemanager_create_path($file, TRUE));
462
463 $area = filemanager_get_area_info($file->area);
464 if ($area->sizelimit > -1 && ($size + $area->size) > ($area->sizelimit * 1024 * 1024)) {
465 drupal_set_message(t('File promotion failed: area out of space'), 'error');
466 return FALSE;
467 }
468
469 $maxsize = variable_get('filemanager_max_size', '400');
470 if ($maxsize > -1 && ($size + filemanager_get_size()) > ($maxsize * 1024 * 1024)) {
471 drupal_set_message(t('File promotion failed: out of space'), 'error');
472 return FALSE;
473 }
474
475 // Verify working directory exists
476 $activedir = filemanager_create_directory_path($file->private, FALSE);
477 if (!file_exists($activedir)) {
478 mkdir($activedir);
479 }
480 $filedir = filemanager_create_directory_path($file->private, FALSE, $file->directory);
481 if (!file_exists($filedir)) {
482 mkdir($filedir);
483 }
484
485 $current_path = filemanager_create_path($file, TRUE);
486 $destination_path = filemanager_create_path($file, FALSE);
487 if (_filemanager_move($current_path, $destination_path, FILE_EXISTS_REPLACE)) {
488 $file->working = FALSE;
489 $file->active = TRUE;
490 $file->size = $size;
491 db_query("UPDATE {file} SET working='%s', active='%s', size=%d WHERE fid=%d", $file->working, $file->active, $file->size, $file->fid);
492 return $file;
493 }
494 }
495 return FALSE;
496 }
497
498 /**
499 * Purge a working file from the repository
500 *
501 * @param $file
502 * File object or file id you want to purge the working file for
503 */
504 function filemanager_purge_working($file) {
505 $file = filemanager_get_file_info($file);
506
507 file_delete(filemanager_create_path($file, TRUE));
508 if ($file->active) {
509 db_query("UPDATE {file} SET working = '%s' WHERE fid = %d", FALSE, $file->fid);
510 }
511 else {
512 db_query("DELETE FROM {file} WHERE fid = %d", $file->fid);
513 }
514 }
515
516 /**
517 * Removes a file from a repository
518 *
519 * @param $file
520 * File object or file id you want to promote
521 */
522 function filemanager_delete($file) {
523 $file = filemanager_get_file_info($file);
524 file_delete(filemanager_create_path($file, TRUE));
525 file_delete(filemanager_create_path($file, FALSE));
526 db_query("DELETE FROM {file} WHERE fid=%d", $file->fid);
527 }
528
529 /**
530 * Returns a list of file areas used by the current module set
531 *
532 * @return
533 * A list of all file areas
534 */
535 function filemanager_area_list() {
536 $areas = array();
537 $areas[] = array('area' => 'general', 'name' => t('General'), 'description' => t('All files not specifically stored in another area.'));
538 foreach (module_list() as $module) {
539 $module_areas = module_invoke($module, 'filemanager_areas');
540 if ($module_areas) {
541 foreach ($module_areas as $area) {
542 $areas[] = $area;
543 }
544 }
545 }
546 return $areas;
547 }
548
549 /**
550 * Purges out files over the age limit in the working repository for all areas
551 */
552 function filemanager_purge_orphans() {
553 $result = db_query("SELECT fid, area, directory, filename, mimetype, size, active, working, private FROM {file} WHERE working = '%s'", TRUE);
554 while ($file = db_fetch_object($result)) {
555 $path = filemanager_create_path($file, TRUE);
556 if (file_exists($path) && (time() - filemtime($path) > 60*variable_get('attachment_tmp_age', '120'))) {
557 filemanager_purge_working($file);
558 }
559 }
560 }
561
562 /**
563 * Transfers a file to the client after calling modules to find out
564 * if a file is accessible for a given user. This function
565 * is here to support legacy private downloads
566 *
567 * @param $file
568 * File object or file id you want to promote
569 * @param $working
570 * Boolean value to indicate if the working version of the file should be returned.
571 * @param $headers
572 * Custom headers, in case you want to stream the file, change the name, etc
573 */
574 function filemanager_transfer($file, $working, $headers = FALSE) {
575 $file = filemanager_get_file_info($file);
576 $filepath = filemanager_create_path($file, $working);
577 $default_headers = array(
578 'Content-Type: '. $file->mimetype,
579 'Content-Length: '. $file->size,
580 'Content-Disposition: attachment; filename="'. $file->filename .'"'
581 );
582
583 if ($file->private) {
584 if (file_check_location($filepath, variable_get('filemanager_private_path','private')) && file_exists($filepath)) {
585 foreach (module_list() as $module) {
586 $headers = module_invoke($module, 'filemanager_download', $file);
587
588 if ($headers === FALSE) {
589 return drupal_access_denied();
590 }
591
592 elseif ($headers === TRUE) {
593 return _filemanager_transfer($filepath, $default_headers);
594 }
595
596 elseif (is_array($headers)) {
597 return _filemanager_transfer($filepath, $headers);
598 }
599 }
600
601 // Since no modules responded check to see if this is a general area file
602 // and allow download if so.
603 if ($file->area == 'general' || variable_get('filemanager_force_private_' . $file->area, 0)) {
604 return _filemanager_transfer($filepath, $default_headers);
605 }
606 }
607
608 return drupal_not_found();
609 }
610 else {
611 // It's a public file so no auth check is required.
612 return file_transfer($filepath, (is_array($headers)) ? $headers : $default_headers);
613 }
614 }
615
616 /**
617 * @}
618 */
619
620 function filemanager_menu($may_cache) {
621 $items = array();
622
623 if ($may_cache) {
624 $items[] = array('path' => 'filemanager/active', 'title' => t('File download'),
625 'callback' => 'filemanager_download_active',
626 'access' => TRUE,
627 'type' => MENU_CALLBACK);
628 $items[] = array('path' => 'filemanager/working', 'title' => t('File download'),
629 'callback' => 'filemanager_download_working',
630 'access' => TRUE,
631 'type' => MENU_CALLBACK);
632 $items[] = array(
633 'path' => 'admin/settings/filemanager',
634 'title' => t('Filemanager'),
635 'description' => t('Settings for Filemanger module'),
636 'callback' => 'drupal_get_form',
637 'callback arguments' => array('filemanager_admin_settings'),
638 'access' => user_access('administer site configuration'),
639 'type' => MENU_NORMAL_ITEM );
640 }
641
642 return $items;
643 }
644
645 /**
646 * Menu callback to download the latest active file
647 */
648 function filemanager_download_active() {
649 $file = filemanager_get_file_info($_GET['fid']);
650 if ($file) {
651 filemanager_transfer($file, FALSE);
652 }
653 else {
654 drupal_not_found();
655 }
656 }
657
658 /**
659 * Menu callback to download the working version of a file. If no working
660 * version is available then the latest active version will be downloaded.
661 */
662 function filemanager_download_working() {
663 $file = filemanager_get_file_info($_GET['fid']);
664 if ($file) {
665 filemanager_transfer($file, TRUE);
666 }
667 else {
668 drupal_not_found();
669 }
670 }
671
672 function filemanager_help($section) {
673 switch ($section) {
674 }
675 }
676
677
678
679 /**
680 * Checks the existence of the directory specified in $form_element. If
681 * validation fails, the form element is flagged with an error from within the
682 * file_check_directory function. See: system_check_directory()
683 *
684 * @param $form_element
685 * The form element containing the name of the directory to check.
686 */
687 function _filemanager_settings_check_directory($form_element) {
688 file_check_directory($form_element['#value'], 0, $form_element['#parents'][0]);
689 return $form_element;
690 }
691
692 /**
693 * Displays filemanager admin screen
694 */
695 function filemanager_admin_settings() {
696 global $base_url;
697
698 $form['filemanager_public_path'] = array(
699 '#type' => 'textfield',
700 '#title' => t('Public file system path'),
701 '#default_value' => variable_get('filemanager_public_path', 'files'),
702 '#maxlength' => 255,
703 '#after_build' => array('_filemanager_settings_check_directory'),
704 '#description' => t('A file system path where public files will be stored. This directory has to exist and be writable by Drupal. This directory has to be accessible over the web. Changing this location after the site has been in use will cause problems so only change this setting on an existing site if you know what you are doing.')
705 );
706 $form['filemanager_public_url'] = array(
707 '#type' => 'textfield',
708 '#title' => t('Public file system URL'),
709 '#default_value' => variable_get('filemanager_public_url', $base_url .'/'. $public_directory_path),
710 '#maxlength' => 255,
711 '#description' => t('Base URL that points to the public files directory.')
712 );
713 $form['filemanager_private_path'] = array(
714 '#type' => 'textfield',
715 '#title' => t('Private file system path'),
716 '#default_value' => variable_get('filemanager_private_path', 'private'),
717 '#maxlength' => 255,
718 '#after_build' => array('_filemanager_settings_check_directory'),
719 '#description' => t('A file system path where private access controlled files will be stored. This directory has to exist and be writable by Drupal. This directory should not be accessible over the web. Changing this location after the site has been in use will cause problems so only change this setting on an existing site if you know what you are doing.')
720 );
721 $form['filemanager_max_filecount'] = array(
722 '#type' => 'textfield',
723 '#title' => t('Maximum files per directory'),
724 '#default_value' => variable_get('filemanager_max_filecount', '200'),
725 '#size' => 6,
726 '#maxlength' => 10,
727 '#description' => t('Maximum number of files to put in each directory.'));
728 $form['filemanager_working_sizelimit'] = array(
729 '#type' => 'textfield',
730 '#title' => t('Working size limit'),
731 '#default_value' => variable_get('filemanager_working_sizelimit', '10'),
732 '#size' => 6,
733 '#maxlength' => 10,
734 '#description' => t('Maximum total size in megabytes for the working storage directory. Enter -1 for unlimited.'),
735 '#requred' => true
736 );
737 $form['filemanager_working_maxage'] = array(
738 '#type' => 'textfield',
739 '#title' => t('Maximum working age'),
740 '#default_value' => variable_get('filemanager_working_maxage', '120'),
741 '#size' => 6,
742 '#maxlength' => 10,
743 '#description' => t('Maximum amoung of time in minutes that an attachment is allowed to live in working storage. Enter -1 for unlimited.'),
744 '#requred' => true
745 );
746 $form['filemanager_max_size'] = array(
747 '#type' => 'textfield',
748 '#title' => t('Maximum size limit'),
749 '#default_value' => variable_get('filemanager_max_size', '400'),
750 '#size' => 6,
751 '#maxlength' => 10,
752 '#description' => t('Maximum amount of disk space that can be consumed by all files. Enter in megabytes.'),
753 '#requred' => true
754 );
755
756 $form['file_areas'] = array(
757 '#type' => 'fieldset',
758 '#title' => t('File areas'),
759 '#tree' => true,
760 '#theme' => 'filemanager_fileareas_admin',
761 'info' => array(
762 '#type' => 'markup',
763 '#value' => '<em>'. t("The following numbers control the total size of all files allowed in a particular area. Enter '-1' to allow unlimited size. Select force private to force all files in that area to be streamed (no direct access) through the private directory. If the module that controls that area does not enforce security it will default to allow all access.") .'</em>'
764 ),
765 );
766 foreach(filemanager_area_list() as $area) {
767 $key = $area['area'];
768 $form['file_areas']['areas'][$key] = array(
769 '#description' => $area['description'],
770 '#title' => $area['name'],
771 'limit' => array(
772 'filemanager_area_limit_' . $key => array(
773 '#type' => 'textfield',
774 '#default_value' => variable_get('filemanager_area_limit_' . $key, '-1'),
775 '#size' => 6,
776 '#maxlength' => 10,
777 ),
778 ),
779 'force' => array(
780 'filemanager_force_private_' . $key => array(
781 '#type' => 'checkbox',
782 '#default_value' => variable_get('filemanager_force_private_' . $key, 0),
783 '#return_value' => 1,
784 ),
785 ),
786 );
787 }
788
789 return system_settings_form($form);
790 }
791
792 function theme_filemanager_fileareas_admin($form) {
793 $output = drupal_render($form['info']);
794
795 $header = array(t('Area'),t('Description'),t('Max size (Mb)'),t('Force Private'));
796 foreach (element_children($form['areas']) as $key) {
797 $row = array();
798 $row[] = $form['areas'][$key]['#title'];
799 $row[] = $form['areas'][$key]['#description'];
800 $row[] = drupal_render($form['areas'][$key]['limit']);
801 $row[] = drupal_render($form['areas'][$key]['force']);
802 $rows[] = $row;
803 }
804 $output .= theme('table', $header, $rows);
805
806 $output .= drupal_render($form);
807 return $output;
808 }
809
810 /**
811 * Handle the submission of the admin/settings form. This is a bit unusual
812 * since the settings form is normally handled automatically, but due to the
813 * deep fileareas->areas array used for the file areas table, the
814 * system_settings_form_submit can't handle all the values without some
815 * pre-processing.
816 */
817 function filemanager_admin_settings_submit($form_id, $values) {
818 // Flatten the fileareas array into $values
819 foreach ($values['file_areas']['areas'] as $area => $settings) {
820 foreach ($settings as $value_key => $value_array) {
821 foreach ($value_array as $key => $value) {
822 $values[$key] = $value;
823 }
824 }
825 }
826 system_settings_form_submit($form_id, $values);
827 }
828
829 /**
830 * Creates a directory if it does not already exist.
831 *
832 * @param $directory
833 */
834 function filemanager_create_directory($directory) {
835 if (!file_exists($directory)) {
836 mkdir($directory);
837 }
838 }
839
840 /**
841 * Removes a directory and all files contained within.
842 *
843 * @param $directory
844 */
845 function filemanager_remove_directory($directory) {
846 if (is_dir($directory)) {
847 $files = file_scan_directory($directory, ".*");
848 foreach($files as $file) {
849 file_delete($file);
850 }
851 rmdir($directory);
852 }
853 }
854
855 function _filemanager_lock() {
856 $lock_file = variable_get('filemanager_private_path', 'private') .'/'. 'filemanager.lck';
857 $flk = fopen($lock_file,'w+');
858 flock($flk, LOCK_EX);
859 return $flk;
860 }
861
862 function _filemanager_unlock(&$handle) {
863 flock($handle, LOCK_UN);
864 fclose($handle);
865 }
866
867 /**
868 * this must be called while the lock is held
869 * @param $file
870 */
871 function _filemanager_find_directory(&$file) {
872 // Find a directory that is not already full and does not contain our files
873 $file->directory = 0;
874 $directories = db_query("SELECT directory, count(1) AS filecount FROM {file} WHERE private = '%s' GROUP BY directory ORDER BY directory ASC", $file->private);
875
876 // this while loop requires the $directories array to be ordered in ascending order
877 while ($directory = db_fetch_object($directories)) {
878 // The idea here is to find a directory where the filename doesn't exist
879 // and we haven't hit the maximum file limit. The directories are named
880 // numerically and the first part of the test makes sure that the they're
881 // filled in sequenceially. $file->directory is incremented by 1 each time
882 // but $directory->directory comes from the database. If
883 // $directory->directory > $file->directory, then $file->directory doesn't
884 // exist and would be a safe place to save the file.
885 if ($directory->directory > $file->directory || $directory->filecount < variable_get('filemanager_max_file_count', '2000')) {
886 // If the directory is ok now lets make sure we don't already have this
887 // filename in the directory (checking both working and active).
888 if (!file_exists(filemanager_create_path($file, FALSE)) && !file_exists(filemanager_create_path($file, TRUE))) {
889 break;
890 }
891 }
892 $file->directory++;
893 }
894 return $file;
895 }
896
897 /**
898 * Copies a file to a new location. This is a powerful function that in many
899 * ways performs like an advanced version of copy().
900 * - Checks if $source and $dest are valid and readable/writable.
901 * - Performs a file copy if $source is not equal to $dest.
902 * - If file already exists in $dest either the call will error out, replace the
903 * file or rename the file based on the $replace parameter.
904 *
905 * @param $source A string specifying the file location of the original file.
906 * This parameter will contain the resulting destination filename in case of
907 * success.
908 * @param $dest A string containing the directory $source should be copied to.
909 * @param $replace Replace behavior when the destination file already exists.
910 * - FILE_EXISTS_REPLACE - Replace the existing file
911 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
912 * unique
913 * - FILE_EXISTS_ERROR - Do nothing and return false.
914 * @return True for success, false for failure.
915 */
916 function _filemanager_copy(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
917 $directory = $dest;
918 $basename = file_check_path($directory);
919
920 // Make sure we at least have a valid directory.
921 if ($basename === false) {
922 drupal_set_message(t('The selected file %file could not be uploaded, because the destination %directory is not properly configured.', array('%file' => theme('placeholder', $source), '%directory' => theme('placeholder', $dest))), 'error');
923 watchdog('file system', t('The selected file %file could not not be uploaded, because the destination %directory could not be found, or because its permissions do not allow the file to be written.', array('%file' => theme('placeholder', $source), '%directory' => theme('placeholder', $dest))), WATCHDOG_ERROR);
924 return 0;
925 }
926
927 // Process a file upload object.
928 if (is_object($source)) {
929 $file = $source;
930 $source = $file->filepath;
931 if (!$basename) {
932 $basename = $file->filename;
933 }
934 }
935
936 $source = realpath($source);
937
938 if (!file_exists($source)) {
939 drupal_set_message(t('The selected file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => theme('placeholder', $source))), 'error');
940 return 0;
941 }
942
943 // If the destination file is not specified then use the filename of the
944 // source file.
945 $basename = $basename ? $basename : basename($source);
946 $dest = $directory .'/'. $basename;
947
948 // Make sure source and destination filenames are not the same, makes no sense
949 // to copy it if they are. In fact copying the file will most likely result in
950 // a 0 byte file. Which is bad. Real bad.
951 if ($source != realpath($dest)) {
952 if (file_exists($dest)) {
953 switch ($replace) {
954 case FILE_EXISTS_RENAME:
955 // Destination file already exists and we can't replace is so we try
956 // and and find a new filename.
957 if ($pos = strrpos($basename, '.')) {
958 $name = substr($basename, 0, $pos);
959 $ext = substr($basename, $pos);
960 }
961 else {
962 $name = $basename;
963 }
964
965 $counter = 0;
966 do {
967 $dest = $directory .'/'. $name .'_'. $counter++ . $ext;
968 } while (file_exists($dest));
969 break;
970
971 case FILE_EXISTS_ERROR:
972 drupal_set_message(t('The selected file %file could not be copied, because a file by that name already exists in the destination.', array('%file' => theme('placeholder', $source))), 'error');
973 return 0;
974 }
975 }
976
977 if (!@copy($source, $dest)) {
978 drupal_set_message(t('The selected file %file could not be copied.', array('%file' => theme('placeholder', $source))), 'error');
979 return 0;
980 }
981
982 // Give everyone read access so that FTP'd users or non-webserver users
983 // can see/read these files.
984 @chmod($dest, 0664);
985 }
986
987 if (is_object($file)) {
988 $file->filename = $basename;
989 $file->filepath = $dest;
990 $source = $file;
991 }
992 else {
993 $source = $dest;
994 }
995
996 return 1; // Everything went ok.
997 }
998
999 /**
1000 * Moves a file to a new location.
1001 * - Checks if $source and $dest are valid and readable/writable.
1002 * - Performs a file move if $source is not equal to $dest.
1003 * - If file already exists in $dest either the call will error out, replace the
1004 * file or rename the file based on the $replace parameter.
1005 *
1006 * @param $source A string specifying the file location of the original file.
1007 * This parameter will contain the resulting destination filename in case of
1008 * success.
1009 * @param $dest A string containing the directory $source should be copied to.
1010 * @param $replace Replace behavior when the destination file already exists.
1011 * - FILE_EXISTS_REPLACE - Replace the existing file
1012 * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is
1013 * unique
1014 * - FILE_EXISTS_ERROR - Do nothing and return false.
1015 * @return True for success, false for failure.
1016 */
1017 function _filemanager_move(&$source, $dest = 0, $replace = FILE_EXISTS_RENAME) {
1018 $path_original = is_object($source) ? $source->filepath : $source;
1019
1020 if (_filemanager_copy($source, $dest, $replace)) {
1021 $path_current = is_object($source) ? $source->filepath : $source;
1022
1023 if ($path_original == $path_current || file_delete($path_original)) {
1024 return 1;
1025 }
1026 drupal_set_message(t('The removal of the original file %file has failed.', array('%file' => theme('placeholder', $source))), 'error');
1027 }
1028 return 0;
1029 }
1030
1031 /**
1032 * Transfer file using http to client. Pipes a file through Drupal to the
1033 * client.
1034 *
1035 * @param $source File to transfer.
1036 * @param $headers An array of http headers to send along with file.
1037 */
1038 function _filemanager_transfer($source, $headers) {
1039 ob_end_clean();
1040
1041 foreach ($headers as $header) {
1042 // To prevent HTTP header injection, we delete new lines that are
1043 // not followed by a space or a tab.
1044 // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
1045 $header = preg_replace('/\r?\n(?!\t| )/', '', $header);
1046 header($header);
1047 }
1048
1049 // Transfer file in 1024 byte chunks to save memory usage.
1050 if ($fd = fopen($source, 'rb')) {
1051 set_time_limit(0);
1052 while (!feof($fd)) {
1053 print fread($fd, 1024);
1054 ob_flush();
1055 flush();
1056 }
1057 fclose($fd);
1058 }
1059 else {
1060 drupal_not_found();
1061 }
1062 exit();
1063 }

  ViewVC Help
Powered by ViewVC 1.1.2