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

Contents of /contributions/modules/filebrowser_extensions/filebrowser_extensions.module

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


Revision 1.12 - (show annotations) (download) (as text)
Sun Nov 26 15:01:07 2006 UTC (3 years ago) by dman
Branch: MAIN
CVS Tags: HEAD
Changes since 1.11: +34 -12 lines
File MIME type: text/x-php
Minor smoothing to enable attaching inline directories.
Changes css a bit to not indent first column of a tree.
re-enabled the 'show uplinks' option that was disabled for a while
1 <?php
2 // $Id: filebrowser_extensions.module,v 1.11 2006/11/19 10:51:58 dman Exp $
3
4 /**
5 * @file
6 * Allows configurable and context-sensitive uses of the filebrowser
7 * functionality
8 *
9 * See the readme for extended usage instructions.
10 *
11 */
12
13 require_once('filebrowser_cells.php');
14
15 /**
16 * Implementation of hook_help().
17 */
18 function filebrowser_extensions_help($section = NULL) {
19 switch ($section) {
20 case 'admin/modules#description':
21 return t('Extends Filebrowser with inline directory listings, pop-ups and AJAX.');
22 break;
23 case 'admin/help#filebrowser_extensions':
24 // include dirname(__FILE__)."/filebrowser_demo.inc";
25 return _filter_autop( drupal_eval( file_get_contents( dirname(__FILE__)."/README.txt") ) ); // . filebrowser_demo() ;
26 case 'admin/settings/filebrowser_extensions':
27 return t("<p>
28 A <em>Filebrowser Profile</em> is a set of rules that allow file browsing in
29 specified directories with given parameters.
30 </p>
31 <p>Different profiles can be given different root directories, different
32 rendering options and different access permissions.
33 </p>
34 <p><a href='%help_link'>More Help and examples</a></p>
35 ",array('%help_link'=>url('admin/help/filebrowser_extensions'))
36 );
37 }
38 }
39
40
41 /**
42 * Implementation of hook_menu().
43 *
44 * A new 'filebrowser_list' page becodes available, an alternate theme to the
45 * normal filebrowser table.
46 * Some callbacks to support AJAX lookups are supplied.
47 *
48 * Two autocomplete responders are published also: filebrowser/autocomplete and
49 * filebrowser/autocomplete_directory can be used as autocomplete handlers in
50 * form building.
51 */
52 function filebrowser_extensions_menu($may_cache=FALSE) {
53 $items = array();
54 if($may_cache){
55 $items[] = array('path' => 'filebrowser_popup', 'title' => t('filebrowser popup'),
56 'access' => user_access('access filebrowser'), 'callback' => 'filebrowser_custom_page',
57 'type' => MENU_CALLBACK);
58 $items[] = array('path' => 'filebrowser_list', 'title' => t('filebrowser list'),
59 'access' => user_access('access filebrowser'), 'callback' => 'filebrowser_custom_page',
60 'type' => MENU_CALLBACK);
61 $items[] = array('path' => 'filebrowser_autocomplete_directory',
62 'title' => t('autocomplete filebrowser'),
63 'callback' => 'filebrowser_autocomplete_directory',
64 'access' => user_access('access filebrowser'),
65 'type' => MENU_CALLBACK);
66 $items[] = array('path' => 'filebrowser_autocomplete',
67 'title' => t('autocomplete filebrowser'),
68 'callback' => 'filebrowser_autocomplete',
69 'access' => user_access('access filebrowser'),
70 'type' => MENU_CALLBACK);
71 }
72 // add the dynamic paths
73 $profiles = variable_get('filebrowser_profiles',array());
74 foreach($profiles as $id => $profile){
75 $items[] = array('path' => $profile['path'],
76 'title' => $profile['title']?$profile['title']:$profile['path'],
77 'callback' => 'filebrowser_custom_page',
78 'access' => user_access('access filebrowser'),
79 'type' => MENU_SUGGESTED_ITEM);
80 }
81
82 return $items;
83 }
84
85 /**
86 * Interface to configure the filebrowser view profiles.
87 *
88 * A filebrowser profile specifies the root directory, the authentication, and
89 * the columns that can show up as part of a filebrowser request.
90 */
91 function filebrowser_extensions_settings(){
92 $filebrowser_profiles = variable_get('filebrowser_profiles',array());
93
94 $settings_form = array();
95
96 if($_REQUEST['create']){
97 $filebrowser_profiles[$_REQUEST['create']] = array(
98 'path' => $_REQUEST['create'],
99 'root' => file_directory_path(),
100 'cols' => 'expander;file;size',
101 'serve_as_drupal_files' => TRUE,
102 'blacklist' => '.htaccess',
103 'hide_directories' => false,
104 );
105 drupal_set_message(t("A new profile has been filled in below. <strong>Save it</strong> then visit the link it publishes." ));
106 }
107 else if(empty($filebrowser_profiles)){
108 $settings_form['sample'] = array('#value' => t(
109 "<a href='%sample_link'>Create a sample profile</a> to get started.",
110 array('%sample_link' => url('admin/settings/filebrowser_extensions','create=browse_files'))
111 ));
112 }
113
114 $profiles_form = array(
115 '#tree' => TRUE,
116 );
117 foreach($filebrowser_profiles as $id => $profile){
118 $profiles_form[$id] = _filebrowser_profile_form($profile);
119 }
120 $profiles_form['_new'] = _filebrowser_profile_form(array());
121
122 $settings_form['filebrowser_profiles'] = $profiles_form;
123 $settings_form['filebrowser_icons'] = array(
124 '#type' => 'textfield',
125 '#title' => t('Icon directory'),
126 '#default_value' => variable_get('filebrowser_icons', drupal_get_path('module','filebrowser_extensions').'/contrib/icons' ),
127 '#maxlength' => '100',
128 '#size' => '70',
129 '#autocomplete_path' => 'filebrowser_autocomplete',
130 '#description' => t('Name of directory where file type icons are stored. <br/>Files should be named "file-txt.png", "file-gif.png", etc. The default icon is "file-default.png".')
131 );
132
133
134 return $settings_form;
135 }
136
137 function _filebrowser_profile_form($profile){
138 $form = array(
139 '#type' => 'fieldset',
140 '#title' => $profile['path'] ? t("Filebrowser instance for '"). $profile['path'] ."'": t("New Filebrowser profile"),
141 '#collapsible' => TRUE,
142 '#collapsed' => TRUE,
143 );
144
145
146 if($profile['path']){
147 $form['summary'] = array( '#value' => t("<p>
148 This filebrowser instance publishes the files stored at
149 <code><strong>%completepath</strong></code> under the URL
150 <code><strong>%path</strong></code>.
151 <br/>Clicking on files presented like that
152 will invoke a request to paths underneath
153 <code><strong>%rel_url</strong></code> .</p>
154 <h2>Link to: %link</h2>
155 <p>The menu system should be displaying a 'suggested item'
156 that can be enabled to link to this utility.
157 </p>
158
159 ", array(
160 '%completepath' => $profile['serve_as_drupal_files']?file_create_path($profile['root']):( ($profile['root'][0]=='/')? $profile['root'] : '[SITEROOT]'.$profile['root']) ,
161 '%path' => url($profile['path']),
162 '%link' => l($profile['path'],$profile['path']),
163 '%rel_url' => file_create_url($profile['rel_url']),
164 )));
165 }
166
167 $form['path'] = array(
168 '#type' => 'textfield',
169 '#title' => 'path',
170 '#default_value' => $profile['path'],
171 '#description' => t("
172 The URL alias this browse function will be published under.
173 eg <b>browse_files</b>.
174 Subdirectories will naturally be accessible underneath this root path.
175 "),
176 );
177 if(is_array($profile['cols']))$profile['cols']=join(';',$profile['cols']);
178 $form['cols'] = array(
179 '#type' => 'textfield',
180 '#title' => 'columns',
181 '#default_value' => $profile['cols']?$profile['cols']:"icon;name;size",
182 '#description' => t("
183 List the desired columns or cells, separated by semicolons
184 "),
185 );
186 $columns = array(
187 'name','file','size','age','date','info','type','filename','checkbox','select_button','expander'
188 );
189 foreach($columns as $a => $d){
190 // ask the cells to describe themselves
191 $f = 'filebrowser_get_cell_'.$d;
192 if(function_exists($f)){
193 $header = $f('',NULL);
194 $col_descriptions[$d] = "<b>$d</b> : ".$header['description'];
195 }
196 }
197
198 $form['about_cols'] = array(
199 '#type' => 'fieldset',
200 '#title' => t('About columns...'),
201 '#collapsed' => TRUE, '#collapsible' => TRUE,
202 'list' => array(
203 '#value' => theme('item_list',$col_descriptions,'Available columns include:'),
204 )
205 );
206
207
208 $form['root'] = array(
209 '#type' => 'textfield',
210 '#title' => 'Actual browse root',
211 '#default_value' => $profile['root'],
212 '#description' => t("
213 File path to the top of all directories that can be browsed using this
214 method.
215 Either <acronym title='/usr/me/my_files'>filesystem-absolute</acronym>,
216 <acronym title='files/images/shared'>drupal-root-relative</acronym>,
217 or <acronym title='images/public (with Drupal file server checked)'>files-dir-relative</acronym>
218 (check below).
219 ")
220 );
221
222 $form['whitemask'] = array(
223 '#type' => 'textfield',
224 '#title' => 'Glob pattern (which files to show)',
225 '#default_value' => $profile['whitemask'],
226 '#description' => t("
227 A Regular Expression matching wanted files.
228 eg: <code>.*\\.jpg|gif</code>.
229 If blank, all files will be selected. Default: <code>.*</code>
230 ")
231 );
232 $form['blacklist'] = array(
233 '#type' => 'textfield',
234 '#title' => 'Blacklist of files to hide',
235 '#default_value' => $profile['blacklist'],
236 '#description' => t("
237 Filenames separated by semicolons,
238 eg: <code>.htaccess;descript.ion;CVS</code>.
239 If blank, no files will be hidden.
240 ")
241 );
242
243 /*
244 $parameters = array(
245 '%path' => t("Current context node path, eg about/us . Gets filled in when called from a node context."),
246 '%userid' => t("Current userid."),
247 '%files' => t("System file directory path ") . file_directory_path(),
248 '%type' => t("Current node type"),
249 );
250 foreach($parameters as $a => $d){
251 $parameters[$a] = "<b>$a</b> : $d";
252 }
253
254 $form['parameters'] = array(
255 '#value' => theme('item_list',$parameters, t("Available path variables are:")),
256 );
257 */
258 $form['recursive'] = array(
259 '#type' => 'checkbox',
260 '#title' => 'Display all contents recursively.',
261 '#default_value' => $profile['recursive'],
262 '#description' => t("
263 This can lead to intensive page requests.
264 Can be over-ridden on demand from the pages themselves
265 ")
266 );
267 $form['hide_directories'] = array(
268 '#type' => 'checkbox',
269 '#title' => 'don\'t display subdirectories at all.',
270 '#default_value' => $profile['hide_directories'],
271 '#description' => t("
272 This will over-ride the recursive option.
273 ")
274 );
275 $form['show_uplinks'] = array(
276 '#type' => 'checkbox',
277 '#title' => 'show links to allow navigation to higher directories (up to this contexts root).',
278 '#default_value' => $profile['show_uplinks'],
279 '#description' => t("
280 Although the root setting controls the actual security of what people can see, some uses
281 - like directory attachments - don't really require 'parent' links.
282 Uncheck this to disallow up navigation.
283 ")
284 );
285
286 $form['serve_as_drupal_files'] = array(
287 '#type' => 'checkbox',
288 '#title' => 'Use the Drupal file server to resolve how the files get served.',
289 '#default_value' => $profile['serve_as_drupal_files'],
290 '#description' => t("
291 This is required if files are served using Drupal 'private' mechanism,
292 or the real file root is above the Drupal base.
293 Can also be used to redirect to an FTP server or virtual directories.
294 ")
295 );
296
297 $form['rel_url'] = array(
298 '#type' => 'textfield',
299 '#title' => 'Presented URL base',
300 '#default_value' => $profile['rel_url'],
301 '#description' => t("Base URL path to publish the above found files as.")
302 );
303
304 $form['render'] = array(
305 '#type' => 'select',
306 '#title' => 'Render method.',
307 '#default_value' => $profile['render'],
308 '#options' => array('list'=>'list','table'=>'table','popup'=>'popup'),
309 );
310 $form['help'] = array( '#value' => t("<p>
311 To <b>delete</b> a profile, clear its 'path' parameter and save.
312 </p>"));
313
314 return $form;
315 }
316
317 function filebrowser_extensions_settings_form_validate($form_id, $form_items){
318 if(! is_dir($form_items['filebrowser_icons'])){
319 form_set_error('filebrowser_icons','Folder '.$form_items['filebrowser_icons'].' does not exist');
320 }
321 }
322
323 /**
324 * Re-indexes the profiles and discards invalid ones
325 */
326 function filebrowser_extensions_settings_form_submit($id,$form_values){
327 // Ensure indexing/labelling is correct
328 $profiles = $form_values['filebrowser_profiles'];
329 foreach($profiles as $id => $profile){
330 unset($profiles[$id]);
331 if($profile['path']){
332 $profiles[$profile['path']] = $profile;
333 }
334 }
335 variable_set('filebrowser_profiles',$profiles);
336 variable_set('filebrowser_icons',$form_values['filebrowser_icons']);
337 }
338
339
340 /**
341 * Implementation of hook_perm().
342 */
343 function filebrowser_extensions_perm() {
344 return array('access filebrowser');
345 }
346
347 /**
348 * Return the context (custom filebrowser path) to be used by the current
349 * request.
350 *
351 * Figure it either from the directly given _REQUEST parameter, or from working
352 * backwards from the requested subsection.
353 */
354 function filebrowser_deduce_context($subfolder){
355 // Our full URL, minus our argument = the current filebrowser context. Unless clean-urls are off
356 $context = $_REQUEST['context'] ? $_REQUEST['context'] : ( $_REQUEST['subsection'] ? $_GET['q'] : filebrowser_split_base($subfolder) );
357 $context = trim($context,'/');
358 return $context;
359 }
360
361 /**
362 * fetch the default profile, or the requested one, or SET it.
363 *
364 * Applies to this pageload only, and is used by other cell functions
365 * that want to know what's up.'
366 * This remembers the last profile fetched, and will return it again if called
367 * with no parameters.
368 */
369 function filebrowser_profile($context=NULL,$p=NULL,$save=FALSE){
370 static $profile;
371 if($context){
372 $profiles = variable_get('filebrowser_profiles',array());
373 $profile = $profiles[$context];
374
375 if(!$profile && ($context == ('filebrowser_popup'))){
376 // hard-code special case
377 filebrowser_set_profile_defaults($profile);
378 $profile['path'] = 'filebrowser_popup';
379 $profile['render'] = 'popup';
380 $profile['cols'] = 'expander;file;select_button';
381 }
382 filebrowser_set_profile_defaults($profile);
383 }
384 if($p){
385 $profile = $p;
386 filebrowser_set_profile_defaults($profile);
387 }
388 if($context && $save){
389 $profiles[$context] = $profile;
390 variable_set('filebrowser_profiles',$profiles);
391 }
392
393 return $profile;
394 }
395
396 /**
397 * Ensure the given profile has suitable defaults.
398 * Operates by handle
399 */
400 function filebrowser_set_profile_defaults(&$profile){
401 // Recursive is either defined in the profile or over-ridden in the request
402 $profile['recursive'] = $_REQUEST['edit']['recursive'] ? $_REQUEST['edit']['recursive'] : $profile['recursive'];
403
404 $profile['path'] = isset($profile['path'])?$profile['path'] : 'filebrowser_list';
405 // ensure the rest of profile has meaningful values
406 $profile['render'] = $profile['render'] ? $profile['render'] : 'list';
407 if(! is_array($profile['cols']))
408 $profile['cols'] = $profile['cols'] ? array_map('trim', (split(";",$profile['cols']))) : array('icon','name');
409 $profile['root'] = $profile['root'] ? $profile['root'] : file_directory_path();
410 $profile['rel_url'] = isset($profile['rel_url']) ? $profile['rel_url'] : ($profile['serve_as_drupal_files'] ? file_create_url('') : file_directory_path()) ;
411
412 $profile['show_uplink'] = isset($profile['show_uplink']) ? $profile['show_uplink'] : TRUE;
413
414 $profile['whitemask'] = $profile['whitemask'] ? $profile['whitemask'] : '.*';
415 $profile['blacklist_a'] = $profile['blacklist'] ? split(';',$profile['blacklist']) : array('CVS');
416 array_push($profile['blacklist_a'], '.');
417 array_push($profile['blacklist_a'],'..');
418
419 return $profile;
420 }
421
422 //////////////////////////////////////////////////////
423 // end admin, begin list & render
424 //////////////////////////////////////////////////////
425
426
427 /**
428 * Generic callback for custom profile filebrowser pages.
429 *
430 * This is the core function of the whole module.
431 *
432 * Renders a filebrowser page with the appropriate parameters.
433 * Configs are from the profile maintainded in filebrowser_estension settings.
434 *
435 * The context is calculated from the request, or optionally may be given via a
436 * 'context=' parameter
437 * @param $subfolder
438 */
439 function filebrowser_custom_page(){
440 $parts = func_get_args();
441 $subfolder = join('/',$parts);
442 // subfolder is always from the users POV, despite what the actual paths may be
443 $subfolder = $subfolder?$subfolder:$_REQUEST['subsection'];
444
445 // Set context for later filebrowser links. Need to be self-aware.
446 if(!filebrowser_profile()){
447 $context = filebrowser_deduce_context($subfolder);
448 $profile = filebrowser_profile($context);
449 }
450 // calling filebrowser_profile also memos the default/current profile.
451
452 if(! $profile){
453 drupal_set_message($subfolder.' ['.$context.']' . t(" is not correctly configured for filebrowser access. No browse profile seemed to match this request."));
454 return drupal_access_denied();
455 }
456
457 // todo - check auth here
458
459 $completepath = filebrowser_create_path($subfolder,$profile);
460
461 # dsm($profile);
462 # dsm("context is $context");
463 # dsm("subfolder is $subfolder");
464 # dsm("completepath is $completepath");
465
466 filebrowser_set_breadcrumbs($subfolder);
467 drupal_set_title( t("Files in <code title='%long_path' >%subfolder</code>" ,
468 array('%long_path'=>$completepath,'%subfolder'=>($subfolder?$subfolder:'filebrowser root'))
469 ));
470
471
472 // Ready to scan...
473 if (! is_dir($completepath)) {
474 drupal_set_message(t("$completepath is not a directory available for browsing for this user"));
475 if ( is_file($completepath)) {
476 drupal_set_message(t("It's a file, and may possibly be accessed at ". l( $subfolder,filebrowser_file_url($subfolder) ) ));
477 }
478 return "sorry";
479 }
480
481 /////////////////
482 // Scan here
483 /////////////////
484 $dir_structure = file_scan_directory($completepath,$profile['whitemask'],$profile['blacklist_a'],NULL,$profile['recursive']) ;
485
486 if (!$dir_structure) {
487 drupal_set_message(t("Unable to get files for this directory '$subfolder'"));
488 return "sorry";
489 }
490
491 if( ($subfolder != '') && ($subfolder != '.') && ($_REQUEST['mode']!='raw')){
492 // Add uplink
493 if($profile['show_uplinks']){
494 $dir_structure['..'] = (object)array(
495 'filename'=>dirname($completepath),
496 'basename'=>t('..up to "%parentname"', array('%parentname' => basename(dirname($completepath))) ),
497 );
498 }
499 }
500
501 #dsm($dir_structure);
502 $tree = filebrowser_sort_list_into_tree($dir_structure,$profile['root'],$subfolder);
503 #dsm($tree);
504
505 # $output = "displaying ".join(", ",$profile['cols']);
506
507 $form = filebrowser_construct_form($tree,$profile);
508
509 if($_REQUEST['mode']=='raw'){
510 /////////////////
511 // Being called as a subrequest
512 // (Not allowed to nest forms, so rendering must differ.)
513 // form_render() invocation does the layout (including sorting), but not form header.
514 $output = form_render($form);
515 $output = $output ? $output : t("[Empty Folder] <a title='$completepath'>$subfolder</a> ");
516 print $output;
517 exit();
518 }
519 else if(($profile['render']== 'popup')){
520 /////////////////
521 // Render without much chrome
522 /////////////////
523
524 $bread = drupal_get_breadcrumb();
525 array_shift($bread);
526 $output = theme('filebrowser_breadcrumb',$bread);
527
528 $output .= drupal_get_form('tree',$form);
529
530 if($_REQUEST['mode']=='in-place'){
531 print $output;
532 return NULL;
533 }
534
535 include(dirname(__FILE__)."/filebrowser-popup.tpl.inc");
536 return NULL;
537 }
538 else {
539 /////////////////
540 // this version returns a whole form, as a page
541 /////////////////
542 $output .= drupal_get_form('tree',$form);
543 return theme('filebrowser',$output);
544 }
545 }
546
547
548 function filebrowser_construct_form($tree,$profile){
549
550 if($profile['render']== 'list'){
551 /////////////////
552 // Render as list
553 /////////////////
554 $form = array(filebrowser_tree_to_rows($tree,$profile['cols']));
555 }
556 else {
557 /////////////////
558 // Render as table
559 /////////////////
560 // Prepare headers for display (ask the functions themselves what they are called)
561 $headers = array_map(
562 create_function(
563 '$colname',
564 '$func = "filebrowser_get_cell_".$colname; if(function_exists($func))return $func(""); '
565 ),
566 $profile['cols']
567 );
568
569 $form = array( filebrowser_tree_to_tables($tree,$profile['cols']) ) ;
570 }
571
572 return $form;
573 }
574
575 function filebrowser_embed($subfolder='',$profile=array()){
576 static $count;
577 $context = $profile['path'];
578
579 filebrowser_set_profile_defaults($profile);
580 // set context
581 // we need to make a dummy callback to serve the
582 // later ajax requests for this item
583 filebrowser_profile($context,$profile,TRUE);
584 $completepath = filebrowser_create_path($subfolder,$profile);
585
586 // scan & render
587 $dir_structure = file_scan_directory($completepath,$profile['whitemask'],$profile['blacklist_a'],NULL,$profile['recursive']) ;
588
589 $tree = filebrowser_sort_list_into_tree($dir_structure,$profile['root'],$subfolder);
590 $form = filebrowser_construct_form($tree,$profile);
591 $output = drupal_get_form($context,$form);
592 return theme('filebrowser', $output);
593
594 }
595
596 /**
597 * Separate the request from the function.
598 * Custom filebrowsers can be invoked from any path on the server, and their
599 * relative subfolders are tacked onto the end of the URL.
600 *
601 * [admin/custom/filebrowser/][my/special/files]
602 *
603 * Find where to split this request. Use rexeps, as strrpos is broken
604 * @return the base filebrowser path for the current job. Contains trailing
605 * slash.
606 */
607 function filebrowser_split_base($subfolder){
608 $filebrowser_base = preg_replace('|'.preg_quote($subfolder).'$|','',$_GET['q']);
609 return $filebrowser_base;
610 }
611
612
613 /**
614 * Prepend the filebrowser location to the path.
615 * Allows easy path generation even if $path has a leading slash.
616 * @param $path
617 */
618 function filebrowser_url($path) {
619 static $base;
620 if(!$base){
621 $profile = filebrowser_profile();
622 $base = $profile['path'];
623 }
624 return join_paths($base,$path);
625 }
626 /**
627 * Similar to above, but links directly to the file,
628 *
629 */
630 function filebrowser_file_url($path) {
631 static $rel_url;
632 if(!$rel_url){
633 $profile = filebrowser_profile();
634 $rel_url = $profile['rel_url'];
635 }
636
637 // The rel_url parameter used here may resolve some embedded variables.
638 $variables = array(
639 '%files' => file_directory_path(),
640 );
641 $url = join_paths( t( $rel_url ,$variables) ,$path);
642
643 if($profile['serve_as_drupal_files']){
644 return file_create_url($url);
645 }
646 return $url;
647 }
648
649 /**
650 * Returns not an l() link, but an AJAX trigger tha has a similar effect.
651 */
652 function filebrowser_inline_link($title,$path){
653 $uri = url(filebrowser_url($path) , 'mode=in-place');
654 return l($title, '#', array('onClick' => "HTTPGet(\"$uri\", display_subdirectory, \"filebrowser\");return false;") );
655 }
656
657 /**
658 * Add the appropriate prefixes to a path
659 * to return a complete system path.
660 *
661 * May manipulate the input path by reference to find a parent if the target
662 * does not exist.
663 */
664 function filebrowser_create_path(&$path,$profile=array()){
665 filebrowser_set_profile_defaults($profile);
666
667 if(substr($path,0,strlen($profile['root'])) == $profile['root'])
668 $completepath = $path;
669 else
670 // If path is not already under root, prepend it
671 $completepath = join_paths($profile['root'],$path);
672
673 if($profile['serve_as_drupal_files']){
674 $completepath=file_create_path($completepath);
675 }
676
677 if (! is_dir($completepath)) {
678 drupal_set_message("$completepath wasn't found to be a directory. Going up");
679 $completepath = dirname($completepath);
680 $path = dirname($path);
681 }
682 return $completepath;
683 }
684
685 /**
686 * Implimentation of hook_file_download
687 * Need to let the system/files 'private' method know it can let everything
688 * through.
689 * This is hence less secure than the 'private' restriction used to be.
690 */
691 function filebrowser_extensions_file_download($file) {
692 if (TRUE || user_access('access content')) {
693 # If it exists, and you can see it, you can get it.
694 $file = file_create_path($file);
695 $name = mime_header_encode($file->filename);
696 return array(
697 'Content-type: '. mime_content_type ($file),
698 );
699 }
700 else {
701 return -1;
702 }
703 }
704
705 /**
706 * The breadcrumbs will be set relative to the current filebrowser profile root.
707 *
708 * When mode is set to 'in-place', the links will trigger an AJAX refresh rather
709 * than a page load.
710 */
711 function filebrowser_set_breadcrumbs($subfolder){
712 $parts=split('/',$subfolder);
713
714 $breadcrumb = array();
715 $profile = filebrowser_profile();
716
717 if ($parts) {
718 $breadcrumb[] = array_pop($parts);
719 while (count($parts)) {
720 if($profile['render'] == 'popup'){
721 $breadcrumb[] = filebrowser_inline_link($parts[count($parts) -1], join("/", $parts));
722 } else {
723 $breadcrumb[] = l($parts[count($parts) -1], filebrowser_url(join("/", $parts)));
724 }
725 array_pop($parts);
726 }
727 $breadcrumb[] = l(t('top'), filebrowser_url(''));
728 } else {
729 $breadcrumb[] = t('Filebrowser');
730 }
731 $breadcrumb[] = l(t('Home'), NULL);
732 drupal_set_breadcrumb(array_reverse($breadcrumb));
733 }
734
735 /**
736 * Return a themed breadcrumb trail.
737 *
738 * Only difference is the divider is a slash.
739 * @see theme_filebrowser_breadcrumb()
740 */
741 function theme_filebrowser_breadcrumb($breadcrumb) {
742 if (!empty($breadcrumb)) {
743 return '<div class="breadcrumb">'. implode(' / ', $breadcrumb) .'</div>';
744 }
745 }
746
747
748
749
750 /**
751 * Display a filebrower window as a pop-up
752 *
753 * The filebrowser_popup path is added to the menu.
754 *
755 * Javascript callbacks.
756 * If this page is called with a $_GET['targetelement']
757 * parameter, the opener page that called this popup will have the value of the
758 * named form element set. If called with a $_GET ['callback'] parameter, the
759 * named function will be called in the opener window, with the selected
760 * filename as a parameter.
761 *
762 *
763 '#type' => 'button',
764 '#value' => 'Browse Server...',
765 '#attributes' => array( "onclick" =>
766 "window.open('"
767 . url("filebrowser_popup/", NULL, NULL, true)
768 . "'+document.getElementById ('edit-choose_file')
769 . value+'?targetelement=edit-choose_file&subsection="
770 . file_directory_path()
771 . "/'
772 ,'filebrowser'
773 ,'width=300, height=400, resizable=yes, scrollbars=yes');
774 return false;",
775 ),
776 );
777 *
778 * The targetelement parameter sent to the popup is the id of the element the
779 * result should be sent to once the user selects something. The subsection
780 * parameter should be trimmed off the front of it if neccessary.
781 *
782 * The subsection to start from should be passed in either as part of the path,
783 * or via $_REQUEST['subsection'].
784 * Authentication for access to these areas is made as usual via the
785 * profiles configs.
786 *
787 */
788
789
790
791 /**
792 * Theme a filebrowser block, as a list or table.
793 */
794 function theme_filebrowser(&$output) {
795 if(module_exist('tweakui')){
796 // UI enhancements - select tree and mouseover rows.
797 tweakui_javascript_functions();
798 tweakui_css();
799 }
800
801 // Add the filebrowser CSS to the page
802 theme_add_style(drupal_get_path('module','filebrowser_extensions') .'/filebrowser-tree.css');
803
804 return '<div class="filebrowser">' . $output . '</div>';
805 }
806
807
808
809 ///////////////////////////////////////////////////////////////////////////////
810
811
812
813 /**
814 * From a flat list of file defs, build a tree structure, and annotate it like
815 * the forms API does.
816 *
817 * The file defs are expected to be supplying complete system paths,
818 * so this must be trimmed back before working with them.
819 * The file['filepath'] values are returned after being made relative to the
820 * $base. (base is removed)
821 *
822 * Incidentally creates a parent trail showing the higher folders if the profile
823 * show_uplinks is true.
824 *
825 * @param $dir_structure A flat array of file details
826 * @base the part of the path that needs to be trimmed off to make the working
827 * path relative again - to whatever
828 * @param $subfolder where, underneath filebrowser_root, we are viewing. It has
829 * no trailing slash
830 */
831 function filebrowser_sort_list_into_tree($dir_structure,$base='',$subfolder=''){
832 $subfolder = trim($subfolder,'/');
833 $base = $base?$base:file_directory_path();
834 $profile = filebrowser_profile();
835
836 $rel_base = trim(substr($dir_structure[0]->filename,strlen($base)),'/');
837 #dsm($dir_structure);
838 $tree = array ();
839 $count = 0;
840 foreach ($dir_structure as $file_path => $file_info) {
841 $filename = $file_info->filename;
842 if(substr($filename,0,strlen($base)) == $base){
843 $rel_path = trim(substr($filename,strlen($base)),'/');
844 }
845 else{
846 drupal_set_message($filename ." not inside $base. This shouldn't be. Illegal paths in config?",'error');
847 }
848
849 if(($profile['hide_directories']) && is_dir($filename)){
850 continue;
851 }
852
853 // rel_path is rel to the file storage dir,
854 // $local_path is the current file relative to the current context
855 // When listing the contents of /profile_root/local_path
856 // you have permission to browse up to /profile_root, but no further
857 // rel_path = subfolder+local_path
858 // filename = storage+relpath = storage+subfolder+local_path
859 $local_path = trim(substr($rel_path,strlen($subfolder)),'/');
860
861 # dsm("rel_path $rel_path minus subfolder $subfolder equals localpath $local_path");
862
863 $ancestors = split("/", $local_path);
864
865 $twig = & $tree; // find the current twig, starting from the top
866 $path = $rel_base;
867
868 // Add parent special case as if it were a child
869 if($file_path == '..'){$ancestors = array('..');}
870
871 while ($dad = array_shift($ancestors)) {
872 // walk down the path
873 if (!array_key_exists($dad, $twig)) {
874 // add new branch
875 $twig['#type'] = 'disk_folder';
876 $twig['#rel_path'] = $path?join_paths($subfolder,$path):'.';
877 $twig['#title'] = $path?basename($path):'top';
878 $twig['#filename'] = join_paths($base,join_paths($subfolder,$path));
879 $twig[$dad] = array();
880 $count++;
881 }
882 $path .= $dad.'/';
883 $twig = & $twig[$dad];
884 }
885 $twig['#type'] = (is_dir($file_path))?'disk_folder':'disk_file';
886 $twig['#title'] = $file_info->basename;
887 $twig['#filename'] = $filename;
888 $twig['#rel_path'] = $rel_path;
889
890 }
891 # dsm($tree);
892 return $tree;
893 }
894
895 /**
896 * Given a tree structure full of file definitions, massage the structure into
897 * nested tables (to be rendered later). Pretty intensive rebuild.
898 *
899 * This is a step between looking at the file structure as an API form, and
900 * using the API form builder to construct tables for me (using the formtable
901 * extension). It treats form elements and table builder elements as similar
902 * things. Just requires a bit of care translating between #properties and form
903 * builder 'data'
904 */
905 function filebrowser_tree_to_tables($tree,$cols = array('icon','name','size') ){
906
907 if(! function_exists('formtable_elements')){
908 drupal_set_message(t("Cannot render the filebrowser files as a table without the formtable extension"),'error');
909 filebrowser_extensions_install();
910 }
911 $table = array(
912 '#type' => 'formtable' ,
913 '#attributes'=>array(
914 'class' => 'filebrowser_table groupItem',
915 'id' => filebrowser_id($tree['#filename']).'_content')
916 );
917 # dsm($tree);
918
919 if($tree['..']){ // special case. Asked to show parent, render it first
920 $table['..'] = array(
921 '#type' => 'tr',
922 'link' => array(
923 '#type' => 'td',
924 '#attributes' => array('colspan'=>'3'),
925 '#value' => l($tree['..']['#title'],filebrowser_url($tree['..']['#rel_path'])),
926 )
927 );
928 unset($tree['..']);
929 }
930
931 static $count; // for odd-even
932
933 foreach(element_children($tree) as $twig_key){
934 $twig = $tree[$twig_key];
935
936 if(!is_array($twig))$twig = array('#filename' => $twig_key);
937 $row = array( '#type' => 'tr' );
938
939 // populate the columns
940 $weight = 0; // keep in order
941 foreach($cols as $colname){
942 $func = 'filebrowser_get_cell_'.$colname;
943 if(function_exists($func)){
944 $cell = $func($twig['#filename'],$twig);
945 // read content out of cell,
946 // need to adjust it slightly to live within form struct, not table struct
947 if(is_array($cell)){
948 $row[$colname]['#value'] = $cell['data'];
949 unset($cell['data']);
950 unset($cell['sv']);
951 $row[$colname]['#attributes'] = $cell;
952 $row[$colname]['#attributes']['class'] .= ' '.$colname;
953 }
954 else {
955 $row[$colname]['#value'] = print_r($cell,1);
956 }
957 $row[$colname]['#type'] = 'td';
958 $row[$colname]['#weight'] = $weight++;
959 }
960 }
961 $row['#attributes']['class'] = ($count++ % 2 == 1) ? 'even': 'odd';
962
963 $table[$twig_key] = $row;
964 if(element_children($twig)){
965 // todo test recursion again
966
967 // It's a container, insert its children in a row/cell below
968 $table[$twig_key]['#attributes']['class'] .= ' container';
969 // This is imperfect nesting, but will have to do.
970 // one day I'll imagine a LI schema for this. I considered tbody - but I can't nest them
971 $row = array(
972 '#type' => 'tr',
973 );
974 $row[] = array('#type' => 'td'); //indent
975 $row[] = array(
976 '#type' => 'td' ,
977 '#attributes'=>array('colspan'=>count($cols)-1,'class'=>'contents'), 'data' => filebrowser_tree_to_tables($twig,$cols)
978 );
979
980 $table[$twig_key.'contents'] = $row;
981 }
982 }
983 #dsm($table);
984 return $table;
985 }
986
987
988 /**
989 * Layout a directory listing using only css, no table.
990 *
991 * Construct the elements mostly as if they were form elements
992 */
993 function filebrowser_tree_to_rows($tree,$cols = array('icon','name','size')){
994 $table = array(
995 '#type' => 'markup' ,
996 '#prefix' => "\n\n<ul class='filebrowser_list' id='".filebrowser_id($tree['#rel_path'])."_content'>" ,
997 '#suffix' => "</ul>\n" ,
998 );
999
1000 if($tree['..']){ // special case. Asked to show parent, render it first
1001 $table['..'] = filebrowser_item_row($tree['..'], array('icon','name') );
1002 unset($tree['..']);
1003 }
1004
1005 foreach(element_children($tree) as $twig_key){
1006 $twig = $tree[$twig_key];
1007 if(!is_array($twig))$twig = array('#filename' => $twig_key);
1008
1009 $row = filebrowser_item_row($twig,$cols);
1010 if(element_children($twig)){
1011 $row['value'] = filebrowser_tree_to_rows($twig,$cols);
1012 // As the columns are ordered, need to ensure the nested content comes after them
1013 $row['value']['#weight'] = count($cols) +1;
1014 }
1015 $table[$twig_key] = $row;
1016 }
1017 return $table;
1018 }
1019
1020 /**
1021 * Structure list item elements representing a files info
1022 *
1023 * This is the central build call-back engine that invokes the cell renderers
1024 */
1025 function filebrowser_item_row($twig,$cols){
1026 static $count; // as well as odd-even, we also need weight for ordering, as form_render seems to sort arbitrarily if I don't define it.'
1027 $count ++;
1028 $class = ($count % 2 == 1) ? 'even': 'odd';
1029 $class .= ($twig['#type']=='disk_folder')?' container':'';
1030
1031 $row = array(
1032 '#type' => 'markup' ,
1033 '#weight' => $count,
1034 '#prefix' => "\n".'<li class="'.$class.'" id="checkbox_'.filebrowser_id($twig['#rel_path']).'">',
1035 '#suffix' => '</li>' ,
1036 );
1037
1038 $row['properties'] = array(
1039 # '#prefix' => "<div class=' properties ' >",
1040 # '#suffix' => "</div> "
1041 '#suffix' => "<br/> "
1042 );
1043
1044 // populate the columns/elements
1045 $o = 1;
1046 foreach($cols as $colname){
1047 $element = array(
1048 '#type' => 'markup' ,
1049 '#weight' => $o++,
1050 );
1051 $func = 'filebrowser_get_cell_'.$colname;
1052
1053 if(function_exists($func)){
1054 // It's a toss-up which is mostly more useful, the absolute or rel path
1055 // Both are available
1056 $cell = $func($twig['#filename'],$twig);
1057
1058 // read content out of cell,
1059 // need to adjust it slightly to live within form struct, not table struct
1060 if(is_array($cell)){
1061 $element['#value'] = $cell['data'];
1062 unset($cell['data']);
1063 $element['#attributes'] = $cell;
1064 }
1065 else {
1066 // just returned a string
1067 $element['#value'] = print_r($cell,1);
1068 }
1069 }
1070 $class = $element['#attributes']['class'].' '.$colname ;
1071 $id = $cell['id'] ? "id='".$cell['id']."'" : "";
1072 $element['#prefix'] = " <span class=' $class ' $id >" ;
1073 $element['#suffix'] = "</span> " ;
1074 $row['properties'][$colname] = $element;
1075 }
1076 return $row;
1077 }
1078
1079
1080
1081 ///////////////////////////////////////////////////////////////////////////////
1082 // Autocomplete
1083
1084 /**
1085 * Helper function for autocompletion
1086 * Returns files or directories matching the given pattern
1087 */
1088 function filebrowser_autocomplete() {
1089 $parts = func_get_args();
1090 return filebrowser_do_autocomplete($parts);
1091 }
1092 /**
1093 * Helper function for autocompletion
1094 * Returns directories matching the given pattern
1095 */
1096 function filebrowser_autocomplete_directory() {
1097 $parts = func_get_args();
1098 return filebrowser_do_autocomplete($parts,GLOB_ONLYDIR);
1099 }
1100
1101 /**
1102 * Return the JS response listing the contents of the requested directory.
1103 * Security should have happened before now.
1104 * Uses glob patterns configured in the profile.
1105 */
1106 function filebrowser_do_autocomplete($parts='',$globflag=0) {
1107 $context = array_shift($parts);
1108
1109 $path = join('/',$parts);
1110 $profile = filebrowser_profile($context);
1111
1112 // if(!$profile['root'])$profile['root']='.';
1113 $completepath = join_paths($profile['root'],$path);
1114 if($profile['serve_as_drupal_files']){
1115 $completepath=file_create_path($completepath?$completepath:'.');
1116 $chop = file_create_path($profile['root']);
1117 }
1118
1119 if(is_dir($completepath) ){
1120 $folder = $completepath;
1121 $partial = '';
1122 }
1123 else {
1124 $partial = basename($completepath);
1125 $folder = dirname($completepath);
1126 }
1127 if(!$folder){$folder='.';}
1128 $matches = array();
1129 if (!(is_dir($folder) && ($dir = opendir($folder)))) {
1130 $matches = array("error - $folder");
1131 } else {
1132 foreach (glob("$folder/$partial*" , $globflag) as $filepath) {
1133 // glob returns the full path, chop the files dir off it again
1134 if(empty($chop))$path = $filepath;
1135 else $path = substr($filepath, strlen($chop)+1);
1136 $matches[$path] = $path;
1137 }
1138 }
1139
1140 if(!$matches){
1141 $matches = array(''=>t( "No %types matching '%partial' in '%folder' glob('%glob')" , array('%types' => ($globflag?'directories':'files'), '%partial' => $partial, '%folder' => $folder, '%glob' => "$folder/$partial*" )));
1142 }
1143 print drupal_to_js($matches);
1144 exit();
1145 }
1146
1147 /////////////////////////////////
1148
1149
1150 /**
1151 * Safely concatenate two paths ensuring they have just 1 slash between, if
1152 * needed. No leading or trailing slashes
1153 */
1154 function join_paths($a,$b){
1155 return trim(join('/',array(trim($a,'/'),trim($b,'/'))),'/');
1156 }
1157
1158 /**
1159 * Returns a CSS-safe ID generated from the given string
1160 */
1161 function filebrowser_id($path){
1162 return "ID_" . preg_replace("/[^\w]/", "", $path);
1163 }
1164
1165 /**
1166 * Ensure dependancies exist.
1167 * Attempt to bootstrap it if not.
1168 */
1169 function filebrowser_extensions_install(){
1170 if(! module_exist('formtable')){
1171 db_query("UPDATE {system} SET status = 1 WHERE type = 'module' AND name = '%s'", 'formtable');
1172 $contrib = dirname(__FILE__).'/contrib/';
1173 if(file_exists($contrib.'formtable.dist') && is_writable($contrib) ){
1174 copy($contrib.'formtable.dist',$contrib.'formtable.module');
1175 db_query("UPDATE {system} SET status = 1 WHERE type = 'module' AND name = '%s'", 'formtable');
1176 module_invoke('formtable', 'install');
1177 }
1178 else {
1179 drupal_set_message("Some formatting options for filebrowser will be unavailable, as the formtable.module layout library is not present. Could not even create it in $contrib. See the README",'error');
1180 }
1181 }
1182 }

  ViewVC Help
Powered by ViewVC 1.1.2