Newer
Older
Darrel O'Pry
committed
<?php // $Id$
/**
* @file
* FileField: Defines a CCK file field type.
* Uses content.module to store the fid and field specific metadata,
* and Drupal's {files} table to store the actual file data.
Darrel O'Pry
committed
/**
* Implementation of hook_init().
*/
function filefield_init() {
Darrel O'Pry
committed
/**
* include filefield dependecies.
* @todo: explore better dynamic loading.
*/
// field file api.
module_load_include('inc', 'filefield', 'field_file');
// widget hooks and callbacks.
Darrel O'Pry
committed
module_load_include('inc', 'filefield', 'filefield_widget');
Darrel O'Pry
committed
// formatter hooks and callbacks.
module_load_include('inc', 'filefield', 'filefield_formatter');
// file hooks and callbacks.
module_load_include('inc', 'filefield', 'filefield_file');
}
/**
* Implementation of hook_menu().
*/
function filefield_menu() {
$items = array();
$items['filefield/js/upload/%/%/%'] = array(
'page callback' => 'filefield_js',
'page arguments' => array(3, 4, 5, 'filefield_file_upload_js'),
'access callback' => 'filefield_edit_access',
'access arguments' => array(3),
'file' => 'filefield_widget.inc',
);
$items['filefield/js/delete/%/%/%'] = array(
'page callback' => 'filefield_js',
'page arguments' => array(3, 4, 5, 'filefield_file_edit_delete_js'),
'access callback' => 'filefield_edit_access',
'access arguments' => array(3),
'type' => MENU_CALLBACK,
'file' => 'filefield_widget.inc',
return $items;
}
/**
* Implementation of hook_elements().
*/
function filefield_elements() {
$elements = array();
Darrel O'Pry
committed
$elements['filefield_widget'] = array(
'#input' => TRUE,
Darrel O'Pry
committed
'#columns' => array('fid', 'description', 'list', 'data'),
'#process' => array('filefield_widget_process'),
'#after_build' => array('filefield_widget_after_build'),
'#value_callback' => 'filefield_widget_value',
'#description' => t('Changes made to the attachments are not permanent until you save this post.'),
);
Darrel O'Pry
committed
$elements['filefield_extensible'] = array(
'#input' => TRUE,
Darrel O'Pry
committed
'#columns' => array('fid', 'description', 'list', 'data'),
'#process' => array('filefield_extensible_process'),
'#after_build' => array('filefield_extensible_after_build'),
'#value_callback' => 'filefield_extensible_value',
'#description' => t('Changes made to the attachments are not permanent until you save this post.'),
);
return $elements;
}
/**
* Implementation of hook_theme().
*/
function filefield_theme() {
return array(
Darrel O'Pry
committed
'filefield_widget' => array(
'arguments' => array('element' => NULL),
Darrel O'Pry
committed
'file' => 'filefield_widget.inc',
),
Darrel O'Pry
committed
// @todo: verify the need for the rest one by one.
'filefield_container_item' => array(
'arguments' => array('element' => NULL),
'file' => 'filefield.theme.inc',
),
'filefield_icon' => array(
'arguments' => array('file' => NULL),
'file' => 'filefield.theme.inc',
),
'filefield_file_upload' => array(
'arguments' => array('element' => NULL),
'file' => 'filefield_widget.inc',
'filefield_file_edit' => array(
'arguments' => array('element' => NULL),
'file' => 'filefield_widget.inc',
),
'filefield_generic_edit' => array(
'arguments' => array('element' => NULL),
//'file' => this one,
'filefield_formatter_default' => array(
'arguments' => array('element' => NULL),
'file' => 'filefield.formatter.inc',
'filefield_unguarded' => array(
'arguments' => array('file' => NULL, 'field' => NULL),
'file' => 'filefield.formatter.inc',
),
'filefield' => array(
'arguments' => array('file' => NULL, 'field' => NULL),
'file' => 'filefield.formatter.inc',
),
'filefield_file_formatter_generic' => array(
'arguments' => array(
'file' => NULL, 'field' => NULL, 'file_formatter_settings' => NULL,
),
'file' => 'filefield.formatter.inc',
),
);
/**
* Implementation of hook_file_download(). Yes, *that* hook that causes
* any attempt for file upload module interoperability to fail spectacularly.
*/
function filefield_file_download($file) {
$file = file_create_path($file);
Darrel O'Pry
committed
$result = db_query("SELECT * FROM {files} WHERE filepath = '%s'", $file);
if (!$file = db_fetch_object($result)) {
// We don't really care about this file.
return;
}
Darrel O'Pry
committed
// Find out if any filefield contains this file, and if so, which field
// and node it belongs to. Required for later access checking.
$cck_files = array();
foreach (content_fields() as $field) {
if ($field['type'] == 'file') {
$db_info = content_database_info($field);
$table = $db_info['table'];
$fid_column = $db_info['columns']['fid']['column'];
$columns = array('vid', 'nid');
foreach ($db_info['columns'] as $property_name => $column_info) {
$columns[] = $column_info['column'] .' AS '. $property_name;
}
$result = db_query("SELECT ". implode(', ', $columns) ."
FROM {". $table ."}
WHERE ". $fid_column ." = %d", $file->fid);
while ($content = db_fetch_array($result)) {
$content['field'] = $field;
$cck_files[$field['field_name']][$content['vid']] = $content;
}
}
// If no filefield item is involved with this file, we don't care about it.
if (empty($cck_files)) {
return;
}
// If any node includes this file but the user may not view this field,
// then deny the download.
foreach ($cck_files as $field_name => $field_files) {
if (!filefield_view_access($field_name)) {
return -1;
}
}
// So the overall field view permissions are not denied, but if access is
// denied for a specific node containing the file, deny the download as well.
// It's probably a little too restrictive, but I can't think of a
// better way at the moment. Input appreciated.
// (And yeah, node access checks also include checking for 'access content'.)
$nodes = array();
foreach ($cck_files as $field_name => $field_files) {
foreach ($field_files as $revision_id => $content) {
// Checking separately for each revision is probably not the best idea -
// what if 'view revisions' is disabled? So, let's just check for the
// current revision of that node.
if (isset($nodes[$content['nid']])) {
continue; // don't check the same node twice
}
$node = node_load($content['nid']);
if (!node_access('view', $node)) {
// You don't have permission to view the node this file is attached to.
return -1;
}
$nodes[$content['nid']] = $node;
}
Darrel O'Pry
committed
}
// Well I guess you can see this file.
$name = mime_header_encode($file->filename);
$type = mime_header_encode($file->filemime);
// Serve images and text inline for the browser to display rather than download.
$disposition = ereg('^(text/|image/)', $file->filemime) ? 'inline' : 'attachment';
return array(
'Content-Type: '. $type .'; name='. $name,
'Content-Length: '. $file->filesize,
'Content-Disposition: '. $disposition .'; filename='. $name,
'Cache-Control: private',
);
Darrel O'Pry
committed
}
Darrel O'Pry
committed
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
* Implementation of CCK's hook_field_info().
*/
function filefield_field_info() {
return array(
'filefield' => array(
'label' => 'File',
'description' => t('Store an arbitrary file.'),
),
);
}
/**
* Implementation of hook_field_settings().
*/
function filefield_field_settings($op, $field) {
module_load_include('inc','filefield','filefield_field_settings');
$op = str_replace(' ', '_', $op);
// add filefield specific handlers...
$function = 'filefield_field_settings_'. $op;
if (function_exists($function)) {
return $function($field);
}
}
/**
* Implementtation of CCK's hook_field().
Darrel O'Pry
committed
function filefield_field($op, $node, $field, &$items, $teaser, $page) {
module_load_include('inc','filefield','filefield_field');
$op = str_replace(' ', '_', $op);
// add filefield specific handlers...
$function = 'filefield_field_'. $op;
if (function_exists($function)) {
return $function($node, $field, $items, $teaser, $page);
}
}
Darrel O'Pry
committed
* Implementation of CCK's hook_widget_settings().
Darrel O'Pry
committed
function filefield_widget_settings($op, $widget) {
module_load_include('inc','filefield','filefield_widget_settings');
$op = str_replace(' ', '_', $op);
$function = 'filefield_field_settings_'. $op;
if (function_exists($function)) {
return $function($widget);
Darrel O'Pry
committed
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
/**
* Implementation of hook_widget().
*/
function filefield_widget(&$form, &$form_state, $field, $items, $delta = 0) {
if (empty($items[$delta])) {
$items[$delta] = array('fid' => 0, 'description' => '', 'list' => 0, 'data' => '');
}
$form['#attributes'] = array('enctype' => 'multipart/form-data');
$element = array(
'#type' => $field['widget']['type'],
'#default_value' => $items[$delta],
);
return $element;
}
/**
* Implementation of CCK's hook_content_is_empty().
*
* The result of this determines whether content.module will save
* the value of the field.
*/
function filefield_content_is_empty($item, $field) {
if (empty($item['fid'])) {
return TRUE;
}
return FALSE;
}
/**
* Implementation of CCK's hook_widget_info().
*/
function filefield_widget_info() {
return array(
'filefield_widget' => array(
'label' => t('File Upload'),
'field types' => array('file'),
'multiple values' => CONTENT_HANDLE_CORE,
'callbacks' => array('default value' => CONTENT_CALLBACK_CUSTOM),
'description' => t('A plain file upload widget.'),
),
'filefield_combo' => array(
'label' => 'Extensible File',
'field types' => array('file'),
'multiple values' => CONTENT_HANDLE_CORE,
'callbacks' => array('default value' => CONTENT_CALLBACK_CUSTOM),
'description' => t('(Experimental)An extensible file upload widget.'),
),
);
}
/**
* Implementation of CCK's hook_field_formatter_info().
*/
function filefield_field_formatter_info() {
return array(
'filefield_default' => array(
'label' => t('Generic files'),
'suitability callback' => TRUE,
'field types' => array('file','image'),
'multiple values' => CONTENT_HANDLE_CORE,
'description' => t('Displays all kinds of files with an icon and a linked file description.'),
),
'filefield_dynamic' => array(
'label' => t('Dynamic file formatters'),
'suitability callback' => TRUE,
'field types' => array('file'),
'multiple values' => CONTENT_HANDLE_CORE,
'description' => t('(experimental) An extensible formatter for filefield.'),
),
);
}
/**
* Determine the most appropriate icon for the given file's mimetype.
*
* @return The URL of the icon image file, or FALSE if no icon could be found.
*/
function filefield_icon_url($file) {
include_once(drupal_get_path('module', 'filefield') .'/filefield.theme.inc');
return _filefield_icon_url($file);
}
/**
* Access callback for the JavaScript upload and deletion AHAH callbacks.
* The content_permissions module provides nice fine-grained permissions for
* us to check, so we can make sure that the user may actually edit the file.
*/
function filefield_edit_access($field_name) {
if (module_exists('content_permissions')) {
return user_access('edit '. $field_name);
}
// No content permissions to check, so let's fall back to a more general permission.
return user_access('access content');
}
/**
* Access callback that checks if the current user may view the filefield.
*/
function filefield_view_access($field_name) {
if (module_exists('content_permissions')) {
return user_access('view '. $field_name);
}
// No content permissions to check, so let's fall back to a more general permission.
return user_access('access content');
}