#516104 by nonzero: Slightly shorter IF statement for file download access check.
[project/filefield.git] / filefield_field.inc
1 <?php
2 // $Id$
3
4 /**
5 * @file
6 * FileField CCK field hooks and callbacks.
7 */
8
9 /**
10 * Implementation of CCK's hook_field_settings($op = 'form').
11 */
12 function filefield_field_settings_form($field) {
13 drupal_add_js(drupal_get_path('module', 'filefield') .'/filefield.js');
14
15 $form = array();
16
17 $form['list_field'] = array(
18 '#type' => 'radios',
19 '#title' => t('List field'),
20 '#options' => array(0 => t('Disabled'), 1 => t('Enabled')),
21 '#default_value' => $field['list_field'] === '' ? 0 : (int) $field['list_field'],
22 '#description' => t('The "list" option lets a user choose if a file should shown in a list when viewing the content after creation.'),
23 '#attributes' => array('class' => 'filefield-list-field'),
24 );
25 $form['list_default'] = array(
26 '#type' => 'checkbox',
27 '#title' => t('Files listed by default'),
28 '#default_value' => $field['list_default'] === '' ? 1 : (int) $field['list_default'],
29 );
30 $form['description_field'] = array(
31 '#type' => 'radios',
32 '#title' => t('Description field'),
33 '#default_value' => $field['description_field'] === '' ? 0 : (int) $field['description_field'],
34 '#options' => array(0 => t('Disabled'), 1 => t('Enabled')),
35 '#description' => t('When enabled, will display a text field where users may enter a description about the uploaded file.'),
36 );
37
38 return $form;
39 }
40
41 /**
42 * Implementation of CCK's hook_field_settings($op = 'save').
43 */
44 function filefield_field_settings_save($field) {
45 return array('list_field', 'list_default', 'description_field');
46 }
47
48 /**
49 * Implementation of CCK's hook_field_settings($op = 'database_columns').
50 */
51 function filefield_field_settings_database_columns($field) {
52 return array(
53 'fid' => array('type' => 'int', 'not null' => FALSE, 'views' => TRUE),
54 'list' => array('type' => 'int', 'size' => 'tiny', 'not null' => FALSE, 'views' => TRUE),
55 'data' => array('type' => 'text', 'serialize' => TRUE, 'views' => TRUE),
56 );
57 }
58
59 /**
60 * Implementation of CCK's hook_field_settings($op = 'views_data').
61 */
62 function filefield_field_settings_views_data($field) {
63 $data = content_views_field_views_data($field);
64 $db_info = content_database_info($field);
65 $table_alias = content_views_tablename($field);
66
67 // By defining the relationship, we already have a "Has file" filter
68 // plus all the filters that Views already provides for files.
69 // No need for having a filter by ourselves.
70 unset($data[$table_alias][$field['field_name'] .'_fid']['filter']);
71
72 // Add a relationship for related file.
73 $data[$table_alias][$field['field_name'] .'_fid']['relationship'] = array(
74 'base' => 'files',
75 'field' => $db_info['columns']['fid']['column'],
76 'handler' => 'content_handler_relationship',
77 'label' => t($field['widget']['label']),
78 'content_field_name' => $field['field_name'],
79 );
80
81 // Use the Views boolean handler for filtering the list value.
82 $data[$table_alias][$field['field_name'] .'_list']['filter']['handler'] = 'views_handler_filter_boolean_operator';
83
84 // Add our special handler for serialized data.
85 $data[$table_alias][$field['field_name'] .'_data']['field'] = array(
86 'title' => $data[$table_alias][$field['field_name'] .'_data']['title'],
87 'title short' => $data[$table_alias][$field['field_name'] .'_data']['title short'],
88 'field' => $db_info['columns']['data']['column'],
89 'table' => $db_info['table'],
90 'handler' => 'filefield_handler_field_data',
91 'click sortable' => FALSE,
92 'access callback' => 'content_access',
93 'access arguments' => array('view', $field),
94 );
95
96 // Remove handlers that are probably unnecessary.
97 unset($data[$table_alias][$field['field_name'] .'_data']['filter']);
98 unset($data[$table_alias][$field['field_name'] .'_data']['argument']);
99 unset($data[$table_alias][$field['field_name'] .'_list']['argument']);
100
101 return $data;
102 }
103
104 /**
105 * Implementation of CCK's hook_field($op = 'load').
106 */
107 function filefield_field_load($node, $field, &$items, $teaser, $page) {
108 if (empty($items)) {
109 return array();
110 }
111 foreach ($items as $delta => $item) {
112 // Despite hook_content_is_empty(), CCK still doesn't filter out
113 // empty items from $op = 'load', so we need to do that ourselves.
114 if (empty($item['fid']) || !($file = field_file_load($item['fid']))) {
115 $items[$delta] = NULL;
116 }
117 else {
118 $item['data'] = unserialize($item['data']);
119 // Temporary fix to unserialize data serialized multiple times.
120 // See the FileField issue http://drupal.org/node/402860.
121 // And the CCK issue http://drupal.org/node/407446.
122 while (!empty($item['data']) && is_string($item['data'])) {
123 $item['data'] = unserialize($item['data']);
124 }
125 $items[$delta] = array_merge($item, $file);
126 }
127 }
128 return array($field['field_name'] => $items);
129 }
130
131 /**
132 * Implementation of CCK's hook_field($op = 'insert').
133 */
134 function filefield_field_insert($node, $field, &$items, $teaser, $page) {
135 return filefield_field_update($node, $field, $items, $teaser, $page);
136 }
137
138 /**
139 * Implementation of CCK's hook_field($op = 'update').
140 */
141 function filefield_field_update($node, $field, &$items, $teaser, $page) {
142
143 // Accumulator to gather current fid to compare with the original node
144 // for deleting replaced files.
145 $curfids = array();
146 foreach ($items as $delta => $item) {
147 $items[$delta] = field_file_save($node, $item);
148 // Remove items from the array if they have been deleted.
149 if (empty($items[$delta]) || empty($items[$delta]['fid'])) {
150 $items[$delta] = NULL;
151 }
152 else {
153 $curfids[] = $items[$delta]['fid'];
154 }
155 }
156
157 // If this is a new node there are no old items to worry about.
158 // On new revisions, old files are always maintained in the previous revision.
159 if ($node->is_new || !empty($node->revision)) {
160 return;
161 }
162
163 // Delete items from original node.
164 $orig = node_load($node->nid);
165 // If there are, figure out which ones must go.
166 if (!empty($orig->$field['field_name'])) {
167 foreach ($orig->$field['field_name'] as $oitem) {
168 if (isset($oitem['fid']) && !in_array($oitem['fid'], $curfids)) {
169 // For hook_file_references, remember that this is being deleted.
170 $oitem['field_name'] = $field['field_name'];
171 filefield_field_delete_file($oitem, $field);
172 }
173 }
174 }
175 }
176
177 /**
178 * Implementation of CCK's hook_field($op = 'delete_revision').
179 */
180 function filefield_field_delete_revision($node, $field, &$items, $teaser, $page) {
181 foreach ($items as $delta => $item) {
182 // For hook_file_references, remember that this is being deleted.
183 $item['field_name'] = $field['field_name'];
184 if (filefield_field_delete_file($item, $field)) {
185 $items[$delta] = NULL;
186 }
187 }
188 }
189
190 /**
191 * Implementation of CCK's hook_field($op = 'delete').
192 */
193 function filefield_field_delete($node, $field, &$items, $teaser, $page) {
194 foreach ($items as $delta => $item) {
195 // For hook_file_references(), remember that this is being deleted.
196 $item['field_name'] = $field['field_name'];
197 // Pass in the nid of the node that is being removed so all references can
198 // be counted in hook_file_references().
199 $item['delete_nid'] = $node->nid;
200 filefield_field_delete_file($item, $field);
201 }
202 }
203
204 /**
205 * Check that FileField controls a file before attempting to deleting it.
206 */
207 function filefield_field_delete_file($file, $field) {
208 $file = (object) $file;
209
210 // Remove the field_name and delete_nid properties so that references can be
211 // counted including the files to be deleted.
212 $field_name = isset($file->field_name) ? $file->field_name : NULL;
213 $delete_nid = isset($file->delete_nid) ? $file->delete_nid : NULL;
214 unset($file->field_name, $file->delete_nid);
215
216 // To prevent FileField from deleting files it doesn't know about, check the
217 // FileField reference count. Temporary files can be deleted because they
218 // are not yet associated with any content at all.
219 if ($file->status == 0 || filefield_get_file_reference_count($file, $field) > 0) {
220 $file->field_name = $field_name;
221 $file->delete_nid = $delete_nid;
222 return field_file_delete($file);
223 }
224
225 // Even if the file is not deleted, return TRUE to indicate the FileField
226 // record can be removed from the FileField database tables.
227 return TRUE;
228 }
229
230 /**
231 * Implementation of CCK's hook_field($op = 'sanitize').
232 */
233 function filefield_field_sanitize($node, $field, &$items, $teaser, $page) {
234 foreach ($items as $delta => $item) {
235 // Cleanup $items during node preview.
236 if (empty($item['fid']) || !empty($item['delete'])) {
237 // Check for default images at the widget level.
238 // TODO: Provide an API to ImageField to do this itself?
239 if (!empty($field['widget']['use_default_image']) && !empty($field['widget']['default_image']['filepath'])) {
240 $items[$delta] = $field['widget']['default_image'];
241 $items[$delta]['default'] = TRUE;
242 }
243 else {
244 $items[$delta] = NULL;
245 continue;
246 }
247 }
248 // Load the complete file if a filepath is not available.
249 if (!empty($item['fid']) && empty($item['filepath'])) {
250 $items[$delta] = array_merge($item, field_file_load($item['fid']));
251 }
252 // Add nid so formatters can create a link to the node.
253 $items[$delta]['nid'] = $node->nid;
254
255 // TODO: This is only necessary for Views, which doesn't call the "load"
256 // $op. It might be preferable to move this to Views integration somehow.
257 if (!empty($items['data']) && is_string($items[$delta]['data'])) {
258 $item['data'] = unserialize($item['data']);
259 }
260 // Temporary fix to unserialize data serialized multiple times.
261 // See the FileField issue http://drupal.org/node/402860.
262 // And the CCK issue http://drupal.org/node/407446.
263 while (!empty($items[$delta]['data']) && is_string($items[$delta]['data'])) {
264 $items[$delta]['data'] = unserialize($items[$delta]['data']);
265 }
266
267 // Verify the file exists on the server.
268 if (!empty($item['filepath']) && !file_exists($item['filepath'])) {
269 watchdog('filefield', 'FileField was trying to display the file %file, but it does not exist.', array('%file' => $item['filepath']));
270 }
271 }
272 }