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