#370531: Properly handle revisions of FileFields.
[project/filefield.git] / filefield_widget.inc
CommitLineData
9412a7cc
JP
1<?php
2// $Id$
a679aaae 3
9412a7cc
JP
4/**
5 * @file
6 * FileField: Defines a CCK file field type.
7 *
8 * Uses content.module to store the fid and field specific metadata,
9 * and Drupal's {files} table to store the actual file data.
10 *
11 * This file contains CCK widget related functionality.
12 */
13
22e736e6 14/**
7e784adc 15 * @file
16 *
22e736e6 17 * FileField Widget Settings Hooks.
6f0f10d4
DP
18 * @todo: move description property to filefield_widget widget callbacks
19 * (filefield_widget_widget_{$op}).
22e736e6
DP
20 */
21
22function filefield_widget_settings_form($widget) {
23 $form = array();
24 $form['file_extensions'] = array(
25 '#type' => 'textfield',
26 '#title' => t('Permitted upload file extensions'),
27 '#default_value' => is_string($widget['file_extensions']) ? $widget['file_extensions'] : 'txt',
28 '#size' => 64,
29 '#description' => t('Extensions a user can upload to this field. Separate extensions with a space and do not include the leading dot. Leaving this blank will allow users to upload a file with any extension.'),
30 '#weight' => 1,
31 );
32 $form['path_settings'] = array(
33 '#type' => 'fieldset',
34 '#title' => t('Path settings'),
35 '#collapsible' => true,
36 '#collapsed' => true,
37 '#weight' => 6,
38 );
39
40 $form['path_settings']['file_path'] = array(
41 '#type' => 'textfield',
42 '#title' => t('File path'),
43 '#default_value' => is_string($widget['file_path']) ? $widget['file_path'] : '',
44 '#description' => t('Optional subdirectory within the "%dir" directory where files will be stored. Do not include trailing slash.', array('%dir' => variable_get('file_directory_path', 'files'))),
45 '#element_validate' => array('_filefield_widget_settings_file_path_validate'),
46 '#suffix' => theme('token_help', 'user'),
47 );
48
49 $form['max_filesize'] = array(
50 '#type' => 'fieldset',
51 '#title' => t('File size restrictions'),
52 '#description' => t('Limits for the size of files that a user can upload. Note that these settings only apply to newly uploaded files, whereas existing files are not affected.'),
53 '#weight' => 6,
54 '#collapsible' => TRUE,
55 '#collapsed' => TRUE,
56 );
7e784adc 57 // upload validator. @todo: consider replacing with global
d6caa409 58 // && node validate.
22e736e6
DP
59 $form['max_filesize']['max_filesize_per_file'] = array(
60 '#type' => 'textfield',
61 '#title' => t('Maximum upload size per file'),
62 '#default_value' => is_string($widget['max_filesize_per_file'])
63 ? $widget['max_filesize_per_file']
64 : '',
a2db4a45 65 '#description' => t('Specify the size limit that applies to each file separately. Enter a value like "512" (bytes), "80K" (kilobytes) or "50M" (megabytes) in order to restrict the allowed file size. If you leave this this empty the file sizes will be limited only by PHP\'s maximum post and file upload sizes (current limit <strong>%limit</strong>).', array('%limit' => format_size(file_upload_max_size()))),
22e736e6
DP
66 '#element_validate' => array('_filefield_widget_settings_max_filesize_per_file_validate'),
67 );
d6caa409
DP
68
69 // node validate.
22e736e6
DP
70 $form['max_filesize']['max_filesize_per_node'] = array(
71 '#type' => 'textfield',
72 '#title' => t('Maximum upload size per node'),
73 '#default_value' => is_string($widget['max_filesize_per_node'])
74 ? $widget['max_filesize_per_node']
75 : '',
76 '#description' => t('Specify the total size limit for all files in field on a given node. Enter a value like "512" (bytes), "80K" (kilobytes) or "50M" (megabytes) in order to restrict the total size of a node. Leave this empty if there should be no size restriction.'),
77 '#element_validate' => array('_filefield_widget_settings_max_filesize_per_node_validate'),
78 );
79 return $form;
80}
81
82function filefield_widget_settings_save($widget) {
83 return array(
84 'file_extensions', 'file_path', 'max_filesize_per_file',
85 'max_filesize_per_node', 'file_widgets'
86 );
87}
88
89function _filefield_widget_settings_file_path_validate($element, &$form_state) {
90 // Strip slashes from the beginning and end of $widget['file_path']
91 $form_state['values']['file_path'] = trim($form_state['values']['file_path'], '\\/');
92}
93
94function _filefield_widget_settings_max_filesize_per_file_validate($element, &$form_state) {
95 if (empty($form_state['values']['max_filesize_per_file'])) {
96 return; // Empty means no size restrictions, so don't throw an error.
97 }
98 else if (!is_numeric(parse_size($form_state['values']['max_filesize_per_file']))) {
99 form_error($element, t('The "Maximum file size for each file" option must contain a valid value. You can either leave the text field empty or enter a string like "512" (bytes), "80K" (kilobytes) or "50M" (megabytes).'));
100 }
101}
102
103function _filefield_widget_settings_max_filesize_per_node_validate($element, &$form_state) {
104 if (empty($form_state['values']['max_filesize_per_node'])) {
105 return; // Empty means no size restrictions, so don't throw an error.
106 }
107 else if (!is_numeric(parse_size($form_state['values']['max_filesize_per_node']))) {
108 form_error($element, t('The "Maximum file size per node" option must contain a valid value. You can either leave the text field empty or enter a string like "512" (bytes), "80K" (kilobytes) or "50M" (megabytes).'));
109 }
110}
111
bf602673 112/**
113 * Determine the widget's files directory
114 *
115 * @param $field CCK field
116 * @return files directory path.
117 */
118function filefield_widget_file_path($field_instance) {
119 $dest = $field_instance['widget']['file_path'];
120 if (module_exists('token')) {
121 global $user;
122 $dest = token_replace($dest, 'user', $user);
123 }
124
125 return file_directory_path() .'/'. $dest;
126}
127
8ffd8e3d
DP
128function filefield_save_upload($element) {
129 $upload_name = $element['#field_name'] .'_'. $element['#delta'];
130 $field_instance = content_fields($element['#field_name'], $element['#type_name']);
131
132 if (empty($_FILES['files']['name'][$upload_name])) {
133 return 0;
134 }
135
bf602673 136 $dest = filefield_widget_file_path($field_instance);
8ffd8e3d 137 if (!field_file_check_directory($dest, FILE_CREATE_DIRECTORY)) {
32984cd2 138 watchdog('filefield', 'The upload directory %directory for the file field %field (content type %type) could not be created or is not accessible. A newly uploaded file could not be saved in this directory as a consequence, and the upload was canceled.', array('%directory' => $dest, '%field' => $element['#field_name'], '%type' => $element['#type_name']));
8ffd8e3d
DP
139 form_set_error($upload_name, t('The file could not be uploaded.'));
140 return 0;
141 }
142
143 if (!$file = field_file_save_upload($upload_name, $element['#upload_validators'], $dest)) {
32984cd2 144 watchdog('filefield', 'The file upload failed. %upload', array('%upload' => $upload_name));
29194778 145 form_set_error($upload_name, t('The file in the @field field was unable to be uploaded.', array('@field' => $element['#title'])));
8ffd8e3d
DP
146 return 0;
147 }
148 return $file['fid'];
149}
150
22e736e6
DP
151/**
152 * FileField widget element callbacks.
153 */
d2f124d5 154function filefield_widget_value($element, $edit = FALSE) {
7e784adc 155 if (!$edit) {
d2f124d5
DP
156 $file = field_file_load($element['#default_value']['fid']);
157 $item = $element['#default_value'];
158 }
159 else {
9811a70a 160 $item = array_merge($element['#default_value'], $edit);
8ffd8e3d
DP
161 // uploads take priority over value of fid text field.
162 if ($fid = filefield_save_upload($element)) {
9811a70a 163 $item['fid'] = $fid;
7e784adc 164 }
8ffd8e3d 165 // load file.
9811a70a 166 $file = field_file_load($item['fid']);
d2f124d5 167 }
8ffd8e3d 168 // merge file and item data so it is available to all widgets.
6e7782b4 169 $item = array_merge($item, $file);
1bffec9e 170
6e7782b4 171 return $item;
d2f124d5
DP
172}
173
174function filefield_widget_process($element, $edit, &$form_state, $form) {
9c0b75b0 175 // The widget is being presented, so apply the JavaScript.
176 drupal_add_js(drupal_get_path('module', 'filefield') .'/filefield.js');
7e784adc 177
8ffd8e3d
DP
178 $item = $element['#value'];
179 $field_name = $element['#field_name'];
180 $delta = $element['#delta'];
3897ed27 181 $element['#theme'] = 'filefield_widget_item';
8ffd8e3d 182
ad6c5ff0
DP
183 $field = content_fields($element['#field_name'], $element['#type_name']);
184
3897ed27
NH
185 // Title is not necessary for each individual field.
186 if ($field['multiple'] > 0) {
187 unset($element['#title']);
188 }
189
190 // Check if a remove button was clicked.
ad98c19d 191 $remove_name = $element['#field_name'] .'_'. $element['#delta'] .'_filefield_remove';
ad6c5ff0 192 if (isset($form_state['clicked_button']) && $form_state['clicked_button']['#name'] == $remove_name) {
3a35dc81 193 $item = array('fid' => 0, 'list' => $field['list_default'], 'data' => array('description' => ''));
8ffd8e3d 194 }
7e784adc 195
196 // figute out our fid...
8ffd8e3d 197 $element['fid'] = array('#type' => 'hidden', '#value' => $item['fid']);
d2f124d5 198
6e7782b4 199 if ($item['fid'] != 0) {
7e784adc 200 $element['preview'] = array('#type' => 'markup', '#value' => theme($element['#type'] .'_preview', $item));
d2f124d5 201 }
28b1c58e
DP
202
203 // placeholder.. will be serialized into the data column. this is a place for widgets
204 // to put additional data.
a679aaae
NH
205 $element['data'] = array(
206 '#tree' => 'true',
207 '#access' => !empty($item['fid']),
208 );
2635f5af 209
35873ed9 210 if ($field['description_field']) {
2635f5af 211 $element['data']['description'] = array(
212 '#type' => 'textfield',
213 '#title' => t('Description'),
214 '#value' => isset($item['data']['description']) ? $item['data']['description'] : '',
215 );
216 }
28b1c58e 217
35873ed9 218 if ($field['list_field']) {
28b1c58e
DP
219 $element['list'] = array(
220 '#type' => 'checkbox',
221 '#title' => t('List'),
8ffd8e3d 222 '#value' => isset($item['list']) ? $item['list'] : $field['list_default'],
28b1c58e 223 '#attributes' => array('class' => 'filefield-list'),
3897ed27 224 '#access' => !empty($item['fid']),
28b1c58e
DP
225 );
226 }
35873ed9
NH
227 else {
228 $element['list'] = array(
229 '#type' => 'hidden',
230 '#value' => '1',
231 );
232 }
d2f124d5 233
8ffd8e3d
DP
234 foreach ($element['#upload_validators'] as $callback => $arguments) {
235 $help_func = $callback .'_help';
ad6bb31f
DP
236 if (function_exists($help_func)) {
237 $desc[] = call_user_func_array($help_func, $arguments);
238 }
8ffd8e3d
DP
239 }
240 $element['upload'] = array(
241 '#name' => 'files['. $element['#field_name'] .'_'. $element['#delta'] .']',
242 '#type' => 'file',
8ffd8e3d 243 '#description' => implode('<br />', $desc),
3897ed27 244 '#size' => 22,
8ffd8e3d 245 '#attributes' => array(
f976317f 246 'accept' => implode(',', array_filter(explode(' ', $field['widget']['file_extensions']))),
3897ed27
NH
247 ),
248 '#access' => empty($item['fid']),
8ffd8e3d
DP
249 );
250
f613b75e 251 $element['#attributes']['id'] = $element['#id'] .'-ahah-wrapper';
f613b75e 252 $element['#prefix'] = '<div '. drupal_attributes($element['#attributes']) .'>';
aa3a8ca0 253 $element['#suffix'] = '</div>';
ad98c19d 254 $element['filefield_upload'] = array(
7e784adc 255 '#type' => 'submit',
8ffd8e3d 256 '#value' => t('Upload'),
aa3a8ca0 257 '#process' => array('form_expand_ahah'),
8ffd8e3d 258 '#submit' => array('node_form_submit_build_node'),
7e784adc 259 '#ahah' => array( // with JavaScript
260 'path' => 'filefield/ahah/'. $element['#type_name'] .'/'. $element['#field_name'] .'/'. $element['#delta'],
261 'wrapper' => $element['#id'] .'-ahah-wrapper',
262 'method' => 'replace',
263 'effect' => 'fade',
8ffd8e3d
DP
264 ),
265 '#field_name' => $element['#field_name'],
266 '#delta' => $element['#delta'],
267 '#type_name' => $element['#type_name'],
268 '#upload_validators' => $element['#upload_validators'],
3897ed27 269 '#access' => empty($item['fid']),
8ffd8e3d 270 );
d2f124d5 271
ad98c19d
NH
272 $element['filefield_remove'] = array(
273 '#name' => $element['#field_name'] .'_'. $element['#delta'] .'_filefield_remove',
3897ed27
NH
274 '#type' => 'submit',
275 '#value' => t('Remove'),
ad98c19d 276 '#process' => array('form_expand_ahah'),
3897ed27
NH
277 '#submit' => array('node_form_submit_build_node'),
278 '#ahah' => array( // with JavaScript
279 'path' => 'filefield/ahah/'. $element['#type_name'] .'/'. $element['#field_name'] .'/'. $element['#delta'],
280 'wrapper' => $element['#id'] .'-ahah-wrapper',
281 'method' => 'replace',
282 'effect' => 'fade',
283 ),
284 '#field_name' => $element['#field_name'],
285 '#delta' => $element['#delta'],
286 '#access' => !empty($item['fid']),
287 );
d2f124d5 288
d2f124d5
DP
289 return $element;
290}
291
ac83a9cf 292function filefield_widget_validate($element, &$form_state) {
1bffec9e 293
ecbb2c26 294}
1bffec9e 295
d2f124d5 296function _filefield_widget_validate($element, &$form_state) {
d2f124d5
DP
297}
298
299
d2f124d5
DP
300/**
301 * FormAPI theme function. Theme the output of an image field.
302 */
bad2b17f 303function theme_filefield_widget($element) {
c6e8be79 304 return theme('form_element', $element, $element['#children']);
d2f124d5
DP
305}
306
22e736e6 307function theme_filefield_widget_preview($item) {
3897ed27
NH
308 return '<div class="filename">'. theme('filefield_file', $item) .'</div>'.
309 '<div class="filesize">'. format_size($item['filesize']) .'</div>'.
310 '<div class="filemime">'. $item['filemime'] .'</div>';
22e736e6
DP
311}
312
313function theme_filefield_widget_item($element) {
3897ed27 314 // Put the upload button directly after the upload field.
ad98c19d 315 $element['upload']['#field_suffix'] = drupal_render($element['filefield_upload']);
3897ed27
NH
316 $element['upload']['#theme'] = 'filefield_widget_file';
317
318 $output = '';
319 $output .= '<div class="filefield-element clear-block">';
320
321 if ($element['fid']['#value'] != 0) {
322 $output .= '<div class="filefield-preview">';
323 $output .= drupal_render($element['preview']);
324 $output .= '</div>';
325 }
326
327 $output .= '<div class="filefield-edit">';
328 $output .= drupal_render($element);
329 $output .= '</div>';
330 $output .= '</div>';
331
332 return $output;
333}
334
335/**
336 * Render the #field_suffix for file elements.
337 */
338function theme_filefield_widget_file($element) {
339 $output = '';
340
341 $output .= '<div class="filefield-upload clear-block">';
342
343 if (isset($element['#field_prefix'])) {
344 $output .= $element['#field_prefix'];
345 }
346
347 _form_set_class($element, array('form-file'));
348 $output .= '<input type="file" name="'. $element['#name'] .'"'. ($element['#attributes'] ? ' '. drupal_attributes($element['#attributes']) : '') .' id="'. $element['#id'] .'" size="'. $element['#size'] ."\" />\n";
349
350 if (isset($element['#field_suffix'])) {
351 $output .= $element['#field_suffix'];
352 }
353
354 $output .= '</div>';
355
356 return theme('form_element', $element, $output);
bad2b17f 357}
d2f124d5 358
2be7e4b0
DP
359/**
360 * #require validation for filetype fields.
361 */
362
363function filefield_node_form_validate($form, &$form_state) {
2be7e4b0
DP
364 $type = content_types($form['type']['#value']);
365 foreach ($type['fields'] as $field_name => $field) {
b6644f47 366 if (!(in_array($field['module'], array('imagefield', 'filefield')))) continue;
2be7e4b0
DP
367 $empty = $field['module'] .'_content_is_empty';
368 $valid = false;
f92ea317 369 $total_filesize = 0;
9b86f834
NH
370 if (!empty($form_state['values'][$field_name])) {
371 foreach($form_state['values'][$field_name] as $delta => $item) {
372 if ($empty($item, $field)) continue;
373 $valid = TRUE;
374 $total_filesize += (int)$item['filesize'];
375 }
2be7e4b0 376 }
9412a7cc 377
b6644f47 378 if (!$valid && $field['required']) {
f92ea317
DP
379 form_set_error($field_name, t('%title field is required.', array('%title' => $field['widget']['label'])));
380 }
381 $max_filesize = parse_size($field['widget']['max_filesize_per_node']);
5af49125 382 if ($max_filesize && $total_filesize > $max_filesize) {
7e784adc 383 form_set_error($field_name, t('Total filesize for %title, %tsize, exceeds field settings of %msize.',
f92ea317 384 array(
7e784adc 385 '%title' => $field['widget']['label'],
386 '%tsize' => format_size($total_filesize),
f92ea317
DP
387 '%msize' => format_size($max_filesize)
388 )
389 ));
390 }
2be7e4b0
DP
391 }
392}
9412a7cc 393
0ed57194 394function filefield_node_form_submit($form, &$form_state) {
ad6c5ff0
DP
395 // we ignore all but the save button here.
396 if ($form_state['values']['op'] != t('Save')) {
397 return;
398 }
399 // @todo: try to delete removed files.
400}