| 1 |
<?php
|
| 2 |
// $Id: fileserver.module,v 1.3 2008/09/03 19:26:14 incanus Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file
|
| 6 |
* Provides WebDAV access to taxonomies and files.
|
| 7 |
*/
|
| 8 |
|
| 9 |
//////////////////////////////////////////////////////////////////////////////
|
| 10 |
// MODULE SETTINGS
|
| 11 |
|
| 12 |
define('FILESERVER_ADMIN_PATH', 'admin/settings/dav/fileserver');
|
| 13 |
|
| 14 |
define('FILESERVER_OG_VOCAB', variable_get('fileserver_og_vocab', FALSE));
|
| 15 |
|
| 16 |
define('FILESERVER_VOCABULARY', 'taxonomy_vocabulary');
|
| 17 |
define('FILESERVER_CATEGORY', 'taxonomy_term');
|
| 18 |
define('FILESERVER_FILE', 'node_file');
|
| 19 |
|
| 20 |
define('FILESERVER_NODE_TYPE', 'file');
|
| 21 |
|
| 22 |
define('FILESERVER_SIG', ' (WebDAV)');
|
| 23 |
|
| 24 |
//////////////////////////////////////////////////////////////////////////////
|
| 25 |
// CORE API HOOKS
|
| 26 |
|
| 27 |
/**
|
| 28 |
* Implementation of hook_help().
|
| 29 |
*/
|
| 30 |
function fileserver_help($path, $arg) {
|
| 31 |
switch ($path) {
|
| 32 |
case 'admin/help#fileserver':
|
| 33 |
return '<p>' . t('File taxonomy server provides WebDAV access to a site\'s taxonomies and files. You can add, remove, move, copy, and edit vocabularies, taxonomy terms, and their associated files using an ordinary WebDAV client.') . '</p>';
|
| 34 |
}
|
| 35 |
}
|
| 36 |
|
| 37 |
/**
|
| 38 |
* Implementation of hook_menu().
|
| 39 |
*/
|
| 40 |
function fileserver_menu() {
|
| 41 |
return array(
|
| 42 |
FILESERVER_ADMIN_PATH => array(
|
| 43 |
'title' => 'File taxonomy server',
|
| 44 |
'description' => 'Configure File taxonomy server settings.',
|
| 45 |
'access arguments' => array('administer site configuration'),
|
| 46 |
'page callback' => 'drupal_get_form',
|
| 47 |
'page arguments' => array('fileserver_admin_settings_form'),
|
| 48 |
)
|
| 49 |
);
|
| 50 |
}
|
| 51 |
|
| 52 |
/**
|
| 53 |
* Admin settings form, called by hook_menu().
|
| 54 |
*/
|
| 55 |
function fileserver_admin_settings_form() {
|
| 56 |
$form = array();
|
| 57 |
|
| 58 |
// Vocabulary settings
|
| 59 |
$form['vocabs'] = array('#type' => 'fieldset', '#title' => t('Vocabulary settings'), '#collapsible' => TRUE, '#collapsed' => TRUE);
|
| 60 |
$options = array_map(create_function('$vocab', 'return $vocab->name;'), taxonomy_get_vocabularies());
|
| 61 |
$form['vocabs']['fileserver_vocabularies'] = array(
|
| 62 |
'#type' => 'select',
|
| 63 |
'#title' => t('Exported vocabularies'),
|
| 64 |
'#default_value' => array_filter(variable_get('fileserver_vocabularies', array()), 'is_string'),
|
| 65 |
'#options' => $options,
|
| 66 |
'#multiple' => TRUE,
|
| 67 |
'#description' => t('Select which vocabularies will be made accessible via DAV as top-level collections (or "root folders"). Access to any other vocabularies will be prohibited.'),
|
| 68 |
);
|
| 69 |
|
| 70 |
// Organic groups integration
|
| 71 |
if (module_exists('og_vocab')) {
|
| 72 |
$form['og'] = array('#type' => 'fieldset', '#title' => t('Organic groups integration'), '#collapsible' => TRUE, '#collapsed' => TRUE);
|
| 73 |
$form['og']['fileserver_og_vocab'] = array(
|
| 74 |
'#type' => 'radios',
|
| 75 |
'#title' => t('Only show vocabularies associated with the user\'s groups'),
|
| 76 |
'#default_value' => FILESERVER_OG_VOCAB,
|
| 77 |
'#options' => array(FALSE => t('Disabled'), TRUE => t('Enabled')),
|
| 78 |
'#description' => t('Whether to enable access to the group vocabularies of those organic groups the user is subscribed to. Note that the exported vocabularies setting, above, has higher priority than this, meaning that the user is always granted access to any explicitly exported vocabularies.</em>'),
|
| 79 |
);
|
| 80 |
}
|
| 81 |
|
| 82 |
return system_settings_form($form);
|
| 83 |
}
|
| 84 |
|
| 85 |
//////////////////////////////////////////////////////////////////////////////
|
| 86 |
// DAV API HOOKS (NAMESPACE/METADATA)
|
| 87 |
|
| 88 |
/**
|
| 89 |
* Implementation of hook_dav_lookup().
|
| 90 |
*/
|
| 91 |
function fileserver_dav_lookup($collection, $name) {
|
| 92 |
list($type, $key) = $collection;
|
| 93 |
|
| 94 |
switch ($type) {
|
| 95 |
case DAV_ROOT_COLLECTION:
|
| 96 |
if ($vocabulary = _fileserver_lookup_vocabulary($name))
|
| 97 |
return array(FILESERVER_VOCABULARY, $vocabulary->vid);
|
| 98 |
break;
|
| 99 |
|
| 100 |
case FILESERVER_VOCABULARY:
|
| 101 |
if ($term = _fileserver_lookup_term($name, $key))
|
| 102 |
return array(FILESERVER_CATEGORY, $term->tid);
|
| 103 |
break;
|
| 104 |
|
| 105 |
case FILESERVER_CATEGORY:
|
| 106 |
//if ($term = _fileserver_lookup_term($name, $parent->vid, $parent->tid))
|
| 107 |
if ($term = _fileserver_lookup_term($name, NULL, $key))
|
| 108 |
return array(FILESERVER_CATEGORY, $term->tid);
|
| 109 |
if ($node = _fileserver_lookup_file($name, $key))
|
| 110 |
return array(FILESERVER_FILE, $node->nid);
|
| 111 |
break;
|
| 112 |
|
| 113 |
case FILESERVER_FILE:
|
| 114 |
break;
|
| 115 |
}
|
| 116 |
}
|
| 117 |
|
| 118 |
/**
|
| 119 |
* Implementation of hook_dav_list().
|
| 120 |
*/
|
| 121 |
function fileserver_dav_list($collection) {
|
| 122 |
list($type, $key) = $collection;
|
| 123 |
$resources = array();
|
| 124 |
|
| 125 |
switch ($type) {
|
| 126 |
case DAV_ROOT_COLLECTION:
|
| 127 |
// top level: list vocabularies
|
| 128 |
foreach (taxonomy_get_vocabularies(FILESERVER_NODE_TYPE) as $vocabulary) {
|
| 129 |
if (_fileserver_include_vocabulary($vocabulary->vid)) {
|
| 130 |
$resources[$vocabulary->name] = array(FILESERVER_VOCABULARY, $vocabulary->vid);
|
| 131 |
}
|
| 132 |
}
|
| 133 |
break;
|
| 134 |
|
| 135 |
case FILESERVER_VOCABULARY:
|
| 136 |
// one level deep: list categories under this vocabulary
|
| 137 |
if (_fileserver_include_vocabulary($key)) {
|
| 138 |
foreach (taxonomy_get_children(NULL, $key) as $term) {
|
| 139 |
$resources[$term->name] = array(FILESERVER_CATEGORY, $term->tid);
|
| 140 |
}
|
| 141 |
}
|
| 142 |
break;
|
| 143 |
|
| 144 |
case FILESERVER_CATEGORY:
|
| 145 |
// two or more levels deep: list categories and files under this category
|
| 146 |
foreach (taxonomy_get_children($key) as $term) {
|
| 147 |
$resources[$term->name] = array(FILESERVER_CATEGORY, $term->tid);
|
| 148 |
}
|
| 149 |
$result = taxonomy_select_nodes(array((int)$key), 'or', 0, FALSE);
|
| 150 |
while ($node = db_fetch_object($result)) {
|
| 151 |
$node = node_load($node->nid);
|
| 152 |
if ($node->type == FILESERVER_NODE_TYPE && _fileserver_node_access('view', $node) /*&& !empty($node->file)*/) { // FIXME
|
| 153 |
$resources[$node->title] = array(FILESERVER_FILE, $node->nid);
|
| 154 |
}
|
| 155 |
}
|
| 156 |
break;
|
| 157 |
|
| 158 |
case FILESERVER_FILE:
|
| 159 |
break;
|
| 160 |
}
|
| 161 |
|
| 162 |
return $resources;
|
| 163 |
}
|
| 164 |
|
| 165 |
/**
|
| 166 |
* Implementation of hook_dav_propfind().
|
| 167 |
*/
|
| 168 |
function fileserver_dav_propfind($resource) {
|
| 169 |
list($type, $key) = $resource;
|
| 170 |
$props = array();
|
| 171 |
|
| 172 |
switch ($type) {
|
| 173 |
// As DAV collections, we support taxonomy.module vocabularies and terms.
|
| 174 |
case FILESERVER_VOCABULARY:
|
| 175 |
case FILESERVER_CATEGORY:
|
| 176 |
// Note that vocabulary terms don't store any creation/modification
|
| 177 |
// date information in the database, so we have to use the current
|
| 178 |
// time by default. One way to fix this would be to add additional
|
| 179 |
// columns to the core Drupal tables, and watch on hook_taxonomy() in
|
| 180 |
// order to update their values. Nasty, but useful; this could be
|
| 181 |
// provided as a separate add-on since we check whether the term has
|
| 182 |
// 'created' and 'changed' attributes.
|
| 183 |
$term = ($type == FILESERVER_VOCABULARY) ? taxonomy_vocabulary_load($key) : taxonomy_get_term($key);
|
| 184 |
$props['displayname'] = $term->name;
|
| 185 |
$props['creationdate'] = !empty($term->created) ? $term->created : time();
|
| 186 |
$props['getlastmodified'] = !empty($term->changed) ? $term->changed : time();
|
| 187 |
$props['resourcetype'] = 'collection';
|
| 188 |
$props['getcontenttype'] = 'httpd/unix-directory';
|
| 189 |
break;
|
| 190 |
|
| 191 |
// As DAV files, we support file.module nodes.
|
| 192 |
case FILESERVER_FILE:
|
| 193 |
if (($node = node_load($key)) && ($file = $node->file) && _fileserver_node_access('view', $node)) {
|
| 194 |
$props['displayname'] = $node->title; // TODO: give suffix automatically?
|
| 195 |
$props['creationdate'] = $node->created;
|
| 196 |
$props['getlastmodified'] = $node->changed;
|
| 197 |
$props['resourcetype'] = '';
|
| 198 |
$props['getcontenttype'] = $file->type;
|
| 199 |
$props['getcontentlength'] = (int)$file->size;
|
| 200 |
}
|
| 201 |
break;
|
| 202 |
}
|
| 203 |
|
| 204 |
return $props;
|
| 205 |
}
|
| 206 |
|
| 207 |
//////////////////////////////////////////////////////////////////////////////
|
| 208 |
// DAV API HOOKS (VERBS)
|
| 209 |
|
| 210 |
/**
|
| 211 |
* Implementation of hook_dav_get().
|
| 212 |
*/
|
| 213 |
function fileserver_dav_get($resource, &$options) {
|
| 214 |
list($type, $key) = $resource;
|
| 215 |
|
| 216 |
switch ($type) {
|
| 217 |
case FILESERVER_FILE:
|
| 218 |
$node = node_load($key);
|
| 219 |
$file = $node->file;
|
| 220 |
$hash = file_get_hash($file);
|
| 221 |
if (_fileserver_node_access('view', $node) && file_bitcache_access($hash, $node->vid)) {
|
| 222 |
return array(
|
| 223 |
'mtime' => $node->changed,
|
| 224 |
'mimetype' => $file->type,
|
| 225 |
'size' => (int)$file->size,
|
| 226 |
'stream' => bitcache_get_stream($hash),
|
| 227 |
);
|
| 228 |
}
|
| 229 |
return FALSE;
|
| 230 |
}
|
| 231 |
}
|
| 232 |
|
| 233 |
/**
|
| 234 |
* Implementation of hook_dav_put().
|
| 235 |
*/
|
| 236 |
function fileserver_dav_put($container, $name, &$options, $filepath = NULL) {
|
| 237 |
list($type, $key) = $container;
|
| 238 |
|
| 239 |
switch ($type) {
|
| 240 |
case DAV_ROOT_COLLECTION:
|
| 241 |
return FALSE; // TODO: maybe we could provide some smarts here?
|
| 242 |
|
| 243 |
case FILESERVER_VOCABULARY:
|
| 244 |
return FALSE; // decline file upload to the vocabulary root
|
| 245 |
|
| 246 |
case FILESERVER_CATEGORY:
|
| 247 |
if (empty($filepath)) { // pre-process upload FIXME what does this mean?
|
| 248 |
return TRUE; // accept file upload
|
| 249 |
}
|
| 250 |
else { // post-process upload
|
| 251 |
$handler = $options['new'] ? '_fileserver_create_file' : '_fileserver_update_file';
|
| 252 |
return $handler($key, $name, $options, $filepath);
|
| 253 |
}
|
| 254 |
}
|
| 255 |
}
|
| 256 |
|
| 257 |
/**
|
| 258 |
* Implementation of hook_dav_mkcol().
|
| 259 |
*/
|
| 260 |
function fileserver_dav_mkcol($container, $name) {
|
| 261 |
list($type, $key) = $container;
|
| 262 |
|
| 263 |
switch ($type) {
|
| 264 |
case DAV_ROOT_COLLECTION:
|
| 265 |
if (!user_access('administer taxonomy'))
|
| 266 |
return STATUS_403; // Forbidden
|
| 267 |
|
| 268 |
if ($vocabulary = _fileserver_create_vocabulary($name)) {
|
| 269 |
// Immediately add the new vocabulary to the exported list, or else
|
| 270 |
// the user would never see it until an administrator were to
|
| 271 |
// manually adjust the module's settings:
|
| 272 |
_fileserver_export_vocabulary($vocabulary->vid);
|
| 273 |
return array(FILESERVER_VOCABULARY, $vocabulary->vid);
|
| 274 |
}
|
| 275 |
return FALSE;
|
| 276 |
|
| 277 |
case FILESERVER_VOCABULARY:
|
| 278 |
case FILESERVER_CATEGORY:
|
| 279 |
if (!user_access('administer taxonomy'))
|
| 280 |
return STATUS_403; // Forbidden
|
| 281 |
|
| 282 |
$parent = ($type == FILESERVER_VOCABULARY) ? taxonomy_vocabulary_load($key) : taxonomy_get_term($key);
|
| 283 |
if ($term = _fileserver_create_term($name, $parent->vid, @$parent->tid))
|
| 284 |
return array(FILESERVER_CATEGORY, $term->tid);
|
| 285 |
return FALSE;
|
| 286 |
}
|
| 287 |
}
|
| 288 |
|
| 289 |
/**
|
| 290 |
* Implementation of hook_dav_delete().
|
| 291 |
*/
|
| 292 |
function fileserver_dav_delete($resource, $container, $move = FALSE) {
|
| 293 |
list($type, $key) = $resource;
|
| 294 |
|
| 295 |
switch ($type) {
|
| 296 |
case FILESERVER_VOCABULARY:
|
| 297 |
// NB: We need to be careful about the deletion of vocabularies...
|
| 298 |
if (!user_access('administer taxonomy'))
|
| 299 |
return STATUS_403; // Forbidden
|
| 300 |
|
| 301 |
return taxonomy_del_vocabulary($key);
|
| 302 |
|
| 303 |
case FILESERVER_CATEGORY:
|
| 304 |
if (!user_access('administer taxonomy'))
|
| 305 |
return STATUS_403; // Forbidden
|
| 306 |
|
| 307 |
return taxonomy_del_term($key);
|
| 308 |
|
| 309 |
case FILESERVER_FILE:
|
| 310 |
$node = node_load($key);
|
| 311 |
if (!_fileserver_node_access('update', $node) && !$move)
|
| 312 |
return STATUS_403; // Forbidden
|
| 313 |
|
| 314 |
list($parent_type, $parent_key) = $container;
|
| 315 |
switch ($parent_type) {
|
| 316 |
case FILESERVER_CATEGORY:
|
| 317 |
$node = node_load($key);
|
| 318 |
if (!$move) {
|
| 319 |
// revision the about-to-be untagged node
|
| 320 |
_fileserver_revision_node($node, 'delete', array(taxonomy_get_term($parent_key)->name));
|
| 321 |
}
|
| 322 |
else {
|
| 323 |
// reload the (uncached) node (already revisioned in fileserver_dav_move())
|
| 324 |
$node = node_load($key, NULL, TRUE);
|
| 325 |
}
|
| 326 |
// only modify the latest revision
|
| 327 |
db_query('DELETE FROM {term_node} WHERE tid = %d AND nid = %d AND vid = %d', $parent_key, $key, $node->vid);
|
| 328 |
// TODO: really delete when reference count reaches zero?
|
| 329 |
// This would require _fileserver_node_access('delete', $node)
|
| 330 |
//node_delete($key);
|
| 331 |
return TRUE;
|
| 332 |
}
|
| 333 |
return NULL;
|
| 334 |
}
|
| 335 |
}
|
| 336 |
|
| 337 |
/**
|
| 338 |
* Implementation of hook_dav_rename().
|
| 339 |
*/
|
| 340 |
function fileserver_dav_rename($resource, $source_name, $target_name) {
|
| 341 |
list($type, $key) = $resource;
|
| 342 |
|
| 343 |
switch ($type) {
|
| 344 |
case FILESERVER_VOCABULARY:
|
| 345 |
if (user_access('administer taxonomy')) {
|
| 346 |
$edit = (array)taxonomy_vocabulary_load($key);
|
| 347 |
$edit['name'] = $target_name;
|
| 348 |
db_query("UPDATE {vocabulary} SET name = '%s' WHERE vid = %d", $edit['name'], $edit['vid']);
|
| 349 |
//module_invoke_all('taxonomy', 'update', 'vocabulary', $edit);
|
| 350 |
return TRUE;
|
| 351 |
}
|
| 352 |
|
| 353 |
case FILESERVER_CATEGORY:
|
| 354 |
if (user_access('administer taxonomy')) {
|
| 355 |
$edit = (array)taxonomy_get_term($key);
|
| 356 |
$edit['name'] = $target_name;
|
| 357 |
db_query("UPDATE {term_data} SET name = '%s' WHERE tid = %d", $edit['name'], $edit['tid']);
|
| 358 |
//module_invoke_all('taxonomy', 'update', 'term', $edit);
|
| 359 |
return TRUE;
|
| 360 |
}
|
| 361 |
|
| 362 |
case FILESERVER_FILE:
|
| 363 |
$node = node_load($key);
|
| 364 |
if (_fileserver_node_access('update', $node) &&
|
| 365 |
_fileserver_revision_node($node, 'rename', array($node->title, $target_name))) {
|
| 366 |
db_query("UPDATE {node} SET title = '%s' WHERE nid = %d", $target_name, $node->nid);
|
| 367 |
db_query("UPDATE {node_revisions} SET title = '%s' WHERE vid = %d", $target_name, $node->vid);
|
| 368 |
//node_invoke($node, 'update');
|
| 369 |
//node_invoke_nodeapi($node, 'update');
|
| 370 |
return TRUE;
|
| 371 |
}
|
| 372 |
}
|
| 373 |
|
| 374 |
return FALSE; // catch-all
|
| 375 |
}
|
| 376 |
|
| 377 |
/**
|
| 378 |
* Implementation of hook_dav_move().
|
| 379 |
*/
|
| 380 |
function fileserver_dav_move($resource, $source_path, $target_path) {
|
| 381 |
list($type, $key) = $resource;
|
| 382 |
|
| 383 |
switch ($type) {
|
| 384 |
case FILESERVER_VOCABULARY:
|
| 385 |
// Vocabularies can't be moved (only created, renamed and deleted)
|
| 386 |
return FALSE;
|
| 387 |
|
| 388 |
case FILESERVER_CATEGORY:
|
| 389 |
if (user_access('administer taxonomy')) {
|
| 390 |
$edit = (array)taxonomy_get_term($key);
|
| 391 |
$edit['name'] = $name = array_pop($target_path);
|
| 392 |
list($type, $parent) = _dav_resolve($target_path);
|
| 393 |
switch ($type) {
|
| 394 |
// Convert/promote a category into a vocabulary, reparenting all its
|
| 395 |
// sub-categories to the newly-created vocabulary
|
| 396 |
case DAV_ROOT_COLLECTION:
|
| 397 |
if ($vocabulary = _fileserver_create_vocabulary($name)) {
|
| 398 |
_fileserver_reparent_tree($edit['vid'], $edit['tid'], $vocabulary->vid, 0);
|
| 399 |
taxonomy_del_term($edit['tid']);
|
| 400 |
_fileserver_export_vocabulary($vocabulary->vid);
|
| 401 |
return TRUE; //array(FILESERVER_VOCABULARY, $vocabulary->vid);
|
| 402 |
}
|
| 403 |
return FALSE;
|
| 404 |
|
| 405 |
// Move a category to another vocabulary
|
| 406 |
case FILESERVER_VOCABULARY:
|
| 407 |
_fileserver_reparent_tree($edit['vid'], $edit['tid'], $parent, $edit['tid']);
|
| 408 |
$edit['vid'] = $parent;
|
| 409 |
db_query("UPDATE {term_data} SET name = '%s', vid = %d WHERE tid = %d", $edit['name'], $edit['vid'], $edit['tid']);
|
| 410 |
_fileserver_reparent_category($edit['tid'], 0);
|
| 411 |
module_invoke_all('taxonomy', 'update', 'term', $edit);
|
| 412 |
return TRUE;
|
| 413 |
|
| 414 |
// Move a category to another parent category within the same vocabulary
|
| 415 |
case FILESERVER_CATEGORY:
|
| 416 |
// TODO: Check that the vocabulary IDs actually match?
|
| 417 |
// Prevent moving a term inside one of its own subterms, which
|
| 418 |
// would unlink that entire branch due to a circular reference
|
| 419 |
$tids = array_map('array_shift', array_map('get_object_vars', taxonomy_get_parents_all($parent)));
|
| 420 |
if (in_array($edit['tid'], $tids))
|
| 421 |
return FALSE; // Conflict
|
| 422 |
$edit['parent'] = $parent;
|
| 423 |
taxonomy_save_term($edit);
|
| 424 |
return TRUE;
|
| 425 |
}
|
| 426 |
return NULL; // we don't know how to handle the given parent type
|
| 427 |
}
|
| 428 |
|
| 429 |
case FILESERVER_FILE:
|
| 430 |
$source_name = array_pop($source_path);
|
| 431 |
list($source_type, $source) = _dav_resolve($source_path);
|
| 432 |
$target_name = array_pop($target_path);
|
| 433 |
list($target_type, $target) = _dav_resolve($target_path);
|
| 434 |
|
| 435 |
switch ($target_type) {
|
| 436 |
case DAV_ROOT_COLLECTION:
|
| 437 |
case FILESERVER_VOCABULARY:
|
| 438 |
return FALSE; // No can do, sorry.
|
| 439 |
|
| 440 |
case FILESERVER_CATEGORY:
|
| 441 |
// NB: we need to insert the new relation before deleting the
|
| 442 |
// existing association to prevent possible node deletion if its
|
| 443 |
// reference count reaches zero. The good ol' retain/release
|
| 444 |
// dance, Web 2.0 version.
|
| 445 |
$node = node_load($key);
|
| 446 |
if (_fileserver_node_access('update', $node) &&
|
| 447 |
_fileserver_revision_node($node, 'move', array(taxonomy_get_term($source)->name, taxonomy_get_term($target)->name))) {
|
| 448 |
// associate with the latest node revision
|
| 449 |
db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $key, $node->vid, $target);
|
| 450 |
return fileserver_dav_delete($resource, array($source_type, $source), TRUE);
|
| 451 |
}
|
| 452 |
else {
|
| 453 |
return FALSE;
|
| 454 |
}
|
| 455 |
}
|
| 456 |
return NULL; // we don't know how to handle the given parent type
|
| 457 |
}
|
| 458 |
|
| 459 |
return FALSE; // catch-all
|
| 460 |
}
|
| 461 |
|
| 462 |
/**
|
| 463 |
* Implementation of hook_dav_copy().
|
| 464 |
*/
|
| 465 |
function fileserver_dav_copy($resource, $source_path, $target_path) {
|
| 466 |
list($type, $key) = $resource;
|
| 467 |
|
| 468 |
switch ($type) {
|
| 469 |
case FILESERVER_VOCABULARY:
|
| 470 |
// Vocabularies can't be copied (only created, renamed and deleted)
|
| 471 |
return FALSE;
|
| 472 |
|
| 473 |
case FILESERVER_CATEGORY:
|
| 474 |
// FIXME: We'll prevent the copying of categories until the exact semantics are sorted out
|
| 475 |
return FALSE;
|
| 476 |
|
| 477 |
$edit = (array)taxonomy_get_term($key);
|
| 478 |
unset($edit['tid']);
|
| 479 |
$edit['name'] = $name = array_pop($target_path);
|
| 480 |
list($type, $parent) = _dav_resolve($target_path);
|
| 481 |
switch ($type) {
|
| 482 |
|
| 483 |
case DAV_ROOT_COLLECTION:
|
| 484 |
// TODO: convert/promote the taxonomy term into a vocabulary
|
| 485 |
return FALSE;
|
| 486 |
|
| 487 |
case FILESERVER_VOCABULARY:
|
| 488 |
// Reparent a taxonomy term to a vocabulary
|
| 489 |
$edit['vid'] = $parent;
|
| 490 |
$edit['parent'] = 0;
|
| 491 |
if (taxonomy_save_term($edit) == SAVED_NEW)
|
| 492 |
return array(FILESERVER_CATEGORY, (int)$edit['tid']);
|
| 493 |
return FALSE;
|
| 494 |
|
| 495 |
case FILESERVER_CATEGORY:
|
| 496 |
// Reparent a taxonomy term to another term
|
| 497 |
$edit['parent'] = $parent;
|
| 498 |
taxonomy_save_term($edit);
|
| 499 |
return TRUE;
|
| 500 |
}
|
| 501 |
return NULL; // we don't know how to handle the given parent type
|
| 502 |
|
| 503 |
case FILESERVER_FILE:
|
| 504 |
$source_name = array_pop($source_path);
|
| 505 |
list($source_type, $source) = _dav_resolve($source_path);
|
| 506 |
$target_name = array_pop($target_path);
|
| 507 |
list($target_type, $target) = _dav_resolve($target_path);
|
| 508 |
|
| 509 |
switch ($target_type) {
|
| 510 |
case DAV_ROOT_COLLECTION:
|
| 511 |
case FILESERVER_VOCABULARY:
|
| 512 |
return FALSE; // No can do
|
| 513 |
|
| 514 |
case FILESERVER_CATEGORY:
|
| 515 |
// Create a "shallow copy" by simply associating the node with the target category
|
| 516 |
$node = node_load($key);
|
| 517 |
if (_fileserver_node_access('update', $node) &&
|
| 518 |
_fileserver_revision_node($node, 'copy', array(taxonomy_get_term($target)->name))) {
|
| 519 |
// associate with the current node revision
|
| 520 |
db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $key, $node->vid, $target);
|
| 521 |
return TRUE;
|
| 522 |
}
|
| 523 |
else {
|
| 524 |
return FALSE;
|
| 525 |
}
|
| 526 |
}
|
| 527 |
return NULL; // we don't know how to handle the given parent type
|
| 528 |
}
|
| 529 |
}
|
| 530 |
|
| 531 |
//////////////////////////////////////////////////////////////////////////////
|
| 532 |
// HELPERS
|
| 533 |
|
| 534 |
function _fileserver_create_vocabulary($name) {
|
| 535 |
$edit = array('name' => $name, 'description' => '', 'help' => '', 'multiple' => 0, 'required' => 0, 'hierarchy' => 0, 'relations' => 0, 'tags' => 0, 'weight' => 0);
|
| 536 |
$edit['nodes'] = array('file' => 'file');
|
| 537 |
return (taxonomy_save_vocabulary($edit) == SAVED_NEW) ?
|
| 538 |
taxonomy_vocabulary_load($edit['vid']) : FALSE;
|
| 539 |
}
|
| 540 |
|
| 541 |
function _fileserver_create_term($name, $vid, $parent = NULL) {
|
| 542 |
$edit = array('vid' => $vid, 'parent' => $parent, 'name' => $name);
|
| 543 |
return (taxonomy_save_term($edit) == SAVED_NEW) ?
|
| 544 |
taxonomy_get_term($edit['tid']) : FALSE;
|
| 545 |
}
|
| 546 |
|
| 547 |
function _fileserver_reparent_category($tid, $parent = 0) {
|
| 548 |
db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $tid);
|
| 549 |
db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $tid, $parent);
|
| 550 |
}
|
| 551 |
|
| 552 |
function _fileserver_reparent_tree($old_vid, $old_parent, $new_vid, $new_parent = 0) {
|
| 553 |
// TODO: call taxonomy hooks?
|
| 554 |
|
| 555 |
$tids = array();
|
| 556 |
foreach (taxonomy_get_tree($old_vid, $old_parent) as $term) {
|
| 557 |
$tids[] = $term->tid;
|
| 558 |
if (empty($term->depth) && $old_parent != $new_parent) {
|
| 559 |
_fileserver_reparent_category($term->tid, $new_parent);
|
| 560 |
}
|
| 561 |
}
|
| 562 |
|
| 563 |
if (!empty($tids)) {
|
| 564 |
$args = array_merge(array($new_vid), $tids);
|
| 565 |
db_query('UPDATE {term_data} SET vid = %d WHERE tid IN (' . db_placeholders($tids) . ')', $args);
|
| 566 |
}
|
| 567 |
return TRUE;
|
| 568 |
}
|
| 569 |
|
| 570 |
function _fileserver_include_vocabulary($vid) {
|
| 571 |
static $vocabs = NULL;
|
| 572 |
if (is_null($vocabs)) {
|
| 573 |
$vocabs = array_filter(variable_get('fileserver_vocabularies', array()), 'is_string');
|
| 574 |
}
|
| 575 |
|
| 576 |
// Filter by administrator-selected vocabularies
|
| 577 |
if (is_array($vocabs) && array_key_exists($vid, $vocabs)) {
|
| 578 |
return TRUE;
|
| 579 |
}
|
| 580 |
|
| 581 |
// Filter by OG vocabularies, if enabled by administrator
|
| 582 |
if (FILESERVER_OG_VOCAB && module_exists('og_vocab')) {
|
| 583 |
global $user;
|
| 584 |
// NOTE: no need to cache this since og_get_subscriptions() memorizes the result.
|
| 585 |
$groups = og_get_subscriptions($user->uid);
|
| 586 |
if (count($groups)) {
|
| 587 |
foreach ($groups as $group) {
|
| 588 |
$nids[] = (int)$group['nid'];
|
| 589 |
}
|
| 590 |
$args = array_merge(array($vid), $nids);
|
| 591 |
$count = db_result(db_query('SELECT COUNT(nid) FROM {og_vocab} WHERE vid = %d AND nid IN (' . db_placeholders($nids) . ')', $args));
|
| 592 |
return !empty($count);
|
| 593 |
}
|
| 594 |
}
|
| 595 |
|
| 596 |
return FALSE;
|
| 597 |
}
|
| 598 |
|
| 599 |
function _fileserver_export_vocabulary($vid) {
|
| 600 |
$vocabs = array_filter(variable_get('fileserver_vocabularies', array()), 'is_string');
|
| 601 |
$vocabs[$vid] = $vid;
|
| 602 |
variable_set('fileserver_vocabularies', $vocabs);
|
| 603 |
}
|
| 604 |
|
| 605 |
function _fileserver_lookup_vocabulary($name) {
|
| 606 |
foreach (taxonomy_get_vocabularies(FILESERVER_NODE_TYPE) as $vocabulary) {
|
| 607 |
if ($vocabulary->name == $name && _fileserver_include_vocabulary($vocabulary->vid)) {
|
| 608 |
return $vocabulary;
|
| 609 |
}
|
| 610 |
}
|
| 611 |
return NULL;
|
| 612 |
}
|
| 613 |
|
| 614 |
function _fileserver_lookup_term($name, $vid, $tid = NULL) {
|
| 615 |
$terms = !$tid ?
|
| 616 |
taxonomy_get_children(NULL, $vid, 'name') :
|
| 617 |
taxonomy_get_children($tid, NULL, 'name');
|
| 618 |
return array_key_exists($name, $terms) ? $terms[$name] : NULL;
|
| 619 |
}
|
| 620 |
|
| 621 |
function _fileserver_lookup_file($name, $tid) {
|
| 622 |
// TODO: this could be significantly optimized yet...
|
| 623 |
$result = taxonomy_select_nodes(array((int)$tid), 'or', 0, FALSE);
|
| 624 |
while ($node = db_fetch_object($result)) {
|
| 625 |
$node = node_load($node->nid);
|
| 626 |
if ($node->type == FILESERVER_NODE_TYPE && _fileserver_node_access('view', $node) && $node->title == $name) {
|
| 627 |
return $node;
|
| 628 |
}
|
| 629 |
}
|
| 630 |
return NULL;
|
| 631 |
}
|
| 632 |
|
| 633 |
function _fileserver_create_file($tid, $name, $options, $filepath) {
|
| 634 |
global $user;
|
| 635 |
|
| 636 |
// Ensure the user has privileges to create nodes of this type. This is
|
| 637 |
// the last chance for access controls to fail, unless some node module
|
| 638 |
// does weird things in hook_validate(), hook_submit() or hook_save().
|
| 639 |
if (!user_access('create file content'))
|
| 640 |
return STATUS_403; // Forbidden
|
| 641 |
|
| 642 |
// manually create an upload object, saving conversions for the cron step
|
| 643 |
$upload = (object)array(
|
| 644 |
'filename' => $name,
|
| 645 |
'filepath' => $filepath,
|
| 646 |
'filemime' => $options['content_type'],
|
| 647 |
'filesize' => filesize($filepath),
|
| 648 |
'noconvert' => TRUE,
|
| 649 |
);
|
| 650 |
|
| 651 |
// according to FileFramework API, make it 'nosave'
|
| 652 |
$node = (object)array(
|
| 653 |
'nosave' => TRUE,
|
| 654 |
);
|
| 655 |
|
| 656 |
// save into Bitcache and RDF
|
| 657 |
if (file_node_save($node, $upload)) {
|
| 658 |
// Organic group vocabularies (og_vocab.module) integration: implicit
|
| 659 |
// audience selection if the currently-selected taxonomy category is
|
| 660 |
// related to an organic group.
|
| 661 |
$og_groups = array();
|
| 662 |
if (module_exists('og_vocab')) {
|
| 663 |
if ($term = taxonomy_get_term($tid)) {
|
| 664 |
if ($og_group = db_result(db_query('SELECT nid FROM {og_vocab} WHERE vid = %d', $term->vid))) {
|
| 665 |
$og_groups[$og_group] = (string)$og_group;
|
| 666 |
}
|
| 667 |
}
|
| 668 |
}
|
| 669 |
|
| 670 |
// actually create the file node
|
| 671 |
$node = file_node_create(array(
|
| 672 |
'file' => $node->file,
|
| 673 |
'taxonomy' => array($tid),
|
| 674 |
'og_groups' => $og_groups,
|
| 675 |
));
|
| 676 |
|
| 677 |
return (bool)$node;
|
| 678 |
}
|
| 679 |
|
| 680 |
return FALSE; // Something must've gone wrong
|
| 681 |
}
|
| 682 |
|
| 683 |
function _fileserver_update_file($tid, $name, $options, $filepath) {
|
| 684 |
// fetch the node by this name for this term
|
| 685 |
if (($node = _fileserver_lookup_file($name, $tid)) && _fileserver_node_access('update', $node)) {
|
| 686 |
// manually create an upload object for the new version
|
| 687 |
$upload = (object)array(
|
| 688 |
'filename' => $name,
|
| 689 |
'filepath' => $filepath,
|
| 690 |
'filemime' => $options['content_type'],
|
| 691 |
'filesize' => filesize($filepath),
|
| 692 |
'noconvert' => TRUE,
|
| 693 |
);
|
| 694 |
|
| 695 |
// save a new revision if required; success even if files aren't versioned
|
| 696 |
if (_fileserver_revision_node($node, 'update', array($node->file->size, filesize($filepath)))) {
|
| 697 |
// use FileFramework to update the upload on the revision
|
| 698 |
return (bool)file_node_save($node, $upload);
|
| 699 |
}
|
| 700 |
}
|
| 701 |
|
| 702 |
return FALSE; // couldn't lookup the old node
|
| 703 |
}
|
| 704 |
|
| 705 |
function _fileserver_term_has_contents($tid) {
|
| 706 |
return (bool)(taxonomy_term_count_nodes($tid, 'file') || count(taxonomy_get_children($tid)));
|
| 707 |
}
|
| 708 |
|
| 709 |
function _fileserver_node_access($op, $node) {
|
| 710 |
global $user;
|
| 711 |
switch ($op) {
|
| 712 |
case 'view':
|
| 713 |
return node_access('view', $node);
|
| 714 |
case 'update':
|
| 715 |
if ($node->uid == $user->uid && user_access('edit own file content'))
|
| 716 |
return TRUE;
|
| 717 |
else
|
| 718 |
return user_access('edit any file content');
|
| 719 |
case 'delete':
|
| 720 |
if ($node->uid == $user->uid && user_access('delete own file content'))
|
| 721 |
return TRUE;
|
| 722 |
else
|
| 723 |
return user_access('delete any file content');
|
| 724 |
}
|
| 725 |
|
| 726 |
return FALSE; // catch-all
|
| 727 |
}
|
| 728 |
|
| 729 |
function _fileserver_revision_node(&$node, $op = NULL, $args = array()) {
|
| 730 |
// get default file node options
|
| 731 |
$node_options = variable_get('node_options_' . FILESERVER_NODE_TYPE, array('status', 'promote'));
|
| 732 |
// set node flag
|
| 733 |
$node->revision = in_array('revision', $node_options);
|
| 734 |
// save a new revision (uploaded file stays the same; gets updated by later calls to file_node_save())
|
| 735 |
if ($node->revision) {
|
| 736 |
// save old vid for comparison
|
| 737 |
$old_vid = $node->vid;
|
| 738 |
// create log message
|
| 739 |
switch ($op) {
|
| 740 |
case 'delete':
|
| 741 |
$node->log = t('Tag %tag removed', array('%tag' => $args[0])) . FILESERVER_SIG;
|
| 742 |
break;
|
| 743 |
case 'rename':
|
| 744 |
$node->log = t('Renamed from %old to %new', array('%old' => $args[0], '%new' => $args[1])) . FILESERVER_SIG;
|
| 745 |
break;
|
| 746 |
case 'move':
|
| 747 |
$node->log = t('Tag %src removed; tag %dst added', array('%src' => $args[0], '%dst' => $args[1])) . FILESERVER_SIG;
|
| 748 |
break;
|
| 749 |
case 'copy':
|
| 750 |
$node->log = t('Tag %tag added', array('%tag' => $args[0])) . FILESERVER_SIG;
|
| 751 |
break;
|
| 752 |
case 'update':
|
| 753 |
$node->log = t('Contents edited');
|
| 754 |
if ($args[0] != $args[1])
|
| 755 |
$node->log .= t('; size changed from !old to !new', array('!old' => format_size($args[0]), '!new' => format_size($args[1])));
|
| 756 |
$node->log .= FILESERVER_SIG;
|
| 757 |
break;
|
| 758 |
}
|
| 759 |
// save a new revision
|
| 760 |
node_save($node);
|
| 761 |
// unset the revision flag & log message
|
| 762 |
unset($node->revision);
|
| 763 |
unset($node->log);
|
| 764 |
// return success if revisioned
|
| 765 |
return ($node->vid > $old_vid ? TRUE : FALSE);
|
| 766 |
}
|
| 767 |
|
| 768 |
return TRUE; // no need for revision
|
| 769 |
}
|
| 770 |
|
| 771 |
//////////////////////////////////////////////////////////////////////////////
|