#349319 by Rob Loach: JavaScript on every page.
[project/filefield.git] / filefield_widget.inc
CommitLineData
9412a7cc
JP
1<?php
2// $Id$
3/**
4 * @file
5 * FileField: Defines a CCK file field type.
6 *
7 * Uses content.module to store the fid and field specific metadata,
8 * and Drupal's {files} table to store the actual file data.
9 *
10 * This file contains CCK widget related functionality.
11 */
12
22e736e6 13/**
7e784adc 14 * @file
15 *
22e736e6 16 * FileField Widget Settings Hooks.
6f0f10d4
DP
17 * @todo: move description property to filefield_widget widget callbacks
18 * (filefield_widget_widget_{$op}).
22e736e6
DP
19 */
20
21function filefield_widget_settings_form($widget) {
22 $form = array();
23 $form['file_extensions'] = array(
24 '#type' => 'textfield',
25 '#title' => t('Permitted upload file extensions'),
26 '#default_value' => is_string($widget['file_extensions']) ? $widget['file_extensions'] : 'txt',
27 '#size' => 64,
28 '#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.'),
29 '#weight' => 1,
30 );
31 $form['path_settings'] = array(
32 '#type' => 'fieldset',
33 '#title' => t('Path settings'),
34 '#collapsible' => true,
35 '#collapsed' => true,
36 '#weight' => 6,
37 );
38
39 $form['path_settings']['file_path'] = array(
40 '#type' => 'textfield',
41 '#title' => t('File path'),
42 '#default_value' => is_string($widget['file_path']) ? $widget['file_path'] : '',
43 '#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'))),
44 '#element_validate' => array('_filefield_widget_settings_file_path_validate'),
45 '#suffix' => theme('token_help', 'user'),
46 );
47
48 $form['max_filesize'] = array(
49 '#type' => 'fieldset',
50 '#title' => t('File size restrictions'),
51 '#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.'),
52 '#weight' => 6,
53 '#collapsible' => TRUE,
54 '#collapsed' => TRUE,
55 );
7e784adc 56 // upload validator. @todo: consider replacing with global
d6caa409 57 // && node validate.
22e736e6
DP
58 $form['max_filesize']['max_filesize_per_file'] = array(
59 '#type' => 'textfield',
60 '#title' => t('Maximum upload size per file'),
61 '#default_value' => is_string($widget['max_filesize_per_file'])
62 ? $widget['max_filesize_per_file']
63 : '',
a2db4a45 64 '#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
65 '#element_validate' => array('_filefield_widget_settings_max_filesize_per_file_validate'),
66 );
d6caa409
DP
67
68 // node validate.
22e736e6
DP
69 $form['max_filesize']['max_filesize_per_node'] = array(
70 '#type' => 'textfield',
71 '#title' => t('Maximum upload size per node'),
72 '#default_value' => is_string($widget['max_filesize_per_node'])
73 ? $widget['max_filesize_per_node']
74 : '',
75 '#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.'),
76 '#element_validate' => array('_filefield_widget_settings_max_filesize_per_node_validate'),
77 );
78 return $form;
79}
80
81function filefield_widget_settings_save($widget) {
82 return array(
83 'file_extensions', 'file_path', 'max_filesize_per_file',
84 'max_filesize_per_node', 'file_widgets'
85 );
86}
87
88function _filefield_widget_settings_file_path_validate($element, &$form_state) {
89 // Strip slashes from the beginning and end of $widget['file_path']
90 $form_state['values']['file_path'] = trim($form_state['values']['file_path'], '\\/');
91}
92
93function _filefield_widget_settings_max_filesize_per_file_validate($element, &$form_state) {
94 if (empty($form_state['values']['max_filesize_per_file'])) {
95 return; // Empty means no size restrictions, so don't throw an error.
96 }
97 else if (!is_numeric(parse_size($form_state['values']['max_filesize_per_file']))) {
98 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).'));
99 }
100}
101
102function _filefield_widget_settings_max_filesize_per_node_validate($element, &$form_state) {
103 if (empty($form_state['values']['max_filesize_per_node'])) {
104 return; // Empty means no size restrictions, so don't throw an error.
105 }
106 else if (!is_numeric(parse_size($form_state['values']['max_filesize_per_node']))) {
107 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).'));
108 }
109}
110
bf602673 111/**
112 * Determine the widget's files directory
113 *
114 * @param $field CCK field
115 * @return files directory path.
116 */
117function filefield_widget_file_path($field_instance) {
118 $dest = $field_instance['widget']['file_path'];
119 if (module_exists('token')) {
120 global $user;
121 $dest = token_replace($dest, 'user', $user);
122 }
123
124 return file_directory_path() .'/'. $dest;
125}
126
8ffd8e3d
DP
127function filefield_save_upload($element) {
128 $upload_name = $element['#field_name'] .'_'. $element['#delta'];
129 $field_instance = content_fields($element['#field_name'], $element['#type_name']);
130
131 if (empty($_FILES['files']['name'][$upload_name])) {
132 return 0;
133 }
134
bf602673 135 $dest = filefield_widget_file_path($field_instance);
8ffd8e3d 136 if (!field_file_check_directory($dest, FILE_CREATE_DIRECTORY)) {
32984cd2 137 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
138 form_set_error($upload_name, t('The file could not be uploaded.'));
139 return 0;
140 }
141
142 if (!$file = field_file_save_upload($upload_name, $element['#upload_validators'], $dest)) {
32984cd2 143 watchdog('filefield', 'The file upload failed. %upload', array('%upload' => $upload_name));
8ffd8e3d
DP
144 form_set_error($upload_name, t('The Image upload failed.'));
145 return 0;
146 }
147 return $file['fid'];
148}
149
22e736e6
DP
150/**
151 * FileField widget element callbacks.
152 */
d2f124d5 153function filefield_widget_value($element, $edit = FALSE) {
7e784adc 154 if (!$edit) {
d2f124d5
DP
155 $file = field_file_load($element['#default_value']['fid']);
156 $item = $element['#default_value'];
157 }
158 else {
8ffd8e3d
DP
159 // uploads take priority over value of fid text field.
160 if ($fid = filefield_save_upload($element)) {
161 $edit['fid'] = $fid;
7e784adc 162 }
8ffd8e3d
DP
163
164 // load file.
d2f124d5
DP
165 $file = field_file_load($edit['fid']);
166 $item = array(
167 'fid' => $edit['fid'],
86e371d9 168 'list' => !empty($edit['list']),
3d3b3ed1 169 'data' => empty($edit['data']) ? array() : $edit['data'],
d2f124d5
DP
170 );
171 }
8ffd8e3d 172 // merge file and item data so it is available to all widgets.
6e7782b4 173 $item = array_merge($item, $file);
1bffec9e 174
6e7782b4
DP
175 // if this widget is another type and leaning on filefield to do the dirty work....
176 // pass it back home.
177 $function = $element['#type'] .'_widget_value';
178 if (function_exists($function)) {
179 $item = array_merge($item, $function($element, $edit));
180 }
181 return $item;
d2f124d5
DP
182}
183
184function filefield_widget_process($element, $edit, &$form_state, $form) {
9c0b75b0 185 // The widget is being presented, so apply the JavaScript.
186 drupal_add_js(drupal_get_path('module', 'filefield') .'/filefield.js');
7e784adc 187
8ffd8e3d
DP
188 $item = $element['#value'];
189 $field_name = $element['#field_name'];
190 $delta = $element['#delta'];
191
ad6c5ff0
DP
192 $field = content_fields($element['#field_name'], $element['#type_name']);
193
8ffd8e3d
DP
194 // check remove buttons...
195 $remove_name = $element['#field_name'] .'_'. $element['#delta'] .'_remove_btn';
ad6c5ff0
DP
196 if (isset($form_state['clicked_button']) && $form_state['clicked_button']['#name'] == $remove_name) {
197 $item = _filefield_default_value($field);
8ffd8e3d 198 }
7e784adc 199
200 // figute out our fid...
8ffd8e3d 201 $element['fid'] = array('#type' => 'hidden', '#value' => $item['fid']);
d2f124d5 202
6e7782b4 203 if ($item['fid'] != 0) {
7e784adc 204 $element['preview'] = array('#type' => 'markup', '#value' => theme($element['#type'] .'_preview', $item));
d2f124d5 205 }
28b1c58e
DP
206
207 // placeholder.. will be serialized into the data column. this is a place for widgets
208 // to put additional data.
209 $element['data'] = array('#tree' => 'true');
2635f5af 210
211 if ($field['show_description']) {
212 $element['data']['description'] = array(
213 '#type' => 'textfield',
214 '#title' => t('Description'),
215 '#value' => isset($item['data']['description']) ? $item['data']['description'] : '',
216 );
217 }
28b1c58e
DP
218
219 if ($field['force_list_default']) {
220 $element['list'] = array(
221 '#type' => 'hidden',
222 '#value' => $field['list_default'],
223 );
224 }
225 else {
226 $element['list'] = array(
227 '#type' => 'checkbox',
228 '#title' => t('List'),
8ffd8e3d 229 '#value' => isset($item['list']) ? $item['list'] : $field['list_default'],
28b1c58e
DP
230 '#attributes' => array('class' => 'filefield-list'),
231 );
232 }
d2f124d5 233
d2f124d5 234
8ffd8e3d
DP
235
236 foreach ($element['#upload_validators'] as $callback => $arguments) {
237 $help_func = $callback .'_help';
238 $desc[] = call_user_func_array($help_func, $arguments);
239 }
240 $element['upload'] = array(
241 '#name' => 'files['. $element['#field_name'] .'_'. $element['#delta'] .']',
242 '#type' => 'file',
243 '#title' => t('New Upload'),
244 '#description' => implode('<br />', $desc),
245 '#attributes' => array(
f976317f 246 'accept' => implode(',', array_filter(explode(' ', $field['widget']['file_extensions']))),
8ffd8e3d
DP
247 )
248 );
249
250
251 if ($item['fid'] != 0) {
252 $element['upload']['#title'] = t('Replace');
253 }
aa3a8ca0
DP
254
255 $element['#prefix'] = '<div id="'. $element['#id'] .'-ahah-wrapper" class="filefield-ahah-wrapper">';
256 $element['#suffix'] = '</div>';
8ffd8e3d 257 $element['upload_btn'] = array(
7e784adc 258 '#type' => 'submit',
8ffd8e3d 259 '#value' => t('Upload'),
aa3a8ca0 260 '#process' => array('form_expand_ahah'),
8ffd8e3d 261 '#submit' => array('node_form_submit_build_node'),
7e784adc 262 '#ahah' => array( // with JavaScript
263 'path' => 'filefield/ahah/'. $element['#type_name'] .'/'. $element['#field_name'] .'/'. $element['#delta'],
264 'wrapper' => $element['#id'] .'-ahah-wrapper',
265 'method' => 'replace',
266 'effect' => 'fade',
8ffd8e3d
DP
267 ),
268 '#field_name' => $element['#field_name'],
269 '#delta' => $element['#delta'],
270 '#type_name' => $element['#type_name'],
271 '#upload_validators' => $element['#upload_validators'],
272 );
6e7782b4 273 if ($item['fid'] != 0) {
d2f124d5 274 $element['remove_btn'] = array(
6e7782b4 275 '#name' => $element['#field_name'] .'_'. $element['#delta'] .'_remove_btn',
7e784adc 276 '#type' => 'submit',
d2f124d5 277 '#value' => t('Remove'),
8ffd8e3d 278 '#process' => array('filefield_widget_process_remove_btn', 'form_expand_ahah'),
ad6c5ff0 279 '#submit' => array('node_form_submit_build_node'),
7e784adc 280 '#ahah' => array( // with JavaScript
281 'path' => 'filefield/ahah/'. $element['#type_name'] .'/'. $element['#field_name'] .'/'. $element['#delta'],
fe9499a1 282 'wrapper' => $element['#id'] .'-ahah-wrapper',
7e784adc 283 'method' => 'replace',
284 'effect' => 'fade',
6c352642 285 ),
6e7782b4
DP
286 '#field_name' => $element['#field_name'],
287 '#delta' => $element['#delta'],
d2f124d5 288 );
d2f124d5
DP
289 }
290
d2f124d5 291
d2f124d5
DP
292 return $element;
293}
294
ac83a9cf 295function filefield_widget_validate($element, &$form_state) {
1bffec9e 296
ecbb2c26 297}
1bffec9e 298
d2f124d5 299function _filefield_widget_validate($element, &$form_state) {
d2f124d5
DP
300}
301
302
d2f124d5
DP
303/**
304 * FormAPI theme function. Theme the output of an image field.
305 */
bad2b17f 306function theme_filefield_widget($element) {
c6e8be79 307 return theme('form_element', $element, $element['#children']);
d2f124d5
DP
308}
309
22e736e6 310function theme_filefield_widget_preview($item) {
c4c7e990
DP
311 return '<div class="filefield-preview clear-block">'.
312 '<div class="filename">'. theme('filefield_file', $item) .'</div>'.
313 '<div class="filesize">'. format_size($item['filesize']) .'</div>'.
314 '<div class="filemime">'. $item['filemime'] .'</div>'.
315 '</div>';
22e736e6
DP
316}
317
318function theme_filefield_widget_item($element) {
bad2b17f 319 return '<div class="filefield-row clear-block">'.
c4c7e990 320 '<div class="filefield-filename clear-block">'. drupal_render($element['preview']) . '</div>'.
6165b4f0 321 '<div class="fllefield-edit clear-block">'.
bad2b17f
DP
322 '<div class="filefield-list">'. drupal_render($element['list']) . '</div>' .
323 '<div class="filefield-description">'. drupal_render($element['description']) . '</div>' .
324 '<div class="filefield-stuff">'. drupal_render($element) .'</div>'.
325 '</div>'.
326 '</div>';
327}
d2f124d5 328
2be7e4b0
DP
329/**
330 * #require validation for filetype fields.
331 */
332
333function filefield_node_form_validate($form, &$form_state) {
2be7e4b0
DP
334 $type = content_types($form['type']['#value']);
335 foreach ($type['fields'] as $field_name => $field) {
b6644f47 336 if (!(in_array($field['module'], array('imagefield', 'filefield')))) continue;
2be7e4b0
DP
337 $empty = $field['module'] .'_content_is_empty';
338 $valid = false;
f92ea317 339 $total_filesize = 0;
2be7e4b0
DP
340 foreach($form_state['values'][$field_name] as $delta => $item) {
341 if ($empty($item, $field)) continue;
3d3b3ed1 342 $valid = true;
f92ea317 343 $total_filesize += (int)$item['filesize'];
2be7e4b0 344 }
9412a7cc 345
b6644f47 346 if (!$valid && $field['required']) {
f92ea317
DP
347 form_set_error($field_name, t('%title field is required.', array('%title' => $field['widget']['label'])));
348 }
349 $max_filesize = parse_size($field['widget']['max_filesize_per_node']);
5af49125 350 if ($max_filesize && $total_filesize > $max_filesize) {
7e784adc 351 form_set_error($field_name, t('Total filesize for %title, %tsize, exceeds field settings of %msize.',
f92ea317 352 array(
7e784adc 353 '%title' => $field['widget']['label'],
354 '%tsize' => format_size($total_filesize),
f92ea317
DP
355 '%msize' => format_size($max_filesize)
356 )
357 ));
358 }
2be7e4b0
DP
359 }
360}
9412a7cc 361
0ed57194 362function filefield_node_form_submit($form, &$form_state) {
ad6c5ff0
DP
363 // we ignore all but the save button here.
364 if ($form_state['values']['op'] != t('Save')) {
365 return;
366 }
367 // @todo: try to delete removed files.
368}