|
1 <?php |
|
2 |
|
3 /** |
|
4 * @file |
|
5 * Tests for file.module. |
|
6 */ |
|
7 |
|
8 /** |
|
9 * Provides methods specifically for testing File module's field handling. |
|
10 */ |
|
11 class FileFieldTestCase extends DrupalWebTestCase { |
|
12 protected $admin_user; |
|
13 |
|
14 function setUp() { |
|
15 // Since this is a base class for many test cases, support the same |
|
16 // flexibility that DrupalWebTestCase::setUp() has for the modules to be |
|
17 // passed in as either an array or a variable number of string arguments. |
|
18 $modules = func_get_args(); |
|
19 if (isset($modules[0]) && is_array($modules[0])) { |
|
20 $modules = $modules[0]; |
|
21 } |
|
22 $modules[] = 'file'; |
|
23 $modules[] = 'file_module_test'; |
|
24 parent::setUp($modules); |
|
25 $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer users', 'administer permissions', 'administer content types', 'administer nodes', 'bypass node access', 'administer fields')); |
|
26 $this->drupalLogin($this->admin_user); |
|
27 } |
|
28 |
|
29 /** |
|
30 * Retrieves a sample file of the specified type. |
|
31 */ |
|
32 function getTestFile($type_name, $size = NULL) { |
|
33 // Get a file to upload. |
|
34 $file = current($this->drupalGetTestFiles($type_name, $size)); |
|
35 |
|
36 // Add a filesize property to files as would be read by file_load(). |
|
37 $file->filesize = filesize($file->uri); |
|
38 |
|
39 return $file; |
|
40 } |
|
41 |
|
42 /** |
|
43 * Retrieves the fid of the last inserted file. |
|
44 */ |
|
45 function getLastFileId() { |
|
46 return (int) db_query('SELECT MAX(fid) FROM {file_managed}')->fetchField(); |
|
47 } |
|
48 |
|
49 /** |
|
50 * Creates a new file field. |
|
51 * |
|
52 * @param $name |
|
53 * The name of the new field (all lowercase), exclude the "field_" prefix. |
|
54 * @param $type_name |
|
55 * The node type that this field will be added to. |
|
56 * @param $field_settings |
|
57 * A list of field settings that will be added to the defaults. |
|
58 * @param $instance_settings |
|
59 * A list of instance settings that will be added to the instance defaults. |
|
60 * @param $widget_settings |
|
61 * A list of widget settings that will be added to the widget defaults. |
|
62 */ |
|
63 function createFileField($name, $type_name, $field_settings = array(), $instance_settings = array(), $widget_settings = array()) { |
|
64 $field = array( |
|
65 'field_name' => $name, |
|
66 'type' => 'file', |
|
67 'settings' => array(), |
|
68 'cardinality' => !empty($field_settings['cardinality']) ? $field_settings['cardinality'] : 1, |
|
69 ); |
|
70 $field['settings'] = array_merge($field['settings'], $field_settings); |
|
71 field_create_field($field); |
|
72 |
|
73 $this->attachFileField($name, 'node', $type_name, $instance_settings, $widget_settings); |
|
74 } |
|
75 |
|
76 /** |
|
77 * Attaches a file field to an entity. |
|
78 * |
|
79 * @param $name |
|
80 * The name of the new field (all lowercase), exclude the "field_" prefix. |
|
81 * @param $entity_type |
|
82 * The entity type this field will be added to. |
|
83 * @param $bundle |
|
84 * The bundle this field will be added to. |
|
85 * @param $field_settings |
|
86 * A list of field settings that will be added to the defaults. |
|
87 * @param $instance_settings |
|
88 * A list of instance settings that will be added to the instance defaults. |
|
89 * @param $widget_settings |
|
90 * A list of widget settings that will be added to the widget defaults. |
|
91 */ |
|
92 function attachFileField($name, $entity_type, $bundle, $instance_settings = array(), $widget_settings = array()) { |
|
93 $instance = array( |
|
94 'field_name' => $name, |
|
95 'label' => $name, |
|
96 'entity_type' => $entity_type, |
|
97 'bundle' => $bundle, |
|
98 'required' => !empty($instance_settings['required']), |
|
99 'settings' => array(), |
|
100 'widget' => array( |
|
101 'type' => 'file_generic', |
|
102 'settings' => array(), |
|
103 ), |
|
104 ); |
|
105 $instance['settings'] = array_merge($instance['settings'], $instance_settings); |
|
106 $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings); |
|
107 field_create_instance($instance); |
|
108 } |
|
109 |
|
110 /** |
|
111 * Updates an existing file field with new settings. |
|
112 */ |
|
113 function updateFileField($name, $type_name, $instance_settings = array(), $widget_settings = array()) { |
|
114 $instance = field_info_instance('node', $name, $type_name); |
|
115 $instance['settings'] = array_merge($instance['settings'], $instance_settings); |
|
116 $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings); |
|
117 |
|
118 field_update_instance($instance); |
|
119 } |
|
120 |
|
121 /** |
|
122 * Uploads a file to a node. |
|
123 */ |
|
124 function uploadNodeFile($file, $field_name, $nid_or_type, $new_revision = TRUE, $extras = array()) { |
|
125 $langcode = LANGUAGE_NONE; |
|
126 $edit = array( |
|
127 "title" => $this->randomName(), |
|
128 'revision' => (string) (int) $new_revision, |
|
129 ); |
|
130 |
|
131 if (is_numeric($nid_or_type)) { |
|
132 $nid = $nid_or_type; |
|
133 } |
|
134 else { |
|
135 // Add a new node. |
|
136 $extras['type'] = $nid_or_type; |
|
137 $node = $this->drupalCreateNode($extras); |
|
138 $nid = $node->nid; |
|
139 // Save at least one revision to better simulate a real site. |
|
140 $this->drupalCreateNode(get_object_vars($node)); |
|
141 $node = node_load($nid, NULL, TRUE); |
|
142 $this->assertNotEqual($nid, $node->vid, 'Node revision exists.'); |
|
143 } |
|
144 |
|
145 // Attach a file to the node. |
|
146 $edit['files[' . $field_name . '_' . $langcode . '_0]'] = drupal_realpath($file->uri); |
|
147 $this->drupalPost("node/$nid/edit", $edit, t('Save')); |
|
148 |
|
149 return $nid; |
|
150 } |
|
151 |
|
152 /** |
|
153 * Removes a file from a node. |
|
154 * |
|
155 * Note that if replacing a file, it must first be removed then added again. |
|
156 */ |
|
157 function removeNodeFile($nid, $new_revision = TRUE) { |
|
158 $edit = array( |
|
159 'revision' => (string) (int) $new_revision, |
|
160 ); |
|
161 |
|
162 $this->drupalPost('node/' . $nid . '/edit', array(), t('Remove')); |
|
163 $this->drupalPost(NULL, $edit, t('Save')); |
|
164 } |
|
165 |
|
166 /** |
|
167 * Replaces a file within a node. |
|
168 */ |
|
169 function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) { |
|
170 $edit = array( |
|
171 'files[' . $field_name . '_' . LANGUAGE_NONE . '_0]' => drupal_realpath($file->uri), |
|
172 'revision' => (string) (int) $new_revision, |
|
173 ); |
|
174 |
|
175 $this->drupalPost('node/' . $nid . '/edit', array(), t('Remove')); |
|
176 $this->drupalPost(NULL, $edit, t('Save')); |
|
177 } |
|
178 |
|
179 /** |
|
180 * Asserts that a file exists physically on disk. |
|
181 */ |
|
182 function assertFileExists($file, $message = NULL) { |
|
183 $message = isset($message) ? $message : format_string('File %file exists on the disk.', array('%file' => $file->uri)); |
|
184 $this->assertTrue(is_file($file->uri), $message); |
|
185 } |
|
186 |
|
187 /** |
|
188 * Asserts that a file exists in the database. |
|
189 */ |
|
190 function assertFileEntryExists($file, $message = NULL) { |
|
191 entity_get_controller('file')->resetCache(); |
|
192 $db_file = file_load($file->fid); |
|
193 $message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', array('%file' => $file->uri)); |
|
194 $this->assertEqual($db_file->uri, $file->uri, $message); |
|
195 } |
|
196 |
|
197 /** |
|
198 * Asserts that a file does not exist on disk. |
|
199 */ |
|
200 function assertFileNotExists($file, $message = NULL) { |
|
201 $message = isset($message) ? $message : format_string('File %file exists on the disk.', array('%file' => $file->uri)); |
|
202 $this->assertFalse(is_file($file->uri), $message); |
|
203 } |
|
204 |
|
205 /** |
|
206 * Asserts that a file does not exist in the database. |
|
207 */ |
|
208 function assertFileEntryNotExists($file, $message) { |
|
209 entity_get_controller('file')->resetCache(); |
|
210 $message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', array('%file' => $file->uri)); |
|
211 $this->assertFalse(file_load($file->fid), $message); |
|
212 } |
|
213 |
|
214 /** |
|
215 * Asserts that a file's status is set to permanent in the database. |
|
216 */ |
|
217 function assertFileIsPermanent($file, $message = NULL) { |
|
218 $message = isset($message) ? $message : format_string('File %file is permanent.', array('%file' => $file->uri)); |
|
219 $this->assertTrue($file->status == FILE_STATUS_PERMANENT, $message); |
|
220 } |
|
221 |
|
222 /** |
|
223 * Creates a temporary file, for a specific user. |
|
224 * |
|
225 * @param string $data |
|
226 * A string containing the contents of the file. |
|
227 * @param int $uid |
|
228 * The user ID of the file owner. |
|
229 * |
|
230 * @return object |
|
231 * A file object, or FALSE on error. |
|
232 */ |
|
233 function createTemporaryFile($data, $uid = NULL) { |
|
234 $file = file_save_data($data, NULL, NULL); |
|
235 |
|
236 if ($file) { |
|
237 $file->uid = isset($uid) ? $uid : $this->admin_user->uid; |
|
238 // Change the file status to be temporary. |
|
239 $file->status = NULL; |
|
240 return file_save($file); |
|
241 } |
|
242 |
|
243 return $file; |
|
244 } |
|
245 } |
|
246 |
|
247 /** |
|
248 * Tests adding a file to a non-node entity. |
|
249 */ |
|
250 class FileTaxonomyTermTestCase extends DrupalWebTestCase { |
|
251 protected $admin_user; |
|
252 |
|
253 public static function getInfo() { |
|
254 return array( |
|
255 'name' => 'Taxonomy term file test', |
|
256 'description' => 'Tests adding a file to a non-node entity.', |
|
257 'group' => 'File', |
|
258 ); |
|
259 } |
|
260 |
|
261 public function setUp() { |
|
262 $modules[] = 'file'; |
|
263 $modules[] = 'taxonomy'; |
|
264 parent::setUp($modules); |
|
265 $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer taxonomy')); |
|
266 $this->drupalLogin($this->admin_user); |
|
267 } |
|
268 |
|
269 /** |
|
270 * Creates a file field and attaches it to the "Tags" taxonomy vocabulary. |
|
271 * |
|
272 * @param $name |
|
273 * The field name of the file field to create. |
|
274 * @param $uri_scheme |
|
275 * The URI scheme to use for the file field (for example, "private" to |
|
276 * create a field that stores private files or "public" to create a field |
|
277 * that stores public files). |
|
278 */ |
|
279 protected function createAttachFileField($name, $uri_scheme) { |
|
280 $field = array( |
|
281 'field_name' => $name, |
|
282 'type' => 'file', |
|
283 'settings' => array( |
|
284 'uri_scheme' => $uri_scheme, |
|
285 ), |
|
286 'cardinality' => 1, |
|
287 ); |
|
288 field_create_field($field); |
|
289 // Attach an instance of it. |
|
290 $instance = array( |
|
291 'field_name' => $name, |
|
292 'label' => 'File', |
|
293 'entity_type' => 'taxonomy_term', |
|
294 'bundle' => 'tags', |
|
295 'required' => FALSE, |
|
296 'settings' => array(), |
|
297 'widget' => array( |
|
298 'type' => 'file_generic', |
|
299 'settings' => array(), |
|
300 ), |
|
301 ); |
|
302 field_create_instance($instance); |
|
303 } |
|
304 |
|
305 /** |
|
306 * Tests that a public file can be attached to a taxonomy term. |
|
307 * |
|
308 * This is a regression test for https://www.drupal.org/node/2305017. |
|
309 */ |
|
310 public function testTermFilePublic() { |
|
311 $this->_testTermFile('public'); |
|
312 } |
|
313 |
|
314 /** |
|
315 * Tests that a private file can be attached to a taxonomy term. |
|
316 * |
|
317 * This is a regression test for https://www.drupal.org/node/2305017. |
|
318 */ |
|
319 public function testTermFilePrivate() { |
|
320 $this->_testTermFile('private'); |
|
321 } |
|
322 |
|
323 /** |
|
324 * Runs tests for attaching a file field to a taxonomy term. |
|
325 * |
|
326 * @param $uri_scheme |
|
327 * The URI scheme to use for the file field, either "public" or "private". |
|
328 */ |
|
329 protected function _testTermFile($uri_scheme) { |
|
330 $field_name = strtolower($this->randomName()); |
|
331 $this->createAttachFileField($field_name, $uri_scheme); |
|
332 // Get a file to upload. |
|
333 $file = current($this->drupalGetTestFiles('text')); |
|
334 // Add a filesize property to files as would be read by file_load(). |
|
335 $file->filesize = filesize($file->uri); |
|
336 $langcode = LANGUAGE_NONE; |
|
337 $edit = array( |
|
338 "name" => $this->randomName(), |
|
339 ); |
|
340 // Attach a file to the term. |
|
341 $edit['files[' . $field_name . '_' . $langcode . '_0]'] = drupal_realpath($file->uri); |
|
342 $this->drupalPost("admin/structure/taxonomy/tags/add", $edit, t('Save')); |
|
343 // Find the term ID we just created. |
|
344 $tid = db_query_range('SELECT tid FROM {taxonomy_term_data} ORDER BY tid DESC', 0, 1)->fetchField(); |
|
345 $terms = entity_load('taxonomy_term', array($tid)); |
|
346 $term = $terms[$tid]; |
|
347 $fid = $term->{$field_name}[LANGUAGE_NONE][0]['fid']; |
|
348 // Check that the uploaded file is present on the edit form. |
|
349 $this->drupalGet("taxonomy/term/$tid/edit"); |
|
350 $file_input_name = $field_name . '[' . LANGUAGE_NONE . '][0][fid]'; |
|
351 $this->assertFieldByXpath('//input[@type="hidden" and @name="' . $file_input_name . '"]', $fid, 'File is attached on edit form.'); |
|
352 // Edit the term and change name without changing the file. |
|
353 $edit = array( |
|
354 "name" => $this->randomName(), |
|
355 ); |
|
356 $this->drupalPost("taxonomy/term/$tid/edit", $edit, t('Save')); |
|
357 // Check that the uploaded file is still present on the edit form. |
|
358 $this->drupalGet("taxonomy/term/$tid/edit"); |
|
359 $file_input_name = $field_name . '[' . LANGUAGE_NONE . '][0][fid]'; |
|
360 $this->assertFieldByXpath('//input[@type="hidden" and @name="' . $file_input_name . '"]', $fid, 'File is attached on edit form.'); |
|
361 // Load term while resetting the cache. |
|
362 $terms = entity_load('taxonomy_term', array($tid), array(), TRUE); |
|
363 $term = $terms[$tid]; |
|
364 $this->assertTrue(!empty($term->{$field_name}[LANGUAGE_NONE]), 'Term has attached files.'); |
|
365 $this->assertEqual($term->{$field_name}[LANGUAGE_NONE][0]['fid'], $fid, 'Same File ID is attached to the term.'); |
|
366 } |
|
367 } |
|
368 |
|
369 /** |
|
370 * Tests the 'managed_file' element type. |
|
371 * |
|
372 * @todo Create a FileTestCase base class and move FileFieldTestCase methods |
|
373 * that aren't related to fields into it. |
|
374 */ |
|
375 class FileManagedFileElementTestCase extends FileFieldTestCase { |
|
376 public static function getInfo() { |
|
377 return array( |
|
378 'name' => 'Managed file element test', |
|
379 'description' => 'Tests the managed_file element type.', |
|
380 'group' => 'File', |
|
381 ); |
|
382 } |
|
383 |
|
384 /** |
|
385 * Tests the managed_file element type. |
|
386 */ |
|
387 function testManagedFile() { |
|
388 // Check that $element['#size'] is passed to the child upload element. |
|
389 $this->drupalGet('file/test'); |
|
390 $this->assertFieldByXpath('//input[@name="files[nested_file]" and @size="13"]', NULL, 'The custom #size attribute is passed to the child upload element.'); |
|
391 |
|
392 // Perform the tests with all permutations of $form['#tree'] and |
|
393 // $element['#extended']. |
|
394 foreach (array(0, 1) as $tree) { |
|
395 foreach (array(0, 1) as $extended) { |
|
396 $test_file = $this->getTestFile('text'); |
|
397 $path = 'file/test/' . $tree . '/' . $extended; |
|
398 $input_base_name = $tree ? 'nested_file' : 'file'; |
|
399 |
|
400 // Submit without a file. |
|
401 $this->drupalPost($path, array(), t('Save')); |
|
402 $this->assertRaw(t('The file id is %fid.', array('%fid' => 0)), 'Submitted without a file.'); |
|
403 |
|
404 // Submit with a file, but with an invalid form token. Ensure the file |
|
405 // was not saved. |
|
406 $last_fid_prior = $this->getLastFileId(); |
|
407 $edit = array( |
|
408 'files[' . $input_base_name . ']' => drupal_realpath($test_file->uri), |
|
409 'form_token' => 'invalid token', |
|
410 ); |
|
411 $this->drupalPost($path, $edit, t('Save')); |
|
412 $this->assertText('The form has become outdated. Copy any unsaved work in the form below'); |
|
413 $last_fid = $this->getLastFileId(); |
|
414 $this->assertEqual($last_fid_prior, $last_fid, 'File was not saved when uploaded with an invalid form token.'); |
|
415 |
|
416 // Submit a new file, without using the Upload button. |
|
417 $last_fid_prior = $this->getLastFileId(); |
|
418 $edit = array('files[' . $input_base_name . ']' => drupal_realpath($test_file->uri)); |
|
419 $this->drupalPost($path, $edit, t('Save')); |
|
420 $last_fid = $this->getLastFileId(); |
|
421 $this->assertTrue($last_fid > $last_fid_prior, 'New file got saved.'); |
|
422 $this->assertRaw(t('The file id is %fid.', array('%fid' => $last_fid)), 'Submit handler has correct file info.'); |
|
423 |
|
424 // Submit no new input, but with a default file. |
|
425 $this->drupalPost($path . '/' . $last_fid, array(), t('Save')); |
|
426 $this->assertRaw(t('The file id is %fid.', array('%fid' => $last_fid)), 'Empty submission did not change an existing file.'); |
|
427 |
|
428 // Now, test the Upload and Remove buttons, with and without Ajax. |
|
429 foreach (array(FALSE, TRUE) as $ajax) { |
|
430 // Upload, then Submit. |
|
431 $last_fid_prior = $this->getLastFileId(); |
|
432 $this->drupalGet($path); |
|
433 $edit = array('files[' . $input_base_name . ']' => drupal_realpath($test_file->uri)); |
|
434 if ($ajax) { |
|
435 $this->drupalPostAJAX(NULL, $edit, $input_base_name . '_upload_button'); |
|
436 } |
|
437 else { |
|
438 $this->drupalPost(NULL, $edit, t('Upload')); |
|
439 } |
|
440 $last_fid = $this->getLastFileId(); |
|
441 $this->assertTrue($last_fid > $last_fid_prior, 'New file got uploaded.'); |
|
442 $this->drupalPost(NULL, array(), t('Save')); |
|
443 $this->assertRaw(t('The file id is %fid.', array('%fid' => $last_fid)), 'Submit handler has correct file info.'); |
|
444 |
|
445 // Remove, then Submit. |
|
446 $this->drupalGet($path . '/' . $last_fid); |
|
447 if ($ajax) { |
|
448 $this->drupalPostAJAX(NULL, array(), $input_base_name . '_remove_button'); |
|
449 } |
|
450 else { |
|
451 $this->drupalPost(NULL, array(), t('Remove')); |
|
452 } |
|
453 $this->drupalPost(NULL, array(), t('Save')); |
|
454 $this->assertRaw(t('The file id is %fid.', array('%fid' => 0)), 'Submission after file removal was successful.'); |
|
455 |
|
456 // Upload, then Remove, then Submit. |
|
457 $this->drupalGet($path); |
|
458 $edit = array('files[' . $input_base_name . ']' => drupal_realpath($test_file->uri)); |
|
459 if ($ajax) { |
|
460 $this->drupalPostAJAX(NULL, $edit, $input_base_name . '_upload_button'); |
|
461 $this->drupalPostAJAX(NULL, array(), $input_base_name . '_remove_button'); |
|
462 } |
|
463 else { |
|
464 $this->drupalPost(NULL, $edit, t('Upload')); |
|
465 $this->drupalPost(NULL, array(), t('Remove')); |
|
466 } |
|
467 $this->drupalPost(NULL, array(), t('Save')); |
|
468 $this->assertRaw(t('The file id is %fid.', array('%fid' => 0)), 'Submission after file upload and removal was successful.'); |
|
469 } |
|
470 } |
|
471 } |
|
472 } |
|
473 } |
|
474 |
|
475 /** |
|
476 * Tests file field widget. |
|
477 */ |
|
478 class FileFieldWidgetTestCase extends FileFieldTestCase { |
|
479 public static function getInfo() { |
|
480 return array( |
|
481 'name' => 'File field widget test', |
|
482 'description' => 'Tests the file field widget, single and multi-valued, with and without AJAX, with public and private files.', |
|
483 'group' => 'File', |
|
484 ); |
|
485 } |
|
486 |
|
487 /** |
|
488 * Tests upload and remove buttons for a single-valued File field. |
|
489 */ |
|
490 function testSingleValuedWidget() { |
|
491 // Use 'page' instead of 'article', so that the 'article' image field does |
|
492 // not conflict with this test. If in the future the 'page' type gets its |
|
493 // own default file or image field, this test can be made more robust by |
|
494 // using a custom node type. |
|
495 $type_name = 'page'; |
|
496 $field_name = strtolower($this->randomName()); |
|
497 $this->createFileField($field_name, $type_name); |
|
498 $field = field_info_field($field_name); |
|
499 $instance = field_info_instance('node', $field_name, $type_name); |
|
500 |
|
501 $test_file = $this->getTestFile('text'); |
|
502 |
|
503 foreach (array('nojs', 'js') as $type) { |
|
504 // Create a new node with the uploaded file and ensure it got uploaded |
|
505 // successfully. |
|
506 // @todo This only tests a 'nojs' submission, because drupalPostAJAX() |
|
507 // does not yet support file uploads. |
|
508 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); |
|
509 $node = node_load($nid, NULL, TRUE); |
|
510 $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
511 $this->assertFileExists($node_file, 'New file saved to disk on node creation.'); |
|
512 |
|
513 // Test that running field_attach_update() leaves the file intact. |
|
514 $field = new stdClass(); |
|
515 $field->type = $type_name; |
|
516 $field->nid = $nid; |
|
517 field_attach_update('node', $field); |
|
518 $node = node_load($nid); |
|
519 $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
520 $this->assertFileExists($node_file, 'New file still saved to disk on field update.'); |
|
521 |
|
522 // Ensure the file can be downloaded. |
|
523 $this->drupalGet(file_create_url($node_file->uri)); |
|
524 $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.'); |
|
525 |
|
526 // Ensure the edit page has a remove button instead of an upload button. |
|
527 $this->drupalGet("node/$nid/edit"); |
|
528 $this->assertNoFieldByXPath('//input[@type="submit"]', t('Upload'), 'Node with file does not display the "Upload" button.'); |
|
529 $this->assertFieldByXpath('//input[@type="submit"]', t('Remove'), 'Node with file displays the "Remove" button.'); |
|
530 |
|
531 // "Click" the remove button (emulating either a nojs or js submission). |
|
532 switch ($type) { |
|
533 case 'nojs': |
|
534 $this->drupalPost(NULL, array(), t('Remove')); |
|
535 break; |
|
536 case 'js': |
|
537 $button = $this->xpath('//input[@type="submit" and @value="' . t('Remove') . '"]'); |
|
538 $this->drupalPostAJAX(NULL, array(), array((string) $button[0]['name'] => (string) $button[0]['value'])); |
|
539 break; |
|
540 } |
|
541 |
|
542 // Ensure the page now has an upload button instead of a remove button. |
|
543 $this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), 'After clicking the "Remove" button, it is no longer displayed.'); |
|
544 $this->assertFieldByXpath('//input[@type="submit"]', t('Upload'), 'After clicking the "Remove" button, the "Upload" button is displayed.'); |
|
545 |
|
546 // Save the node and ensure it does not have the file. |
|
547 $this->drupalPost(NULL, array(), t('Save')); |
|
548 $node = node_load($nid, NULL, TRUE); |
|
549 $this->assertTrue(empty($node->{$field_name}[LANGUAGE_NONE][0]['fid']), 'File was successfully removed from the node.'); |
|
550 } |
|
551 } |
|
552 |
|
553 /** |
|
554 * Tests exploiting the temporary file removal of another user using fid. |
|
555 */ |
|
556 function testTemporaryFileRemovalExploit() { |
|
557 // Create a victim user. |
|
558 $victim_user = $this->drupalCreateUser(); |
|
559 |
|
560 // Create an attacker user. |
|
561 $attacker_user = $this->drupalCreateUser(array( |
|
562 'access content', |
|
563 'create page content', |
|
564 'edit any page content', |
|
565 )); |
|
566 |
|
567 // Log in as the attacker user. |
|
568 $this->drupalLogin($attacker_user); |
|
569 |
|
570 // Perform tests using the newly created users. |
|
571 $this->doTestTemporaryFileRemovalExploit($victim_user->uid, $attacker_user->uid); |
|
572 } |
|
573 |
|
574 /** |
|
575 * Tests exploiting the temporary file removal for anonymous users using fid. |
|
576 */ |
|
577 public function testTemporaryFileRemovalExploitAnonymous() { |
|
578 // Set up an anonymous victim user. |
|
579 $victim_uid = 0; |
|
580 |
|
581 // Set up an anonymous attacker user. |
|
582 $attacker_uid = 0; |
|
583 |
|
584 // Set up permissions for anonymous attacker user. |
|
585 user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( |
|
586 'access content' => TRUE, |
|
587 'create page content' => TRUE, |
|
588 'edit any page content' => TRUE, |
|
589 )); |
|
590 |
|
591 // In order to simulate being the anonymous attacker user, we need to log |
|
592 // out here since setUp() has logged in the admin. |
|
593 $this->drupalLogout(); |
|
594 |
|
595 // Perform tests using the newly set up users. |
|
596 $this->doTestTemporaryFileRemovalExploit($victim_uid, $attacker_uid); |
|
597 } |
|
598 |
|
599 /** |
|
600 * Tests validation with the Upload button. |
|
601 */ |
|
602 function testWidgetValidation() { |
|
603 $type_name = 'article'; |
|
604 $field_name = strtolower($this->randomName()); |
|
605 $this->createFileField($field_name, $type_name); |
|
606 $this->updateFileField($field_name, $type_name, array('file_extensions' => 'txt')); |
|
607 |
|
608 foreach (array('nojs', 'js') as $type) { |
|
609 // Create node and prepare files for upload. |
|
610 $node = $this->drupalCreateNode(array('type' => 'article')); |
|
611 $nid = $node->nid; |
|
612 $this->drupalGet("node/$nid/edit"); |
|
613 $test_file_text = $this->getTestFile('text'); |
|
614 $test_file_image = $this->getTestFile('image'); |
|
615 $field = field_info_field($field_name); |
|
616 $name = 'files[' . $field_name . '_' . LANGUAGE_NONE . '_0]'; |
|
617 |
|
618 // Upload file with incorrect extension, check for validation error. |
|
619 $edit[$name] = drupal_realpath($test_file_image->uri); |
|
620 switch ($type) { |
|
621 case 'nojs': |
|
622 $this->drupalPost(NULL, $edit, t('Upload')); |
|
623 break; |
|
624 |
|
625 case 'js': |
|
626 $button = $this->xpath('//input[@type="submit" and @value="' . t('Upload') . '"]'); |
|
627 $this->drupalPostAJAX(NULL, $edit, array((string) $button[0]['name'] => (string) $button[0]['value'])); |
|
628 break; |
|
629 } |
|
630 $error_message = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => 'txt')); |
|
631 $this->assertRaw($error_message, t('Validation error when file with wrong extension uploaded (JSMode=%type).', array('%type' => $type))); |
|
632 |
|
633 // Upload file with correct extension, check that error message is removed. |
|
634 $edit[$name] = drupal_realpath($test_file_text->uri); |
|
635 switch ($type) { |
|
636 case 'nojs': |
|
637 $this->drupalPost(NULL, $edit, t('Upload')); |
|
638 break; |
|
639 |
|
640 case 'js': |
|
641 $button = $this->xpath('//input[@type="submit" and @value="' . t('Upload') . '"]'); |
|
642 $this->drupalPostAJAX(NULL, $edit, array((string) $button[0]['name'] => (string) $button[0]['value'])); |
|
643 break; |
|
644 } |
|
645 $this->assertNoRaw($error_message, t('Validation error removed when file with correct extension uploaded (JSMode=%type).', array('%type' => $type))); |
|
646 } |
|
647 } |
|
648 |
|
649 /** |
|
650 * Helper for testing exploiting the temporary file removal using fid. |
|
651 * |
|
652 * @param int $victim_uid |
|
653 * The victim user ID. |
|
654 * @param int $attacker_uid |
|
655 * The attacker user ID. |
|
656 */ |
|
657 protected function doTestTemporaryFileRemovalExploit($victim_uid, $attacker_uid) { |
|
658 // Use 'page' instead of 'article', so that the 'article' image field does |
|
659 // not conflict with this test. If in the future the 'page' type gets its |
|
660 // own default file or image field, this test can be made more robust by |
|
661 // using a custom node type. |
|
662 $type_name = 'page'; |
|
663 $field_name = 'test_file_field'; |
|
664 $this->createFileField($field_name, $type_name); |
|
665 |
|
666 $test_file = $this->getTestFile('text'); |
|
667 foreach (array('nojs', 'js') as $type) { |
|
668 // Create a temporary file owned by the anonymous victim user. This will be |
|
669 // as if they had uploaded the file, but not saved the node they were |
|
670 // editing or creating. |
|
671 $victim_tmp_file = $this->createTemporaryFile('some text', $victim_uid); |
|
672 $victim_tmp_file = file_load($victim_tmp_file->fid); |
|
673 $this->assertTrue($victim_tmp_file->status != FILE_STATUS_PERMANENT, 'New file saved to disk is temporary.'); |
|
674 $this->assertFalse(empty($victim_tmp_file->fid), 'New file has a fid'); |
|
675 $this->assertEqual($victim_uid, $victim_tmp_file->uid, 'New file belongs to the victim user'); |
|
676 |
|
677 // Have attacker create a new node with a different uploaded file and |
|
678 // ensure it got uploaded successfully. |
|
679 // @todo Can we test AJAX? See https://www.drupal.org/node/2538260 |
|
680 $edit = array( |
|
681 'title' => $type . '-title', |
|
682 ); |
|
683 |
|
684 // Attach a file to a node. |
|
685 $langcode = LANGUAGE_NONE; |
|
686 $edit['files[' . $field_name . '_' . $langcode . '_0]'] = drupal_realpath($test_file->uri); |
|
687 $this->drupalPost("node/add/$type_name", $edit, 'Save'); |
|
688 $node = $this->drupalGetNodeByTitle($edit['title']); |
|
689 $node_file = file_load($node->{$field_name}[$langcode][0]['fid']); |
|
690 $this->assertFileExists($node_file, 'New file saved to disk on node creation.'); |
|
691 $this->assertEqual($attacker_uid, $node_file->uid, 'New file belongs to the attacker.'); |
|
692 |
|
693 // Ensure the file can be downloaded. |
|
694 $this->drupalGet(file_create_url($node_file->uri)); |
|
695 $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.'); |
|
696 |
|
697 // "Click" the remove button (emulating either a nojs or js submission). |
|
698 // In this POST request, the attacker "guesses" the fid of the victim's |
|
699 // temporary file and uses that to remove this file. |
|
700 $this->drupalGet('node/' . $node->nid . '/edit'); |
|
701 switch ($type) { |
|
702 case 'nojs': |
|
703 $this->drupalPost(NULL, array("{$field_name}[$langcode][0][fid]" => (string) $victim_tmp_file->fid), 'Remove'); |
|
704 break; |
|
705 case 'js': |
|
706 $button = $this->xpath('//input[@type="submit" and @value="Remove"]'); |
|
707 $this->drupalPostAJAX(NULL, array("{$field_name}[$langcode][0][fid]" => (string) $victim_tmp_file->fid), array((string) $button[0]['name'] => (string) $button[0]['value'])); |
|
708 break; |
|
709 } |
|
710 |
|
711 // The victim's temporary file should not be removed by the attacker's |
|
712 // POST request. |
|
713 $this->assertFileExists($victim_tmp_file); |
|
714 } |
|
715 } |
|
716 |
|
717 /** |
|
718 * Tests upload and remove buttons for multiple multi-valued File fields. |
|
719 */ |
|
720 function testMultiValuedWidget() { |
|
721 // Use 'page' instead of 'article', so that the 'article' image field does |
|
722 // not conflict with this test. If in the future the 'page' type gets its |
|
723 // own default file or image field, this test can be made more robust by |
|
724 // using a custom node type. |
|
725 $type_name = 'page'; |
|
726 $field_name = strtolower($this->randomName()); |
|
727 $field_name2 = strtolower($this->randomName()); |
|
728 $this->createFileField($field_name, $type_name, array('cardinality' => 3)); |
|
729 $this->createFileField($field_name2, $type_name, array('cardinality' => 3)); |
|
730 |
|
731 $field = field_info_field($field_name); |
|
732 $instance = field_info_instance('node', $field_name, $type_name); |
|
733 |
|
734 $field2 = field_info_field($field_name2); |
|
735 $instance2 = field_info_instance('node', $field_name2, $type_name); |
|
736 |
|
737 $test_file = $this->getTestFile('text'); |
|
738 |
|
739 foreach (array('nojs', 'js') as $type) { |
|
740 // Visit the node creation form, and upload 3 files for each field. Since |
|
741 // the field has cardinality of 3, ensure the "Upload" button is displayed |
|
742 // until after the 3rd file, and after that, isn't displayed. Because |
|
743 // SimpleTest triggers the last button with a given name, so upload to the |
|
744 // second field first. |
|
745 // @todo This is only testing a non-Ajax upload, because drupalPostAJAX() |
|
746 // does not yet emulate jQuery's file upload. |
|
747 // |
|
748 $this->drupalGet("node/add/$type_name"); |
|
749 foreach (array($field_name2, $field_name) as $each_field_name) { |
|
750 for ($delta = 0; $delta < 3; $delta++) { |
|
751 $edit = array('files[' . $each_field_name . '_' . LANGUAGE_NONE . '_' . $delta . ']' => drupal_realpath($test_file->uri)); |
|
752 // If the Upload button doesn't exist, drupalPost() will automatically |
|
753 // fail with an assertion message. |
|
754 $this->drupalPost(NULL, $edit, t('Upload')); |
|
755 } |
|
756 } |
|
757 $this->assertNoFieldByXpath('//input[@type="submit"]', t('Upload'), 'After uploading 3 files for each field, the "Upload" button is no longer displayed.'); |
|
758 |
|
759 $num_expected_remove_buttons = 6; |
|
760 |
|
761 foreach (array($field_name, $field_name2) as $current_field_name) { |
|
762 // How many uploaded files for the current field are remaining. |
|
763 $remaining = 3; |
|
764 // Test clicking each "Remove" button. For extra robustness, test them out |
|
765 // of sequential order. They are 0-indexed, and get renumbered after each |
|
766 // iteration, so array(1, 1, 0) means: |
|
767 // - First remove the 2nd file. |
|
768 // - Then remove what is then the 2nd file (was originally the 3rd file). |
|
769 // - Then remove the first file. |
|
770 foreach (array(1,1,0) as $delta) { |
|
771 // Ensure we have the expected number of Remove buttons, and that they |
|
772 // are numbered sequentially. |
|
773 $buttons = $this->xpath('//input[@type="submit" and @value="Remove"]'); |
|
774 $this->assertTrue(is_array($buttons) && count($buttons) === $num_expected_remove_buttons, format_string('There are %n "Remove" buttons displayed (JSMode=%type).', array('%n' => $num_expected_remove_buttons, '%type' => $type))); |
|
775 foreach ($buttons as $i => $button) { |
|
776 $key = $i >= $remaining ? $i - $remaining : $i; |
|
777 $check_field_name = $field_name2; |
|
778 if ($current_field_name == $field_name && $i < $remaining) { |
|
779 $check_field_name = $field_name; |
|
780 } |
|
781 |
|
782 $this->assertIdentical((string) $button['name'], $check_field_name . '_' . LANGUAGE_NONE . '_' . $key. '_remove_button'); |
|
783 } |
|
784 |
|
785 // "Click" the remove button (emulating either a nojs or js submission). |
|
786 $button_name = $current_field_name . '_' . LANGUAGE_NONE . '_' . $delta . '_remove_button'; |
|
787 switch ($type) { |
|
788 case 'nojs': |
|
789 // drupalPost() takes a $submit parameter that is the value of the |
|
790 // button whose click we want to emulate. Since we have multiple |
|
791 // buttons with the value "Remove", and want to control which one we |
|
792 // use, we change the value of the other ones to something else. |
|
793 // Since non-clicked buttons aren't included in the submitted POST |
|
794 // data, and since drupalPost() will result in $this being updated |
|
795 // with a newly rebuilt form, this doesn't cause problems. |
|
796 foreach ($buttons as $button) { |
|
797 if ($button['name'] != $button_name) { |
|
798 $button['value'] = 'DUMMY'; |
|
799 } |
|
800 } |
|
801 $this->drupalPost(NULL, array(), t('Remove')); |
|
802 break; |
|
803 case 'js': |
|
804 // drupalPostAJAX() lets us target the button precisely, so we don't |
|
805 // require the workaround used above for nojs. |
|
806 $this->drupalPostAJAX(NULL, array(), array($button_name => t('Remove'))); |
|
807 break; |
|
808 } |
|
809 $num_expected_remove_buttons--; |
|
810 $remaining--; |
|
811 |
|
812 // Ensure an "Upload" button for the current field is displayed with the |
|
813 // correct name. |
|
814 $upload_button_name = $current_field_name . '_' . LANGUAGE_NONE . '_' . $remaining . '_upload_button'; |
|
815 $buttons = $this->xpath('//input[@type="submit" and @value="Upload" and @name=:name]', array(':name' => $upload_button_name)); |
|
816 $this->assertTrue(is_array($buttons) && count($buttons) == 1, format_string('The upload button is displayed with the correct name (JSMode=%type).', array('%type' => $type))); |
|
817 |
|
818 // Ensure only at most one button per field is displayed. |
|
819 $buttons = $this->xpath('//input[@type="submit" and @value="Upload"]'); |
|
820 $expected = $current_field_name == $field_name ? 1 : 2; |
|
821 $this->assertTrue(is_array($buttons) && count($buttons) == $expected, format_string('After removing a file, only one "Upload" button for each possible field is displayed (JSMode=%type).', array('%type' => $type))); |
|
822 } |
|
823 } |
|
824 |
|
825 // Ensure the page now has no Remove buttons. |
|
826 $this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), format_string('After removing all files, there is no "Remove" button displayed (JSMode=%type).', array('%type' => $type))); |
|
827 |
|
828 // Save the node and ensure it does not have any files. |
|
829 $this->drupalPost(NULL, array('title' => $this->randomName()), t('Save')); |
|
830 $matches = array(); |
|
831 preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches); |
|
832 $nid = $matches[1]; |
|
833 $node = node_load($nid, NULL, TRUE); |
|
834 $this->assertTrue(empty($node->{$field_name}[LANGUAGE_NONE][0]['fid']), 'Node was successfully saved without any files.'); |
|
835 } |
|
836 } |
|
837 |
|
838 /** |
|
839 * Tests a file field with a "Private files" upload destination setting. |
|
840 */ |
|
841 function testPrivateFileSetting() { |
|
842 // Use 'page' instead of 'article', so that the 'article' image field does |
|
843 // not conflict with this test. If in the future the 'page' type gets its |
|
844 // own default file or image field, this test can be made more robust by |
|
845 // using a custom node type. |
|
846 $type_name = 'page'; |
|
847 $field_name = strtolower($this->randomName()); |
|
848 $this->createFileField($field_name, $type_name); |
|
849 $field = field_info_field($field_name); |
|
850 $instance = field_info_instance('node', $field_name, $type_name); |
|
851 |
|
852 $test_file = $this->getTestFile('text'); |
|
853 |
|
854 // Change the field setting to make its files private, and upload a file. |
|
855 $edit = array('field[settings][uri_scheme]' => 'private'); |
|
856 $this->drupalPost("admin/structure/types/manage/$type_name/fields/$field_name", $edit, t('Save settings')); |
|
857 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); |
|
858 $node = node_load($nid, NULL, TRUE); |
|
859 $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
860 $this->assertFileExists($node_file, 'New file saved to disk on node creation.'); |
|
861 |
|
862 // Ensure the private file is available to the user who uploaded it. |
|
863 $this->drupalGet(file_create_url($node_file->uri)); |
|
864 $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.'); |
|
865 |
|
866 // Ensure we can't change 'uri_scheme' field settings while there are some |
|
867 // entities with uploaded files. |
|
868 $this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_name"); |
|
869 $this->assertFieldByXpath('//input[@id="edit-field-settings-uri-scheme-public" and @disabled="disabled"]', 'public', 'Upload destination setting disabled.'); |
|
870 |
|
871 // Delete node and confirm that setting could be changed. |
|
872 node_delete($nid); |
|
873 $this->drupalGet("admin/structure/types/manage/$type_name/fields/$field_name"); |
|
874 $this->assertFieldByXpath('//input[@id="edit-field-settings-uri-scheme-public" and not(@disabled)]', 'public', 'Upload destination setting enabled.'); |
|
875 } |
|
876 |
|
877 /** |
|
878 * Tests that download restrictions on private files work on comments. |
|
879 */ |
|
880 function testPrivateFileComment() { |
|
881 $user = $this->drupalCreateUser(array('access comments')); |
|
882 |
|
883 // Remove access comments permission from anon user. |
|
884 $edit = array( |
|
885 DRUPAL_ANONYMOUS_RID . '[access comments]' => FALSE, |
|
886 ); |
|
887 $this->drupalPost('admin/people/permissions', $edit, t('Save permissions')); |
|
888 |
|
889 // Create a new field. |
|
890 $edit = array( |
|
891 'fields[_add_new_field][label]' => $label = $this->randomName(), |
|
892 'fields[_add_new_field][field_name]' => $name = strtolower($this->randomName()), |
|
893 'fields[_add_new_field][type]' => 'file', |
|
894 'fields[_add_new_field][widget_type]' => 'file_generic', |
|
895 ); |
|
896 $this->drupalPost('admin/structure/types/manage/article/comment/fields', $edit, t('Save')); |
|
897 $edit = array('field[settings][uri_scheme]' => 'private'); |
|
898 $this->drupalPost(NULL, $edit, t('Save field settings')); |
|
899 $this->drupalPost(NULL, array(), t('Save settings')); |
|
900 |
|
901 // Create node. |
|
902 $text_file = $this->getTestFile('text'); |
|
903 $edit = array( |
|
904 'title' => $this->randomName(), |
|
905 ); |
|
906 $this->drupalPost('node/add/article', $edit, t('Save')); |
|
907 $node = $this->drupalGetNodeByTitle($edit['title']); |
|
908 |
|
909 // Add a comment with a file. |
|
910 $text_file = $this->getTestFile('text'); |
|
911 $edit = array( |
|
912 'files[field_' . $name . '_' . LANGUAGE_NONE . '_' . 0 . ']' => drupal_realpath($text_file->uri), |
|
913 'comment_body[' . LANGUAGE_NONE . '][0][value]' => $comment_body = $this->randomName(), |
|
914 ); |
|
915 $this->drupalPost(NULL, $edit, t('Save')); |
|
916 |
|
917 // Get the comment ID. |
|
918 preg_match('/comment-([0-9]+)/', $this->getUrl(), $matches); |
|
919 $cid = $matches[1]; |
|
920 |
|
921 // Log in as normal user. |
|
922 $this->drupalLogin($user); |
|
923 |
|
924 $comment = comment_load($cid); |
|
925 $comment_file = (object) $comment->{'field_' . $name}[LANGUAGE_NONE][0]; |
|
926 $this->assertFileExists($comment_file, 'New file saved to disk on node creation.'); |
|
927 // Test authenticated file download. |
|
928 $url = file_create_url($comment_file->uri); |
|
929 $this->assertNotEqual($url, NULL, 'Confirmed that the URL is valid'); |
|
930 $this->drupalGet(file_create_url($comment_file->uri)); |
|
931 $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.'); |
|
932 |
|
933 // Test anonymous file download. |
|
934 $this->drupalLogout(); |
|
935 $this->drupalGet(file_create_url($comment_file->uri)); |
|
936 $this->assertResponse(403, 'Confirmed that access is denied for the file without the needed permission.'); |
|
937 |
|
938 // Unpublishes node. |
|
939 $this->drupalLogin($this->admin_user); |
|
940 $edit = array( |
|
941 'status' => FALSE, |
|
942 ); |
|
943 $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); |
|
944 |
|
945 // Ensures normal user can no longer download the file. |
|
946 $this->drupalLogin($user); |
|
947 $this->drupalGet(file_create_url($comment_file->uri)); |
|
948 $this->assertResponse(403, 'Confirmed that access is denied for the file without the needed permission.'); |
|
949 } |
|
950 |
|
951 } |
|
952 |
|
953 /** |
|
954 * Tests file handling with node revisions. |
|
955 */ |
|
956 class FileFieldRevisionTestCase extends FileFieldTestCase { |
|
957 public static function getInfo() { |
|
958 return array( |
|
959 'name' => 'File field revision test', |
|
960 'description' => 'Test creating and deleting revisions with files attached.', |
|
961 'group' => 'File', |
|
962 ); |
|
963 } |
|
964 |
|
965 /** |
|
966 * Tests creating multiple revisions of a node and managing attached files. |
|
967 * |
|
968 * Expected behaviors: |
|
969 * - Adding a new revision will make another entry in the field table, but |
|
970 * the original file will not be duplicated. |
|
971 * - Deleting a revision should not delete the original file if the file |
|
972 * is in use by another revision. |
|
973 * - When the last revision that uses a file is deleted, the original file |
|
974 * should be deleted also. |
|
975 */ |
|
976 function testRevisions() { |
|
977 $type_name = 'article'; |
|
978 $field_name = strtolower($this->randomName()); |
|
979 $this->createFileField($field_name, $type_name); |
|
980 $field = field_info_field($field_name); |
|
981 $instance = field_info_instance('node', $field_name, $type_name); |
|
982 |
|
983 // Attach the same fields to users. |
|
984 $this->attachFileField($field_name, 'user', 'user'); |
|
985 |
|
986 $test_file = $this->getTestFile('text'); |
|
987 |
|
988 // Create a new node with the uploaded file. |
|
989 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); |
|
990 |
|
991 // Check that the file exists on disk and in the database. |
|
992 $node = node_load($nid, NULL, TRUE); |
|
993 $node_file_r1 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
994 $node_vid_r1 = $node->vid; |
|
995 $this->assertFileExists($node_file_r1, 'New file saved to disk on node creation.'); |
|
996 $this->assertFileEntryExists($node_file_r1, 'File entry exists in database on node creation.'); |
|
997 $this->assertFileIsPermanent($node_file_r1, 'File is permanent.'); |
|
998 |
|
999 // Upload another file to the same node in a new revision. |
|
1000 $this->replaceNodeFile($test_file, $field_name, $nid); |
|
1001 $node = node_load($nid, NULL, TRUE); |
|
1002 $node_file_r2 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
1003 $node_vid_r2 = $node->vid; |
|
1004 $this->assertFileExists($node_file_r2, 'Replacement file exists on disk after creating new revision.'); |
|
1005 $this->assertFileEntryExists($node_file_r2, 'Replacement file entry exists in database after creating new revision.'); |
|
1006 $this->assertFileIsPermanent($node_file_r2, 'Replacement file is permanent.'); |
|
1007 |
|
1008 // Check that the original file is still in place on the first revision. |
|
1009 $node = node_load($nid, $node_vid_r1, TRUE); |
|
1010 $this->assertEqual($node_file_r1, (object) $node->{$field_name}[LANGUAGE_NONE][0], 'Original file still in place after replacing file in new revision.'); |
|
1011 $this->assertFileExists($node_file_r1, 'Original file still in place after replacing file in new revision.'); |
|
1012 $this->assertFileEntryExists($node_file_r1, 'Original file entry still in place after replacing file in new revision'); |
|
1013 $this->assertFileIsPermanent($node_file_r1, 'Original file is still permanent.'); |
|
1014 |
|
1015 // Save a new version of the node without any changes. |
|
1016 // Check that the file is still the same as the previous revision. |
|
1017 $this->drupalPost('node/' . $nid . '/edit', array('revision' => '1'), t('Save')); |
|
1018 $node = node_load($nid, NULL, TRUE); |
|
1019 $node_file_r3 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
1020 $node_vid_r3 = $node->vid; |
|
1021 $this->assertEqual($node_file_r2, $node_file_r3, 'Previous revision file still in place after creating a new revision without a new file.'); |
|
1022 $this->assertFileIsPermanent($node_file_r3, 'New revision file is permanent.'); |
|
1023 |
|
1024 // Revert to the first revision and check that the original file is active. |
|
1025 $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r1 . '/revert', array(), t('Revert')); |
|
1026 $node = node_load($nid, NULL, TRUE); |
|
1027 $node_file_r4 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
1028 $node_vid_r4 = $node->vid; |
|
1029 $this->assertEqual($node_file_r1, $node_file_r4, 'Original revision file still in place after reverting to the original revision.'); |
|
1030 $this->assertFileIsPermanent($node_file_r4, 'Original revision file still permanent after reverting to the original revision.'); |
|
1031 |
|
1032 // Delete the second revision and check that the file is kept (since it is |
|
1033 // still being used by the third revision). |
|
1034 $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r2 . '/delete', array(), t('Delete')); |
|
1035 $this->assertFileExists($node_file_r3, 'Second file is still available after deleting second revision, since it is being used by the third revision.'); |
|
1036 $this->assertFileEntryExists($node_file_r3, 'Second file entry is still available after deleting second revision, since it is being used by the third revision.'); |
|
1037 $this->assertFileIsPermanent($node_file_r3, 'Second file entry is still permanent after deleting second revision, since it is being used by the third revision.'); |
|
1038 |
|
1039 // Attach the second file to a user. |
|
1040 $user = $this->drupalCreateUser(); |
|
1041 $edit = (array) $user; |
|
1042 $edit[$field_name][LANGUAGE_NONE][0] = (array) $node_file_r3; |
|
1043 user_save($user, $edit); |
|
1044 $this->drupalGet('user/' . $user->uid . '/edit'); |
|
1045 |
|
1046 // Delete the third revision and check that the file is not deleted yet. |
|
1047 $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete', array(), t('Delete')); |
|
1048 $this->assertFileExists($node_file_r3, 'Second file is still available after deleting third revision, since it is being used by the user.'); |
|
1049 $this->assertFileEntryExists($node_file_r3, 'Second file entry is still available after deleting third revision, since it is being used by the user.'); |
|
1050 $this->assertFileIsPermanent($node_file_r3, 'Second file entry is still permanent after deleting third revision, since it is being used by the user.'); |
|
1051 |
|
1052 // Delete the user and check that the file is also deleted. |
|
1053 user_delete($user->uid); |
|
1054 // TODO: This seems like a bug in File API. Clearing the stat cache should |
|
1055 // not be necessary here. The file really is deleted, but stream wrappers |
|
1056 // doesn't seem to think so unless we clear the PHP file stat() cache. |
|
1057 clearstatcache(); |
|
1058 $this->assertFileNotExists($node_file_r3, 'Second file is now deleted after deleting third revision, since it is no longer being used by any other nodes.'); |
|
1059 $this->assertFileEntryNotExists($node_file_r3, 'Second file entry is now deleted after deleting third revision, since it is no longer being used by any other nodes.'); |
|
1060 |
|
1061 // Delete the entire node and check that the original file is deleted. |
|
1062 $this->drupalPost('node/' . $nid . '/delete', array(), t('Delete')); |
|
1063 $this->assertFileNotExists($node_file_r1, 'Original file is deleted after deleting the entire node with two revisions remaining.'); |
|
1064 $this->assertFileEntryNotExists($node_file_r1, 'Original file entry is deleted after deleting the entire node with two revisions remaining.'); |
|
1065 } |
|
1066 } |
|
1067 |
|
1068 /** |
|
1069 * Tests that formatters are working properly. |
|
1070 */ |
|
1071 class FileFieldDisplayTestCase extends FileFieldTestCase { |
|
1072 public static function getInfo() { |
|
1073 return array( |
|
1074 'name' => 'File field display tests', |
|
1075 'description' => 'Test the display of file fields in node and views.', |
|
1076 'group' => 'File', |
|
1077 ); |
|
1078 } |
|
1079 |
|
1080 /** |
|
1081 * Tests normal formatter display on node display. |
|
1082 */ |
|
1083 function testNodeDisplay() { |
|
1084 $field_name = strtolower($this->randomName()); |
|
1085 $type_name = 'article'; |
|
1086 $field_settings = array( |
|
1087 'display_field' => '1', |
|
1088 'display_default' => '1', |
|
1089 'cardinality' => FIELD_CARDINALITY_UNLIMITED, |
|
1090 ); |
|
1091 $instance_settings = array( |
|
1092 'description_field' => '1', |
|
1093 ); |
|
1094 $widget_settings = array(); |
|
1095 $this->createFileField($field_name, $type_name, $field_settings, $instance_settings, $widget_settings); |
|
1096 $field = field_info_field($field_name); |
|
1097 $instance = field_info_instance('node', $field_name, $type_name); |
|
1098 |
|
1099 // Create a new node *without* the file field set, and check that the field |
|
1100 // is not shown for each node display. |
|
1101 $node = $this->drupalCreateNode(array('type' => $type_name)); |
|
1102 $file_formatters = array('file_default', 'file_table', 'file_url_plain', 'hidden'); |
|
1103 foreach ($file_formatters as $formatter) { |
|
1104 $edit = array( |
|
1105 "fields[$field_name][type]" => $formatter, |
|
1106 ); |
|
1107 $this->drupalPost("admin/structure/types/manage/$type_name/display", $edit, t('Save')); |
|
1108 $this->drupalGet('node/' . $node->nid); |
|
1109 $this->assertNoText($field_name, format_string('Field label is hidden when no file attached for formatter %formatter', array('%formatter' => $formatter))); |
|
1110 } |
|
1111 |
|
1112 $test_file = $this->getTestFile('text'); |
|
1113 |
|
1114 // Create a new node with the uploaded file. |
|
1115 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); |
|
1116 $this->drupalGet('node/' . $nid . '/edit'); |
|
1117 |
|
1118 // Check that the default formatter is displaying with the file name. |
|
1119 $node = node_load($nid, NULL, TRUE); |
|
1120 $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
1121 $default_output = theme('file_link', array('file' => $node_file)); |
|
1122 $this->assertRaw($default_output, 'Default formatter displaying correctly on full node view.'); |
|
1123 |
|
1124 // Turn the "display" option off and check that the file is no longer displayed. |
|
1125 $edit = array($field_name . '[' . LANGUAGE_NONE . '][0][display]' => FALSE); |
|
1126 $this->drupalPost('node/' . $nid . '/edit', $edit, t('Save')); |
|
1127 |
|
1128 $this->assertNoRaw($default_output, 'Field is hidden when "display" option is unchecked.'); |
|
1129 |
|
1130 // Test that fields appear as expected during the preview. |
|
1131 // Add a second file. |
|
1132 $name = 'files[' . $field_name . '_' . LANGUAGE_NONE . '_1]'; |
|
1133 $edit[$name] = drupal_realpath($test_file->uri); |
|
1134 |
|
1135 // Uncheck the display checkboxes and go to the preview. |
|
1136 $edit[$field_name . '[' . LANGUAGE_NONE . '][0][display]'] = FALSE; |
|
1137 $edit[$field_name . '[' . LANGUAGE_NONE . '][1][display]'] = FALSE; |
|
1138 $this->drupalPost('node/' . $nid . '/edit', $edit, t('Preview')); |
|
1139 $this->assertRaw($field_name . '[' . LANGUAGE_NONE . '][0][display]', 'First file appears as expected.'); |
|
1140 $this->assertRaw($field_name . '[' . LANGUAGE_NONE . '][1][display]', 'Second file appears as expected.'); |
|
1141 } |
|
1142 |
|
1143 /** |
|
1144 * Tests default display of File Field. |
|
1145 */ |
|
1146 function testDefaultFileFieldDisplay() { |
|
1147 $field_name = strtolower($this->randomName()); |
|
1148 $type_name = 'article'; |
|
1149 $field_settings = array( |
|
1150 'display_field' => '1', |
|
1151 'display_default' => '0', |
|
1152 ); |
|
1153 $instance_settings = array( |
|
1154 'description_field' => '1', |
|
1155 ); |
|
1156 $widget_settings = array(); |
|
1157 $this->createFileField($field_name, $type_name, $field_settings, $instance_settings, $widget_settings); |
|
1158 $field = field_info_field($field_name); |
|
1159 $instance = field_info_instance('node', $field_name, $type_name); |
|
1160 |
|
1161 $test_file = $this->getTestFile('text'); |
|
1162 |
|
1163 // Create a new node with the uploaded file. |
|
1164 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); |
|
1165 |
|
1166 $this->drupalGet('node/' . $nid . '/edit'); |
|
1167 $this->assertFieldByXPath('//input[@type="checkbox" and @name="' . $field_name . '[und][0][display]"]', NULL, 'Default file display checkbox field exists.'); |
|
1168 $this->assertFieldByXPath('//input[@type="checkbox" and @name="' . $field_name . '[und][0][display]" and not(@checked)]', NULL, 'Default file display is off.'); |
|
1169 } |
|
1170 } |
|
1171 |
|
1172 /** |
|
1173 * Tests various validations. |
|
1174 */ |
|
1175 class FileFieldValidateTestCase extends FileFieldTestCase { |
|
1176 protected $field; |
|
1177 protected $node_type; |
|
1178 |
|
1179 public static function getInfo() { |
|
1180 return array( |
|
1181 'name' => 'File field validation tests', |
|
1182 'description' => 'Tests validation functions such as file type, max file size, max size per node, and required.', |
|
1183 'group' => 'File', |
|
1184 ); |
|
1185 } |
|
1186 |
|
1187 /** |
|
1188 * Tests the required property on file fields. |
|
1189 */ |
|
1190 function testRequired() { |
|
1191 $type_name = 'article'; |
|
1192 $field_name = strtolower($this->randomName()); |
|
1193 $this->createFileField($field_name, $type_name, array(), array('required' => '1')); |
|
1194 $field = field_info_field($field_name); |
|
1195 $instance = field_info_instance('node', $field_name, $type_name); |
|
1196 |
|
1197 $test_file = $this->getTestFile('text'); |
|
1198 |
|
1199 // Try to post a new node without uploading a file. |
|
1200 $langcode = LANGUAGE_NONE; |
|
1201 $edit = array("title" => $this->randomName()); |
|
1202 $this->drupalPost('node/add/' . $type_name, $edit, t('Save')); |
|
1203 $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), 'Node save failed when required file field was empty.'); |
|
1204 |
|
1205 // Create a new node with the uploaded file. |
|
1206 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); |
|
1207 $this->assertTrue($nid !== FALSE, format_string('uploadNodeFile(@test_file, @field_name, @type_name) succeeded', array('@test_file' => $test_file->uri, '@field_name' => $field_name, '@type_name' => $type_name))); |
|
1208 |
|
1209 $node = node_load($nid, NULL, TRUE); |
|
1210 |
|
1211 $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
1212 $this->assertFileExists($node_file, 'File exists after uploading to the required field.'); |
|
1213 $this->assertFileEntryExists($node_file, 'File entry exists after uploading to the required field.'); |
|
1214 |
|
1215 // Try again with a multiple value field. |
|
1216 field_delete_field($field_name); |
|
1217 $this->createFileField($field_name, $type_name, array('cardinality' => FIELD_CARDINALITY_UNLIMITED), array('required' => '1')); |
|
1218 |
|
1219 // Try to post a new node without uploading a file in the multivalue field. |
|
1220 $edit = array('title' => $this->randomName()); |
|
1221 $this->drupalPost('node/add/' . $type_name, $edit, t('Save')); |
|
1222 $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), 'Node save failed when required multiple value file field was empty.'); |
|
1223 |
|
1224 // Create a new node with the uploaded file into the multivalue field. |
|
1225 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); |
|
1226 $node = node_load($nid, NULL, TRUE); |
|
1227 $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
1228 $this->assertFileExists($node_file, 'File exists after uploading to the required multiple value field.'); |
|
1229 $this->assertFileEntryExists($node_file, 'File entry exists after uploading to the required multipel value field.'); |
|
1230 |
|
1231 // Remove our file field. |
|
1232 field_delete_field($field_name); |
|
1233 } |
|
1234 |
|
1235 /** |
|
1236 * Tests the max file size validator. |
|
1237 */ |
|
1238 function testFileMaxSize() { |
|
1239 $type_name = 'article'; |
|
1240 $field_name = strtolower($this->randomName()); |
|
1241 $this->createFileField($field_name, $type_name, array(), array('required' => '1')); |
|
1242 $field = field_info_field($field_name); |
|
1243 $instance = field_info_instance('node', $field_name, $type_name); |
|
1244 |
|
1245 $small_file = $this->getTestFile('text', 131072); // 128KB. |
|
1246 $large_file = $this->getTestFile('text', 1310720); // 1.2MB |
|
1247 |
|
1248 // Test uploading both a large and small file with different increments. |
|
1249 $sizes = array( |
|
1250 '1M' => 1048576, |
|
1251 '1024K' => 1048576, |
|
1252 '1048576' => 1048576, |
|
1253 ); |
|
1254 |
|
1255 foreach ($sizes as $max_filesize => $file_limit) { |
|
1256 // Set the max file upload size. |
|
1257 $this->updateFileField($field_name, $type_name, array('max_filesize' => $max_filesize)); |
|
1258 $instance = field_info_instance('node', $field_name, $type_name); |
|
1259 |
|
1260 // Create a new node with the small file, which should pass. |
|
1261 $nid = $this->uploadNodeFile($small_file, $field_name, $type_name); |
|
1262 $node = node_load($nid, NULL, TRUE); |
|
1263 $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
1264 $this->assertFileExists($node_file, format_string('File exists after uploading a file (%filesize) under the max limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_filesize))); |
|
1265 $this->assertFileEntryExists($node_file, format_string('File entry exists after uploading a file (%filesize) under the max limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_filesize))); |
|
1266 |
|
1267 // Check that uploading the large file fails (1M limit). |
|
1268 $nid = $this->uploadNodeFile($large_file, $field_name, $type_name); |
|
1269 $error_message = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($large_file->filesize), '%maxsize' => format_size($file_limit))); |
|
1270 $this->assertRaw($error_message, format_string('Node save failed when file (%filesize) exceeded the max upload size (%maxsize).', array('%filesize' => format_size($large_file->filesize), '%maxsize' => $max_filesize))); |
|
1271 } |
|
1272 |
|
1273 // Turn off the max filesize. |
|
1274 $this->updateFileField($field_name, $type_name, array('max_filesize' => '')); |
|
1275 |
|
1276 // Upload the big file successfully. |
|
1277 $nid = $this->uploadNodeFile($large_file, $field_name, $type_name); |
|
1278 $node = node_load($nid, NULL, TRUE); |
|
1279 $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
1280 $this->assertFileExists($node_file, format_string('File exists after uploading a file (%filesize) with no max limit.', array('%filesize' => format_size($large_file->filesize)))); |
|
1281 $this->assertFileEntryExists($node_file, format_string('File entry exists after uploading a file (%filesize) with no max limit.', array('%filesize' => format_size($large_file->filesize)))); |
|
1282 |
|
1283 // Remove our file field. |
|
1284 field_delete_field($field_name); |
|
1285 } |
|
1286 |
|
1287 /** |
|
1288 * Tests file extension checking. |
|
1289 */ |
|
1290 function testFileExtension() { |
|
1291 $type_name = 'article'; |
|
1292 $field_name = strtolower($this->randomName()); |
|
1293 $this->createFileField($field_name, $type_name); |
|
1294 $field = field_info_field($field_name); |
|
1295 $instance = field_info_instance('node', $field_name, $type_name); |
|
1296 |
|
1297 $test_file = $this->getTestFile('image'); |
|
1298 list(, $test_file_extension) = explode('.', $test_file->filename); |
|
1299 |
|
1300 // Disable extension checking. |
|
1301 $this->updateFileField($field_name, $type_name, array('file_extensions' => '')); |
|
1302 |
|
1303 // Check that the file can be uploaded with no extension checking. |
|
1304 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); |
|
1305 $node = node_load($nid, NULL, TRUE); |
|
1306 $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
1307 $this->assertFileExists($node_file, 'File exists after uploading a file with no extension checking.'); |
|
1308 $this->assertFileEntryExists($node_file, 'File entry exists after uploading a file with no extension checking.'); |
|
1309 |
|
1310 // Enable extension checking for text files. |
|
1311 $this->updateFileField($field_name, $type_name, array('file_extensions' => 'txt')); |
|
1312 |
|
1313 // Check that the file with the wrong extension cannot be uploaded. |
|
1314 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); |
|
1315 $error_message = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => 'txt')); |
|
1316 $this->assertRaw($error_message, 'Node save failed when file uploaded with the wrong extension.'); |
|
1317 |
|
1318 // Enable extension checking for text and image files. |
|
1319 $this->updateFileField($field_name, $type_name, array('file_extensions' => "txt $test_file_extension")); |
|
1320 |
|
1321 // Check that the file can be uploaded with extension checking. |
|
1322 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); |
|
1323 $node = node_load($nid, NULL, TRUE); |
|
1324 $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
1325 $this->assertFileExists($node_file, 'File exists after uploading a file with extension checking.'); |
|
1326 $this->assertFileEntryExists($node_file, 'File entry exists after uploading a file with extension checking.'); |
|
1327 |
|
1328 // Remove our file field. |
|
1329 field_delete_field($field_name); |
|
1330 } |
|
1331 } |
|
1332 |
|
1333 /** |
|
1334 * Tests that files are uploaded to proper locations. |
|
1335 */ |
|
1336 class FileFieldPathTestCase extends FileFieldTestCase { |
|
1337 public static function getInfo() { |
|
1338 return array( |
|
1339 'name' => 'File field file path tests', |
|
1340 'description' => 'Test that files are uploaded to the proper location with token support.', |
|
1341 'group' => 'File', |
|
1342 ); |
|
1343 } |
|
1344 |
|
1345 /** |
|
1346 * Tests the normal formatter display on node display. |
|
1347 */ |
|
1348 function testUploadPath() { |
|
1349 $field_name = strtolower($this->randomName()); |
|
1350 $type_name = 'article'; |
|
1351 $field = $this->createFileField($field_name, $type_name); |
|
1352 $test_file = $this->getTestFile('text'); |
|
1353 |
|
1354 // Create a new node. |
|
1355 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); |
|
1356 |
|
1357 // Check that the file was uploaded to the file root. |
|
1358 $node = node_load($nid, NULL, TRUE); |
|
1359 $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
1360 $this->assertPathMatch('public://' . $test_file->filename, $node_file->uri, format_string('The file %file was uploaded to the correct path.', array('%file' => $node_file->uri))); |
|
1361 |
|
1362 // Change the path to contain multiple subdirectories. |
|
1363 $field = $this->updateFileField($field_name, $type_name, array('file_directory' => 'foo/bar/baz')); |
|
1364 |
|
1365 // Upload a new file into the subdirectories. |
|
1366 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); |
|
1367 |
|
1368 // Check that the file was uploaded into the subdirectory. |
|
1369 $node = node_load($nid, NULL, TRUE); |
|
1370 $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
1371 $this->assertPathMatch('public://foo/bar/baz/' . $test_file->filename, $node_file->uri, format_string('The file %file was uploaded to the correct path.', array('%file' => $node_file->uri))); |
|
1372 |
|
1373 // Check the path when used with tokens. |
|
1374 // Change the path to contain multiple token directories. |
|
1375 $field = $this->updateFileField($field_name, $type_name, array('file_directory' => '[current-user:uid]/[current-user:name]')); |
|
1376 |
|
1377 // Upload a new file into the token subdirectories. |
|
1378 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); |
|
1379 |
|
1380 // Check that the file was uploaded into the subdirectory. |
|
1381 $node = node_load($nid, NULL, TRUE); |
|
1382 $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
1383 // Do token replacement using the same user which uploaded the file, not |
|
1384 // the user running the test case. |
|
1385 $data = array('user' => $this->admin_user); |
|
1386 $subdirectory = token_replace('[user:uid]/[user:name]', $data); |
|
1387 $this->assertPathMatch('public://' . $subdirectory . '/' . $test_file->filename, $node_file->uri, format_string('The file %file was uploaded to the correct path with token replacements.', array('%file' => $node_file->uri))); |
|
1388 } |
|
1389 |
|
1390 /** |
|
1391 * Asserts that a file is uploaded to the right location. |
|
1392 * |
|
1393 * @param $expected_path |
|
1394 * The location where the file is expected to be uploaded. Duplicate file |
|
1395 * names to not need to be taken into account. |
|
1396 * @param $actual_path |
|
1397 * Where the file was actually uploaded. |
|
1398 * @param $message |
|
1399 * The message to display with this assertion. |
|
1400 */ |
|
1401 function assertPathMatch($expected_path, $actual_path, $message) { |
|
1402 // Strip off the extension of the expected path to allow for _0, _1, etc. |
|
1403 // suffixes when the file hits a duplicate name. |
|
1404 $pos = strrpos($expected_path, '.'); |
|
1405 $base_path = substr($expected_path, 0, $pos); |
|
1406 $extension = substr($expected_path, $pos + 1); |
|
1407 |
|
1408 $result = preg_match('/' . preg_quote($base_path, '/') . '(_[0-9]+)?\.' . preg_quote($extension, '/') . '/', $actual_path); |
|
1409 $this->assertTrue($result, $message); |
|
1410 } |
|
1411 } |
|
1412 |
|
1413 /** |
|
1414 * Tests the file token replacement in strings. |
|
1415 */ |
|
1416 class FileTokenReplaceTestCase extends FileFieldTestCase { |
|
1417 public static function getInfo() { |
|
1418 return array( |
|
1419 'name' => 'File token replacement', |
|
1420 'description' => 'Generates text using placeholders for dummy content to check file token replacement.', |
|
1421 'group' => 'File', |
|
1422 ); |
|
1423 } |
|
1424 |
|
1425 /** |
|
1426 * Creates a file, then tests the tokens generated from it. |
|
1427 */ |
|
1428 function testFileTokenReplacement() { |
|
1429 global $language; |
|
1430 $url_options = array( |
|
1431 'absolute' => TRUE, |
|
1432 'language' => $language, |
|
1433 ); |
|
1434 |
|
1435 // Create file field. |
|
1436 $type_name = 'article'; |
|
1437 $field_name = 'field_' . strtolower($this->randomName()); |
|
1438 $this->createFileField($field_name, $type_name); |
|
1439 $field = field_info_field($field_name); |
|
1440 $instance = field_info_instance('node', $field_name, $type_name); |
|
1441 |
|
1442 $test_file = $this->getTestFile('text'); |
|
1443 // Coping a file to test uploads with non-latin filenames. |
|
1444 $filename = drupal_dirname($test_file->uri) . '/текстовый файл.txt'; |
|
1445 $test_file = file_copy($test_file, $filename); |
|
1446 |
|
1447 // Create a new node with the uploaded file. |
|
1448 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); |
|
1449 |
|
1450 // Load the node and the file. |
|
1451 $node = node_load($nid, NULL, TRUE); |
|
1452 $file = file_load($node->{$field_name}[LANGUAGE_NONE][0]['fid']); |
|
1453 |
|
1454 // Generate and test sanitized tokens. |
|
1455 $tests = array(); |
|
1456 $tests['[file:fid]'] = $file->fid; |
|
1457 $tests['[file:name]'] = check_plain($file->filename); |
|
1458 $tests['[file:path]'] = check_plain($file->uri); |
|
1459 $tests['[file:mime]'] = check_plain($file->filemime); |
|
1460 $tests['[file:size]'] = format_size($file->filesize); |
|
1461 $tests['[file:url]'] = check_plain(file_create_url($file->uri)); |
|
1462 $tests['[file:timestamp]'] = format_date($file->timestamp, 'medium', '', NULL, $language->language); |
|
1463 $tests['[file:timestamp:short]'] = format_date($file->timestamp, 'short', '', NULL, $language->language); |
|
1464 $tests['[file:owner]'] = check_plain(format_username($this->admin_user)); |
|
1465 $tests['[file:owner:uid]'] = $file->uid; |
|
1466 |
|
1467 // Test to make sure that we generated something for each token. |
|
1468 $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.'); |
|
1469 |
|
1470 foreach ($tests as $input => $expected) { |
|
1471 $output = token_replace($input, array('file' => $file), array('language' => $language)); |
|
1472 $this->assertEqual($output, $expected, format_string('Sanitized file token %token replaced.', array('%token' => $input))); |
|
1473 } |
|
1474 |
|
1475 // Generate and test unsanitized tokens. |
|
1476 $tests['[file:name]'] = $file->filename; |
|
1477 $tests['[file:path]'] = $file->uri; |
|
1478 $tests['[file:mime]'] = $file->filemime; |
|
1479 $tests['[file:size]'] = format_size($file->filesize); |
|
1480 |
|
1481 foreach ($tests as $input => $expected) { |
|
1482 $output = token_replace($input, array('file' => $file), array('language' => $language, 'sanitize' => FALSE)); |
|
1483 $this->assertEqual($output, $expected, format_string('Unsanitized file token %token replaced.', array('%token' => $input))); |
|
1484 } |
|
1485 } |
|
1486 } |
|
1487 |
|
1488 /** |
|
1489 * Tests file access on private nodes. |
|
1490 */ |
|
1491 class FilePrivateTestCase extends FileFieldTestCase { |
|
1492 public static function getInfo() { |
|
1493 return array( |
|
1494 'name' => 'Private file test', |
|
1495 'description' => 'Uploads a test to a private node and checks access.', |
|
1496 'group' => 'File', |
|
1497 ); |
|
1498 } |
|
1499 |
|
1500 function setUp() { |
|
1501 parent::setUp(array('node_access_test', 'field_test')); |
|
1502 node_access_rebuild(); |
|
1503 variable_set('node_access_test_private', TRUE); |
|
1504 } |
|
1505 |
|
1506 /** |
|
1507 * Tests file access for file uploaded to a private node. |
|
1508 */ |
|
1509 function testPrivateFile() { |
|
1510 // Use 'page' instead of 'article', so that the 'article' image field does |
|
1511 // not conflict with this test. If in the future the 'page' type gets its |
|
1512 // own default file or image field, this test can be made more robust by |
|
1513 // using a custom node type. |
|
1514 $type_name = 'page'; |
|
1515 $field_name = strtolower($this->randomName()); |
|
1516 $this->createFileField($field_name, $type_name, array('uri_scheme' => 'private')); |
|
1517 |
|
1518 // Create a field with no view access - see field_test_field_access(). |
|
1519 $no_access_field_name = 'field_no_view_access'; |
|
1520 $this->createFileField($no_access_field_name, $type_name, array('uri_scheme' => 'private')); |
|
1521 |
|
1522 $test_file = $this->getTestFile('text'); |
|
1523 $nid = $this->uploadNodeFile($test_file, $field_name, $type_name, TRUE, array('private' => TRUE)); |
|
1524 $node = node_load($nid, NULL, TRUE); |
|
1525 $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; |
|
1526 // Ensure the file can be downloaded. |
|
1527 $this->drupalGet(file_create_url($node_file->uri)); |
|
1528 $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.'); |
|
1529 $this->drupalLogOut(); |
|
1530 $this->drupalGet(file_create_url($node_file->uri)); |
|
1531 $this->assertResponse(403, 'Confirmed that access is denied for the file without the needed permission.'); |
|
1532 |
|
1533 // Test with the field that should deny access through field access. |
|
1534 $this->drupalLogin($this->admin_user); |
|
1535 $nid = $this->uploadNodeFile($test_file, $no_access_field_name, $type_name, TRUE, array('private' => TRUE)); |
|
1536 $node = node_load($nid, NULL, TRUE); |
|
1537 $node_file = (object) $node->{$no_access_field_name}[LANGUAGE_NONE][0]; |
|
1538 // Ensure the file cannot be downloaded. |
|
1539 $this->drupalGet(file_create_url($node_file->uri)); |
|
1540 $this->assertResponse(403, 'Confirmed that access is denied for the file without view field access permission.'); |
|
1541 |
|
1542 // Attempt to reuse the existing file when creating a new node, and confirm |
|
1543 // that access is still denied. |
|
1544 $edit = array(); |
|
1545 $edit['title'] = $this->randomName(8); |
|
1546 $edit[$field_name . '[' . LANGUAGE_NONE . '][0][fid]'] = $node_file->fid; |
|
1547 $this->drupalPost('node/add/page', $edit, t('Save')); |
|
1548 $new_node = $this->drupalGetNodeByTitle($edit['title']); |
|
1549 $this->assertTrue(!empty($new_node), 'Node was created.'); |
|
1550 $this->assertUrl('node/' . $new_node->nid); |
|
1551 $this->assertNoRaw($node_file->filename, 'File without view field access permission does not appear after attempting to attach it to a new node.'); |
|
1552 $this->drupalGet(file_create_url($node_file->uri)); |
|
1553 $this->assertResponse(403, 'Confirmed that access is denied for the file without view field access permission after attempting to attach it to a new node.'); |
|
1554 |
|
1555 // As an anonymous user, create a temporary file with no references and |
|
1556 // confirm that only the session that uploaded it may view it. |
|
1557 $this->drupalLogout(); |
|
1558 user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array( |
|
1559 "create $type_name content", |
|
1560 'access content', |
|
1561 )); |
|
1562 $test_file = $this->getTestFile('text'); |
|
1563 $this->drupalGet('node/add/' . $type_name); |
|
1564 $edit = array('files[' . $field_name . '_' . LANGUAGE_NONE . '_0]' => drupal_realpath($test_file->uri)); |
|
1565 $this->drupalPost(NULL, $edit, t('Upload')); |
|
1566 $files = file_load_multiple(array(), array('uid' => 0)); |
|
1567 $this->assertEqual(1, count($files), 'Loaded one anonymous file.'); |
|
1568 $file = end($files); |
|
1569 $this->assertNotEqual($file->status, FILE_STATUS_PERMANENT, 'File is temporary.'); |
|
1570 $usage = file_usage_list($file); |
|
1571 $this->assertFalse($usage, 'No file usage found.'); |
|
1572 $file_url = file_create_url($file->uri); |
|
1573 $this->drupalGet($file_url); |
|
1574 $this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the temporary file.'); |
|
1575 // Close the prior connection and remove the session cookie. |
|
1576 $this->curlClose(); |
|
1577 $this->cookies = array(); |
|
1578 $this->drupalGet($file_url); |
|
1579 $this->assertResponse(403, 'Confirmed that another anonymous user cannot access the temporary file.'); |
|
1580 |
|
1581 // As an anonymous user, create a permanent file that is referenced by a |
|
1582 // published node and confirm that all anonymous users may view it. |
|
1583 $test_file = $this->getTestFile('text'); |
|
1584 $this->drupalGet('node/add/' . $type_name); |
|
1585 $edit = array(); |
|
1586 $edit['title'] = $this->randomName(); |
|
1587 $edit['files[' . $field_name . '_' . LANGUAGE_NONE . '_0]'] = drupal_realpath($test_file->uri); |
|
1588 $this->drupalPost(NULL, $edit, t('Save')); |
|
1589 $new_node = $this->drupalGetNodeByTitle($edit['title']); |
|
1590 $file = file_load($new_node->{$field_name}[LANGUAGE_NONE][0]['fid']); |
|
1591 $this->assertEqual($file->status, FILE_STATUS_PERMANENT, 'File is permanent.'); |
|
1592 $usage = file_usage_list($file); |
|
1593 $this->assertTrue($usage, 'File usage found.'); |
|
1594 $file_url = file_create_url($file->uri); |
|
1595 $this->drupalGet($file_url); |
|
1596 $this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the permanent file that is referenced by a published node.'); |
|
1597 // Close the prior connection and remove the session cookie. |
|
1598 $this->curlClose(); |
|
1599 $this->cookies = array(); |
|
1600 $this->drupalGet($file_url); |
|
1601 $this->assertResponse(200, 'Confirmed that another anonymous user also has access to the permanent file that is referenced by a published node.'); |
|
1602 |
|
1603 // As an anonymous user, create a permanent file that is referenced by an |
|
1604 // unpublished node and confirm that no anonymous users may view it (even |
|
1605 // the session that uploaded the file) because they cannot view the |
|
1606 // unpublished node. |
|
1607 $test_file = $this->getTestFile('text'); |
|
1608 $this->drupalGet('node/add/' . $type_name); |
|
1609 $edit = array(); |
|
1610 $edit['title'] = $this->randomName(); |
|
1611 $edit['files[' . $field_name . '_' . LANGUAGE_NONE . '_0]'] = drupal_realpath($test_file->uri); |
|
1612 $this->drupalPost(NULL, $edit, t('Save')); |
|
1613 $new_node = $this->drupalGetNodeByTitle($edit['title']); |
|
1614 $new_node->status = NODE_NOT_PUBLISHED; |
|
1615 node_save($new_node); |
|
1616 $file = file_load($new_node->{$field_name}[LANGUAGE_NONE][0]['fid']); |
|
1617 $this->assertEqual($file->status, FILE_STATUS_PERMANENT, 'File is permanent.'); |
|
1618 $usage = file_usage_list($file); |
|
1619 $this->assertTrue($usage, 'File usage found.'); |
|
1620 $file_url = file_create_url($file->uri); |
|
1621 $this->drupalGet($file_url); |
|
1622 $this->assertResponse(403, 'Confirmed that the anonymous uploader cannot access the permanent file when it is referenced by an unpublished node.'); |
|
1623 // Close the prior connection and remove the session cookie. |
|
1624 $this->curlClose(); |
|
1625 $this->cookies = array(); |
|
1626 $this->drupalGet($file_url); |
|
1627 $this->assertResponse(403, 'Confirmed that another anonymous user cannot access the permanent file when it is referenced by an unpublished node.'); |
|
1628 } |
|
1629 } |
|
1630 |
|
1631 /** |
|
1632 * Confirm that file field submissions work correctly for anonymous visitors. |
|
1633 */ |
|
1634 class FileFieldAnonymousSubmission extends FileFieldTestCase { |
|
1635 |
|
1636 public static function getInfo() { |
|
1637 return array( |
|
1638 'name' => 'File form anonymous submission', |
|
1639 'description' => 'Test anonymous form submission.', |
|
1640 'group' => 'File', |
|
1641 ); |
|
1642 } |
|
1643 |
|
1644 function setUp() { |
|
1645 parent::setUp(); |
|
1646 |
|
1647 // Allow node submissions by anonymous users. |
|
1648 user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array( |
|
1649 'create article content', |
|
1650 'access content', |
|
1651 )); |
|
1652 } |
|
1653 |
|
1654 /** |
|
1655 * Tests the basic node submission for an anonymous visitor. |
|
1656 */ |
|
1657 function testAnonymousNode() { |
|
1658 $bundle_label = 'Article'; |
|
1659 $node_title = 'Test page'; |
|
1660 |
|
1661 // Load the node form. |
|
1662 $this->drupalGet('node/add/article'); |
|
1663 $this->assertResponse(200, 'Loaded the article node form.'); |
|
1664 $this->assertText(strip_tags(t('Create @name', array('@name' => $bundle_label)))); |
|
1665 |
|
1666 $edit = array( |
|
1667 'title' => $node_title, |
|
1668 'body[und][0][value]' => 'Test article', |
|
1669 'body[und][0][format]' => 'filtered_html', |
|
1670 ); |
|
1671 $this->drupalPost(NULL, $edit, t('Save')); |
|
1672 $this->assertResponse(200); |
|
1673 $t_args = array('@type' => $bundle_label, '%title' => $node_title); |
|
1674 $this->assertText(strip_tags(t('@type %title has been created.', $t_args)), 'The node was created.'); |
|
1675 $matches = array(); |
|
1676 if (preg_match('@node/(\d+)$@', $this->getUrl(), $matches)) { |
|
1677 $nid = end($matches); |
|
1678 $this->assertNotEqual($nid, 0, 'The node ID was extracted from the URL.'); |
|
1679 $node = node_load($nid); |
|
1680 $this->assertNotEqual($node, NULL, 'The node was loaded successfully.'); |
|
1681 } |
|
1682 } |
|
1683 |
|
1684 /** |
|
1685 * Tests file submission for an anonymous visitor. |
|
1686 */ |
|
1687 function testAnonymousNodeWithFile() { |
|
1688 $bundle_label = 'Article'; |
|
1689 $node_title = 'Test page'; |
|
1690 |
|
1691 // Load the node form. |
|
1692 $this->drupalGet('node/add/article'); |
|
1693 $this->assertResponse(200, 'Loaded the article node form.'); |
|
1694 $this->assertText(strip_tags(t('Create @name', array('@name' => $bundle_label)))); |
|
1695 |
|
1696 // Generate an image file. |
|
1697 $image = $this->getTestImage(); |
|
1698 |
|
1699 // Submit the form. |
|
1700 $edit = array( |
|
1701 'title' => $node_title, |
|
1702 'body[und][0][value]' => 'Test article', |
|
1703 'body[und][0][format]' => 'filtered_html', |
|
1704 'files[field_image_und_0]' => drupal_realpath($image->uri), |
|
1705 ); |
|
1706 $this->drupalPost(NULL, $edit, t('Save')); |
|
1707 $this->assertResponse(200); |
|
1708 $t_args = array('@type' => $bundle_label, '%title' => $node_title); |
|
1709 $this->assertText(strip_tags(t('@type %title has been created.', $t_args)), 'The node was created.'); |
|
1710 $matches = array(); |
|
1711 if (preg_match('@node/(\d+)$@', $this->getUrl(), $matches)) { |
|
1712 $nid = end($matches); |
|
1713 $this->assertNotEqual($nid, 0, 'The node ID was extracted from the URL.'); |
|
1714 $node = node_load($nid); |
|
1715 $this->assertNotEqual($node, NULL, 'The node was loaded successfully.'); |
|
1716 $this->assertEqual($node->field_image[LANGUAGE_NONE][0]['filename'], $image->filename, 'The image was uploaded successfully.'); |
|
1717 } |
|
1718 } |
|
1719 |
|
1720 /** |
|
1721 * Tests file submission for an anonymous visitor with a missing node title. |
|
1722 */ |
|
1723 function testAnonymousNodeWithFileWithoutTitle() { |
|
1724 $this->drupalLogout(); |
|
1725 $this->_testNodeWithFileWithoutTitle(); |
|
1726 } |
|
1727 |
|
1728 /** |
|
1729 * Tests file submission for an authenticated user with a missing node title. |
|
1730 */ |
|
1731 function testAuthenticatedNodeWithFileWithoutTitle() { |
|
1732 $admin_user = $this->drupalCreateUser(array( |
|
1733 'bypass node access', |
|
1734 'access content overview', |
|
1735 'administer nodes', |
|
1736 )); |
|
1737 $this->drupalLogin($admin_user); |
|
1738 $this->_testNodeWithFileWithoutTitle(); |
|
1739 } |
|
1740 |
|
1741 /** |
|
1742 * Helper method to test file submissions with missing node titles. |
|
1743 */ |
|
1744 protected function _testNodeWithFileWithoutTitle() { |
|
1745 $bundle_label = 'Article'; |
|
1746 $node_title = 'Test page'; |
|
1747 |
|
1748 // Load the node form. |
|
1749 $this->drupalGet('node/add/article'); |
|
1750 $this->assertResponse(200, 'Loaded the article node form.'); |
|
1751 $this->assertText(strip_tags(t('Create @name', array('@name' => $bundle_label)))); |
|
1752 |
|
1753 // Generate an image file. |
|
1754 $image = $this->getTestImage(); |
|
1755 |
|
1756 // Submit the form but exclude the title field. |
|
1757 $edit = array( |
|
1758 'body[und][0][value]' => 'Test article', |
|
1759 'body[und][0][format]' => 'filtered_html', |
|
1760 'files[field_image_und_0]' => drupal_realpath($image->uri), |
|
1761 ); |
|
1762 $this->drupalPost(NULL, $edit, t('Save')); |
|
1763 $this->assertResponse(200); |
|
1764 $t_args = array('@type' => $bundle_label, '%title' => $node_title); |
|
1765 $this->assertNoText(strip_tags(t('@type %title has been created.', $t_args)), 'The node was created.'); |
|
1766 $this->assertText(t('!name field is required.', array('!name' => t('Title')))); |
|
1767 |
|
1768 // Submit the form again but this time with the missing title field. This |
|
1769 // should still work. |
|
1770 $edit = array( |
|
1771 'title' => $node_title, |
|
1772 ); |
|
1773 $this->drupalPost(NULL, $edit, t('Save')); |
|
1774 |
|
1775 // Confirm the final submission actually worked. |
|
1776 $t_args = array('@type' => $bundle_label, '%title' => $node_title); |
|
1777 $this->assertText(strip_tags(t('@type %title has been created.', $t_args)), 'The node was created.'); |
|
1778 $matches = array(); |
|
1779 if (preg_match('@node/(\d+)$@', $this->getUrl(), $matches)) { |
|
1780 $nid = end($matches); |
|
1781 $this->assertNotEqual($nid, 0, 'The node ID was extracted from the URL.'); |
|
1782 $node = node_load($nid); |
|
1783 $this->assertNotEqual($node, NULL, 'The node was loaded successfully.'); |
|
1784 $this->assertEqual($node->field_image[LANGUAGE_NONE][0]['filename'], $image->filename, 'The image was uploaded successfully.'); |
|
1785 } |
|
1786 } |
|
1787 |
|
1788 /** |
|
1789 * Generates a test image. |
|
1790 * |
|
1791 * @return stdClass |
|
1792 * A file object. |
|
1793 */ |
|
1794 function getTestImage() { |
|
1795 // Get a file to upload. |
|
1796 $file = current($this->drupalGetTestFiles('image')); |
|
1797 |
|
1798 // Add a filesize property to files as would be read by file_load(). |
|
1799 $file->filesize = filesize($file->uri); |
|
1800 |
|
1801 return $file; |
|
1802 } |
|
1803 |
|
1804 } |