f73d0e9908b04a58f69230a9325124cc713d9f28
[project/filefield.git] / filefield.module
1 <?php
2 // $Id$
3
4 /**
5 * @file
6 * Defines an image field type.
7 * uses content.module to store the fid, and the drupal files table to store the actual file data.
8 *
9 * @todo
10 * - access control
11 * - multiple image support for views integration. -- stange territory.
12 * @hurrah!!
13 */
14
15 function filefield_init () {
16 //drupal_set_message('POST: <pre>'. print_r($_POST, true) .'</pre>');
17 }
18
19 /**
20 * Implementation of hook_help().
21 */
22 function filefield_help($section) {
23 switch ($section) {
24 case 'admin/modules#description':
25 return t('<strong>CCK:</strong> Defines a file field type for content.module.');
26 }
27 }
28
29 function filefield_menu($maycache) {
30 $items = array();
31
32 // Add handlers for previewing new uploads.
33 if ($_SESSION['filefield']) {
34 foreach ($_SESSION['filefield'] as $fieldname => $files) {
35 foreach($files as $delta => $file) {
36 $filename = file_create_filename($file['filename'], file_create_path());
37 // strip file_directory_path() from private downloads path. @see file_create_url
38 if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE) {
39 if (strpos($filename, file_directory_path()) !== false) {
40 $filename = trim(substr($filename, strlen(file_directory_path())), '\\/');
41 }
42 $filename = 'system/files/' . $filename;
43 }
44 $items[] = array(
45 'path' => $filename, 'title' => t('file download'),
46 'callback' => '_filefield_preview',
47 'access' => TRUE,
48 'type' => MENU_CALLBACK
49 );
50 $file['preview'] = $filename;
51 $_SESSION['filefield'][$fieldname][$delta] = $file;
52 }
53 }
54 }
55 //drupal_set_message('SESSION @ hook_menu: <pre>'. print_r($_SESSION['filefield'], true) .'</pre>');
56 return $items;
57 }
58
59 /**
60 * transfer a file that is in a 'preview' state.
61 * @todo multiple support
62 */
63 function _filefield_preview() {
64 foreach ($_SESSION['filefield'] as $fieldname => $files) {
65 foreach ($files as $delta => $file) {
66 if ($file['preview'] == $_GET['q']) {
67 file_transfer($file['filepath'], array('Content-Type: '. mime_header_encode($file['filemime']),
68 'Content-Length: '. $file['filesize']));
69 exit();
70 }
71 }
72 }
73 }
74
75 /**
76 * Implementation of hook_field_info().
77 */
78 function filefield_field_info() {
79 return array(
80 'file' => array('label' => 'file'),
81 );
82 }
83
84 /**
85 * Implementation of hook_field_settings().
86 */
87 function filefield_field_settings($op, $field) {
88 switch ($op) {
89 case 'form':
90 $form = array();
91 return $form;
92 case 'validate':
93
94 break;
95 case 'save':
96 return array();
97
98 case 'database columns':
99 $columns = array(
100 'fid' => array('type' => 'int', 'not null' => TRUE, 'default' => '0'),
101 'description' => array('type' => 'varchar', length => 255, 'not null' => TRUE, 'default' => "''", 'sortable' => TRUE),
102 'list' => array('type' => 'int', 'not null' => TRUE, 'default' => '0'),
103 );
104 return $columns;
105 }
106 }
107
108 /**
109 * insert a file into the database.
110 * @param $node
111 * node object file will be associated with.
112 * @param $file
113 * file to be inserted, passed by reference since fid should be attached.
114 *
115 */
116 function filefield_file_insert($node, &$file, $field) {
117 $fieldname = $field['field_name'];
118 if ($file = file_save_upload((object)$file, file_directory_path() . '/'.$file['filename'])) {
119 $file = (array)$file;
120 $file['fid'] = db_next_id('{files}_fid');
121 db_query('INSERT into {files} (fid, nid, filename, filepath, filemime, filesize)
122 VALUES (%d, %d, "%s","%s","%s",%d)',
123 $file['fid'], $node->nid, $file['filename'], $file['filepath'], $file['filemime'], $file['filesize']);
124 return (array)$file;
125 }
126 else {
127 // Include file name in upload error.
128 form_set_error(NULL, t('file upload was unsuccessful.'));
129 return FALSE;
130 }
131 }
132
133
134 /**
135 * update the file record if necessary
136 * @param $node
137 * @param $file
138 * @param $field
139 */
140 function filefield_file_update($node, &$file, $field) {
141 $file = (array)$file;
142 //drupal_set_message(' filefield_file_update: <pre>'. print_r($file, true) .'</pre>');
143 if ($file['remove'] == TRUE) {
144 _filefield_file_delete($file, $field['field_name']);
145 return NULL;
146 }
147 if ($file['fid'] == 'upload') {
148 return filefield_file_insert($node, $file, $field);
149 }
150 else {
151 // if fid is not numeric here we should complain.
152 // else we update the file table.
153 }
154 return $file;
155 }
156
157 /**
158 * Implementation of hook_field().
159 */
160 function filefield_field($op, $node, $field, &$node_field, $a1, $a2) {
161 //drupal_set_message('pre hook_field: '.$op.'<pre>'.print_r($node_field, true).'</pre>');
162 $fieldname = $field['field_name'];
163 switch ($op) {
164 // called after content.module loads default data.
165 case 'load':
166 $values = array();
167 foreach ($node_field as $delta => $file) {
168 //drupal_set_message('loading file form content.module: <pre>'. print_r($file, true) .'</pre>');
169 $dbfile = _filefield_file_load($file['fid']);
170 //drupal_set_message('loading file from db: <pre>'. print_r($dbfile, true) .'</pre>');
171 if (!empty($file)) {
172 $values[$delta] = array_merge($file, $dbfile);
173 //$node_field[$delta] = $values[$delta];
174 }
175 }
176 //drupal_set_message('post: hook_field: '.$op.'<pre>'.print_r($node_field, true).'</pre>');
177 $output = array($fieldname => $values);
178 break;
179
180 case 'view':
181 $files = array();
182 foreach ($node_field as $delta => $item) {
183 //drupal_set_message('<pre>'. print_r($item, true) .'</pre>');
184 // do not show images flagged for deletion in preview.
185 if (!$item['remove'] && $item['list']) {
186 $node_field[$delta]['view'] = theme('filefield', $item);
187 $files[] = $node_field[$delta];
188 }
189 }
190 $view = theme('field', $node, $field, $files, $a1, $a2);
191
192 $output = '<div class="'. $field['field_name'] .'">'. $view .'</div>';
193 break;
194
195 // called before content.module defaults.
196 case 'insert':
197 foreach ($node_field as $delta => $item) {
198 $node_field[$delta] = filefield_file_insert($node, $item, $field);
199 }
200 break;
201
202 // called before content.module defaults.
203 case 'update':
204 foreach ($node_field as $delta => $item) {
205 $node_field[$delta] = filefield_file_update($node, $item, $field);
206 }
207 break;
208
209 case 'delete':
210 foreach ($node_field as $delta => $item) {
211 _filefield_file_delete($item, $field['field_name']);
212 }
213 break;
214 }
215 //drupal_set_message('post hook_field: '.$op.'<pre>'.print_r($node_field, true).'</pre>');
216 return $output;
217 }
218
219 /**
220 * Implementation of hook_widget_info().
221 */
222 function filefield_widget_info() {
223 return array(
224 'file' => array(
225 'label' => 'File',
226 'field types' => array('file'),
227 ),
228 );
229 }
230
231 /**
232 * Implementation of hook_widget_settings().
233 */
234 function filefield_widget_settings($op, $widget) {
235 switch ($op) {
236 case 'form':
237 $form = array();
238 $form['file_extensions'] = array (
239 '#type' => 'textfield',
240 '#title' => t('Permitted upload file extensions.'),
241 '#default_value' => $widget['file_extensions'] ? $widget['file_extensions'] : 'txt',
242 '#size' => 64,
243 '#maxlength' => 64,
244 '#description' => t('Extensions a user can upload to this field. Seperate extensions with a space and do not include the leading dot.')
245 );
246 return $form;
247 case 'validate':
248 break;
249 case 'save':
250 return array('file_extensions');
251 }
252 }
253
254
255 function filefield_clear_session() {
256 if (is_array($_SESSION['filefield']) && count($_SESSION['filefield'])) {
257 foreach (array_keys($_SESSION['filefield']) as $fieldname) {
258 filefield_clear_field_session($fieldname);
259 }
260 unset($_SESSION['filefield']);
261 }
262 }
263
264 function filefield_clear_field_session($fieldname) {
265 if (count($_SESSION['filefield'][$fieldname])) {
266 foreach ($_SESSION['filefield'][$fieldname] as $files) {
267 foreach ($files as $delta => $file) {
268 if (is_file($file['filepath'])) {
269 file_delete($file['filepath']);
270 }
271 }
272 }
273 unset($_SESSION['filefield'][$fieldname]);
274 }
275 }
276
277 function _filefield_file_delete($file, $fieldname) {
278 //drupal_set_message(' _filefield_file_delete: <pre>'. print_r($file, true) .'</pre>');
279 if (is_numeric($file['fid'])) {
280 db_query('DELETE FROM {files} WHERE fid = %d', $file['fid']);
281 }
282 else {
283 unset($_SESSION['filefield'][$fieldname][$file['sessionid']]);
284 }
285 return file_delete($file['filepath']);
286 }
287
288 /**
289 * Implementation of hook_widget().
290 */
291 function filefield_widget($op, $node, $field, &$node_field) {
292 //drupal_set_message('pre filefield_widget: '. $op .'<pre>'. print_r($node_field, true) .'</pre>');
293 $fieldname = $field['field_name'];
294 switch ($op) {
295 case 'prepare form values':
296 // drupal_set_message('pre filefield_widget: '. $op .'<pre>'. print_r($node_field, true) .'</pre>');
297 //drupal_set_message('pre filefield_widget node: '. $op .'<pre>'. print_r($node, true) .'</pre>');
298 // clean up the session if we weren't posted.
299 if (!count($_POST)) {
300 filefield_clear_session();
301 }
302
303 // Attach new files
304 if ($file = file_check_upload($fieldname . '_upload')) {
305 //drupal_set_message('file found!!!');
306 $file = (array)$file;
307 $ext = array_pop(explode('.',$file['filename']));
308 $allowed_extensions = array_unique(explode(' ', trim($field['widget']['file_extensions'])));
309 if (in_array($ext, $allowed_extensions)) {
310 //do mime specific handling.
311 }
312 else {
313 form_set_error($field['field_name'] .'_upload',t('Files with the extension %ext are not allowed. Please upload a file with an extension from the following list: %allowed_extensions', array('%ext' => theme('placeholder',$ext), '%allowed_extensions' => theme('placeholder', $field['widget']['file_extensions']))));
314 return FALSE;
315 }
316 $file['fid'] = 'upload';
317 if (!$field['multiple']) {
318 // Remove old temporary file from session.
319 filefield_clear_field_session($fieldname);
320 }
321 $file['sessionid'] = count($_SESSION['filefield'][$fieldname]);
322 $_SESSION['filefield'][$fieldname][$file['sessionid']] = $file;
323 // drupal_set_message('SESSION: <pre>'. print_r($_SESSION['filefield'], true) .'</pre>');
324 }
325
326
327
328 // Load files from preview state. before committing actions.
329 /* commented out until cck prepare form values / node_field situation is worked out
330 if (is_array($_SESSION['filefield'][$fieldname]) && count($_SESSION['filefield'][$fieldname])) {
331 foreach($_SESSION['filefield'][$fieldname] as $delta => $file) {
332 $node_field[] = $file;
333 }
334 }
335 */
336 break;
337
338 case 'form':
339 if (is_array($_SESSION['filefield'][$fieldname]) && count($_SESSION['filefield'][$fieldname])) {
340 foreach($_SESSION['filefield'][$fieldname] as $delta => $file) {
341 $node_field[] = $file;
342 }
343 }
344
345 $form = _filefield_widget_form($node, $field, $node_field);
346 return $form;
347
348 case 'validate':
349 if ($field['required']) {
350 if (!count($node_field)) {
351 form_set_error($fieldname, $field['widget']['label'] .' is required.');
352 }
353 }
354 return;
355
356 case 'process form values':
357 //drupal_set_message('<pre>'. print_r($node, true) .'</pre>');
358 foreach ($node_field as $delta => $item) {
359 if ($item['fid'] == '0') {
360 $node_field[$delta] = NULL;
361 }
362 }
363 return;
364
365 }
366 //drupal_set_message('post filefield_widget: '. $op .'<pre>'. print_r($node_field, true) .'<pre>');
367 }
368
369 function _filefield_widget_form($node, $field, &$node_field) {
370 $fieldname = $field['field_name'];
371 theme_add_style(drupal_get_path('module', 'filefield') .'/filefield.css');
372
373 $form = array();
374 $form[$fieldname] = array(
375 '#type' => 'fieldset',
376 '#title' => t($field['widget']['label']),
377 '#weight' => 5,
378 '#collapsible' => TRUE,
379 '#collapsed' => FALSE,
380 '#tree' => TRUE,
381 '#theme' => 'filefield_current',
382 );
383
384 $form[$fieldname][$fieldname .'_upload'] = array(
385 '#type' => 'file',
386 '#description' => t('Attach a file. allowed extensions(%ext)', array('%ext' => theme('placeholder', $field['widget']['file_extensions']))),
387 '#weight' => 9,
388 '#tree' => FALSE,
389 );
390
391 $form[$fieldname]['Update'] = array(
392 '#type' => 'button',
393 '#value' => t('Update'),
394 '#name' => 'cck_filefield_'.$fieldname.'_op',
395 '#attributes' => array('id' => $fieldname.'-attach-button'),
396 '#tree' => FALSE,
397 '#weight' => 10,
398 );
399
400 //drupal_set_message('form: <pre>'. print_r($form, TRUE) .'</pre>');
401 //drupal_set_message('filefield_widget_form: nodefield: <pre>'. print_r($node_field, true) .'</pre>');
402 if (is_array($node_field) && count($node_field)) {
403 foreach($node_field as $delta => $file) {
404 if (!empty($file)) {
405 $form[$fieldname][$delta]['icon'] = array(
406 '#type' => 'markup',
407 '#value' => theme('filefield_icon', $file),
408 );
409
410 if (strpos($file['fid'], 'upload') === false) {
411 $filepath = $file['filepath'];
412 }
413 else {
414 $filepath = file_create_filename($file['filename'], file_create_path());
415 }
416 $description = file_create_url($filepath);
417 $description = "<small>". check_plain($description) ."</small>";
418 $form[$fieldname][$delta]['description'] = array('#type' => 'textfield', '#default_value' => (strlen($file['description'])) ? $file['description'] : $file['filename'], '#maxlength' => 256, '#description' => $description );
419 $form[$fieldname][$delta]['size'] = array('#type' => 'markup', '#value' => format_size($file['filesize']));
420 $form[$fieldname][$delta]['remove'] = array('#type' => 'checkbox', '#default_value' => $file['remove']);
421 $form[$fieldname][$delta]['list'] = array('#type' => 'checkbox', '#default_value' => $file['list']);
422
423 $form[$fieldname][$delta]['filename'] = array('#type' => 'value', '#value' => $file['filename']);
424 $form[$fieldname][$delta]['filepath'] = array('#type' => 'value', '#value' => $file['filepath']);
425 $form[$fieldname][$delta]['filemime'] = array('#type' => 'value', '#value' => $file['filemime']);
426 $form[$fieldname][$delta]['filesize'] = array('#type' => 'value', '#value' => $file['filesize']);
427 $form[$fieldname][$delta]['fid'] = array('#type' => 'value', '#value' => $file['fid']);
428
429 // Special handling for single value fields
430 // mark original item for deletion.
431 if (!$field['multiple'] && $delta == 0 && (count($_SESSION['filefield'][$fieldname]) > 1)) {
432 $form[$fieldname][$delta]['remove']['#value'] = 1;
433 $form[$fieldname][$delta]['replace'] = array(
434 '#type' => 'markup',
435 '#value' => t('If a new file is uploaded, the current file will be replaced upon submitting the node edit form.'),
436 );
437 }
438 }
439 }
440 // end foreach...
441 }
442 //drupal_set_message('filefield_form('. $fieldname .'): <pre>'. print_r($form, TRUE) .'</pre>');
443 return $form;
444 }
445
446 function theme_filefield_current(&$form) {
447 $header = array(t('Type'), t('Description'), t('Size'), t('List'), t('Delete'));
448 //drupal_set_message('form: <pre>'. print_r($form, TRUE) .'</pre>');
449
450 foreach (element_children($form) as $key) {
451 //drupal_set_message($key);
452 //only display numerically keyed fields in form.
453 //numeric key == $delta
454 if (is_numeric($key)) {
455 $row = array();
456 $row[] = form_render($form[$key]['icon']);
457 $row[] = form_render($form[$key]['description']);
458 $row[] = form_render($form[$key]['size']);
459 $row[] = form_render($form[$key]['list']);
460 $row[] = form_render($form[$key]['remove']);
461 $rows[] = $row;
462 }
463 }
464 if (count($rows)) {
465 $output = theme('table', $header, $rows);
466 }
467 $output .= form_render($form);
468 return $output;
469 }
470
471
472 function filefield_field_view_item($field, $node_field_item) {
473 $file = _filefield_file_load($node_field_item['fid']);
474 return theme('filefield_view_file', $file, $node_field_item['description']);
475 }
476
477
478 function _filefield_file_load($fid = NULL) {
479 // Exit if we were not passed an fid to load;
480 if (!isset($fid)) {
481 //drupal_set_message('_filefield_file_load did not recieve an fid');
482 return false;
483 }
484 // Test to catch fid, eventuall plan to have node_load syntax
485 // once file_attributes table is complete
486 if (is_numeric($fid)) {
487 $result = db_query('SELECT * FROM {files} WHERE fid = %d', $fid);
488 return db_fetch_array($result);
489 }
490 //drupal_set_message('expected file not found.');
491 return FALSE;
492 }
493
494 function theme_filefield_icon($file) {
495 $ext = array_pop(explode('.',$file['filename']));
496 $known_extensions = array('0','ace','aif','ai','ani','asf','asp','avi','bak','bat','bin','bmp','bz2','bz','cab','cdr','cfg','com','conf','cpt','css','cur','dat','db','dcr','dic','diff','dir','dll','dmg','doc','dwg','edir','eml','eps','exe','fla','flv','fon','gif','gz','hqx','html','htm','ico','inc','ini','iso','jpeg','jpg','js','lnk','log','m3u','mdb','midi','mid','mov','mp3','mpeg','mpg','nfo','odb','odc','odf','odg','odm','odp','ods','odt','ogg','otg','oth','otp','ots','ott','patch','pdf','php3','php','phtml','pl','png','pps','ppt','psd','pwl','qt','ram','ra','rar','reg','rpm','rtf','sh','shtml','sit','sql','svg','swf','sxc','sxi','sxw','sys','tar','tgz','tiff','tif','tmp','tpl','ttf','txt','wav','wma','wmv','wp','xls','xml','zip');
497 if (!in_array($ext, $known_extensions)) {
498 $ext = 0;
499 }
500 $imagepath = drupal_get_path('module','filefield') .'/ico/'. $ext .'.png';
501
502
503
504 return '<div class="filefield-icon-container"><div class="filefield-icon field-icon-'. $ext .'"><img src="'. $imagepath .'" /></div></div>';
505 //return '<div class="filefield-icon-container"><div class="filefield-icon field-icon-'. $ext .'">&nbsp;</div></div>'."\n";
506 }
507
508 function theme_filefield_view_file($file) {
509 return theme('filefield', $file);
510 }
511
512 function theme_filefield($file) {
513 $file = (array)$file;
514 if (is_file($file['filepath'])) {
515 if ($file['fid'] == 'upload') {
516 $path = file_create_filename($file['filename'], file_create_path());
517 }
518 else {
519 $path = $file['filepath'];
520 }
521 $url = file_create_url($path);
522 $name = $file['filename'];
523 $desc = $file['description'];
524 return '<a href="'. check_url($url) .'">'.check_plain($name).'</a>';
525 }
526 }