admin_user = $this->drupalCreateUser(array('access content', 'view files', 'view own files', 'access media browser', 'access administration pages', 'administer site configuration', 'administer users', 'administer permissions', 'administer content types', 'administer nodes', 'administer files', 'bypass node access', 'bypass file access', 'administer fields')); $this->drupalLogin($this->admin_user); } /** * Retrieves a sample file of the specified type. */ function getTestFile($type_name, $size = NULL) { // Get a file to upload. $file = current($this->drupalGetTestFiles($type_name, $size)); // Add a filesize property to files as would be read by file_load(). $file->filesize = filesize($file->uri); return $file; } /** * Creates a new file entity. * * @param $settings * A list of settings that will be added to the entity defaults. */ protected function createFileEntity($settings = array()) { $file = new stdClass(); // Populate defaults array. $settings += array( 'filepath' => 'Файл для тестирования ' . $this->randomName(), // Prefix with non-latin characters to ensure that all file-related tests work with international filenames. 'filemime' => 'text/plain', 'uid' => 1, 'timestamp' => REQUEST_TIME, 'status' => FILE_STATUS_PERMANENT, 'contents' => "file_put_contents() doesn't seem to appreciate empty strings so let's put in some data.", 'scheme' => file_default_scheme(), 'type' => NULL, ); $filepath = $settings['scheme'] . '://' . $settings['filepath']; file_put_contents($filepath, $settings['contents']); $this->assertTrue(is_file($filepath), t('The test file exists on the disk.'), 'Create test file'); $file = new stdClass(); $file->uri = $filepath; $file->filename = drupal_basename($file->uri); $file->filemime = $settings['filemime']; $file->uid = $settings['uid']; $file->timestamp = $settings['timestamp']; $file->filesize = filesize($file->uri); $file->status = $settings['status']; $file->type = $settings['type']; // The file type is used as a bundle key, and therefore, must not be NULL. if (!isset($file->type)) { $file->type = FILE_TYPE_NONE; } // If the file isn't already assigned a real type, determine what type should // be assigned to it. if ($file->type === FILE_TYPE_NONE) { $type = file_get_type($file); if (isset($type)) { $file->type = $type; } } // Write the record directly rather than calling file_save() so we don't // invoke the hooks. $this->assertNotIdentical(drupal_write_record('file_managed', $file), FALSE, t('The file was added to the database.'), 'Create test file'); return $file; } /** * Creates a new file field. * * @param $name * The name of the new field (all lowercase), exclude the "field_" prefix. * @param $type_name * The node type that this field will be added to. * @param $field_settings * A list of field settings that will be added to the defaults. * @param $instance_settings * A list of instance settings that will be added to the instance defaults. * @param $widget_settings * A list of widget settings that will be added to the widget defaults. */ function createFileField($name, $type_name, $field_settings = array(), $instance_settings = array(), $widget_settings = array()) { $field = array( 'field_name' => $name, 'type' => 'file', 'settings' => array(), 'cardinality' => !empty($field_settings['cardinality']) ? $field_settings['cardinality'] : 1, ); $field['settings'] = array_merge($field['settings'], $field_settings); field_create_field($field); $this->attachFileField($name, 'node', $type_name, $instance_settings, $widget_settings); } /** * Attaches a file field to an entity. * * @param $name * The name of the new field (all lowercase), exclude the "field_" prefix. * @param $entity_type * The entity type this field will be added to. * @param $bundle * The bundle this field will be added to. * @param $field_settings * A list of field settings that will be added to the defaults. * @param $instance_settings * A list of instance settings that will be added to the instance defaults. * @param $widget_settings * A list of widget settings that will be added to the widget defaults. */ function attachFileField($name, $entity_type, $bundle, $instance_settings = array(), $widget_settings = array()) { $instance = array( 'field_name' => $name, 'label' => $name, 'entity_type' => $entity_type, 'bundle' => $bundle, 'required' => !empty($instance_settings['required']), 'settings' => array(), 'widget' => array( 'type' => 'media_generic', 'settings' => array(), ), ); $instance['settings'] = array_merge($instance['settings'], $instance_settings); $instance['widget']['settings'] = array_merge($instance['widget']['settings'], $widget_settings); field_create_instance($instance); } /** * Attaches a file to a node. */ function attachNodeFile($file, $field_name, $nid_or_type, $new_revision = TRUE, $extras = array()) { $langcode = LANGUAGE_NONE; $edit = array( "title" => $this->randomName(), 'revision' => (string) (int) $new_revision, ); if (is_numeric($nid_or_type)) { $nid = $nid_or_type; } else { // Add a new node. $extras['type'] = $nid_or_type; $node = $this->drupalCreateNode($extras); $nid = $node->nid; // Save at least one revision to better simulate a real site. $this->drupalCreateNode(get_object_vars($node)); $node = node_load($nid, NULL, TRUE); $this->assertNotEqual($nid, $node->vid, 'Node revision exists.'); } // Attach a file to the node. $edit[$field_name . '[' . $langcode . '][0][fid]'] = $file->fid; $this->drupalPost("node/$nid/edit", $edit, t('Save')); return $nid; } /** * Replaces a file within a node. */ function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) { $edit = array( $field_name . '[' . LANGUAGE_NONE . '][0][fid]' => $file->fid, 'revision' => (string) (int) $new_revision, ); $this->drupalPost('node/' . $nid . '/edit', array(), t('Remove')); $this->drupalPost(NULL, $edit, t('Save')); } /** * Asserts that a file exists physically on disk. */ function assertFileExists($file, $message = NULL) { $message = isset($message) ? $message : format_string('File %file exists on the disk.', array('%file' => $file->uri)); $this->assertTrue(is_file($file->uri), $message); } /** * Asserts that a file exists in the database. */ function assertFileEntryExists($file, $message = NULL) { entity_get_controller('file')->resetCache(); $db_file = file_load($file->fid); $message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', array('%file' => $file->uri)); $this->assertEqual($db_file->uri, $file->uri, $message); } /** * Asserts that a file does not exist on disk. */ function assertFileNotExists($file, $message = NULL) { $message = isset($message) ? $message : format_string('File %file exists on the disk.', array('%file' => $file->uri)); $this->assertFalse(is_file($file->uri), $message); } /** * Asserts that a file does not exist in the database. */ function assertFileEntryNotExists($file, $message) { entity_get_controller('file')->resetCache(); $message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', array('%file' => $file->uri)); $this->assertFalse(file_load($file->fid), $message); } /** * Asserts that a file's status is set to permanent in the database. */ function assertFileIsPermanent($file, $message = NULL) { $message = isset($message) ? $message : format_string('File %file is permanent.', array('%file' => $file->uri)); $this->assertTrue($file->status == FILE_STATUS_PERMANENT, $message); } } /** * Tests the 'media' element type. * * @todo Create a MediaFileTestCase base class and move MediaFileFieldTestCase * methods that aren't related to fields into it. */ class MediaElementTestCase extends MediaFileFieldTestCase { public static function getInfo() { return array( 'name' => 'Media element test', 'description' => 'Tests the media element type.', 'group' => 'Media', ); } /** * Tests the media element type. */ function testMedia() { // Check that $element['#size'] is passed to the child upload element. $this->drupalGet('media/test'); $this->assertFieldByXpath('//input[@name="media[nested_media]" and @size="13"]', NULL, 'The custom #size attribute is passed to the child upload element.'); // Perform the tests with all permutations of $form['#tree'] and // $element['#extended']. foreach (array(0, 1) as $tree) { foreach (array(0, 1) as $extended) { $test_file = $this->getTestFile('text'); $test_file->uid = $this->admin_user->uid; $test_file = file_save($test_file); $path = 'media/test/' . $tree . '/' . $extended; $input_base_name = $tree ? 'nested_media' : 'media'; // Submit without a file. $this->drupalPost($path, array(), t('Save')); $this->assertRaw(t('The file id is %fid.', array('%fid' => 0)), 'Submitted without a file.'); // Submit a new file, without using the Attach button. $edit = array('media[' . $input_base_name . ']' => $test_file->fid); $this->drupalPost($path, $edit, t('Save')); $this->assertRaw(t('The file id is %fid.', array('%fid' => $test_file->fid)), 'Submit handler has correct file info.'); // Now, test the Attach and Remove buttons, with and without Ajax. foreach (array(FALSE) as $ajax) { // Attach, then Submit. $this->drupalGet($path); $edit = array('media[' . $input_base_name . ']' => $test_file->fid); if ($ajax) { $this->drupalPostAJAX(NULL, $edit, $input_base_name . '_attach_button'); } else { $this->drupalPost(NULL, $edit, t('Attach')); } $this->drupalPost(NULL, array(), t('Save')); $this->assertRaw(t('The file id is %fid.', array('%fid' => $test_file->fid)), 'Submit handler has correct file info.'); // Attach, then Remove, then Submit. $this->drupalGet($path); $edit = array('media[' . $input_base_name . ']' => $test_file->fid); if ($ajax) { $this->drupalPostAJAX(NULL, $edit, $input_base_name . '_attach_button'); $this->drupalPostAJAX(NULL, array(), $input_base_name . '_remove_button'); } else { $this->drupalPost(NULL, $edit, t('Attach')); $this->drupalPost(NULL, array(), t('Remove')); } $this->drupalPost(NULL, array(), t('Save')); $this->assertRaw(t('The file id is %fid.', array('%fid' => 0)), 'Submission after file attachment and removal was successful.'); } } } } } /** * Test media file administration page functionality. */ class MediaAdminTestCase extends MediaFileFieldTestCase { public static function getInfo() { return array( 'name' => 'Media file administration', 'description' => 'Test media file administration page functionality.', 'group' => 'Media', ); } function setUp() { parent::setUp(); // Remove the "view files" permission which is set // by default for all users so we can test this permission // correctly. $roles = user_roles(); foreach ($roles as $rid => $role) { user_role_revoke_permissions($rid, array('view files')); } $this->base_user_1 = $this->drupalCreateUser(array('administer files')); $this->base_user_2 = $this->drupalCreateUser(array('administer files', 'view own private files')); $this->base_user_3 = $this->drupalCreateUser(array('administer files', 'view private files')); $this->base_user_4 = $this->drupalCreateUser(array('administer files', 'edit any document files', 'delete any document files', 'edit any image files', 'delete any image files')); } /** * Tests that the table sorting works on the files admin pages. */ function testFilesAdminSort() { $i = 0; foreach (array('dd', 'aa', 'DD', 'bb', 'cc', 'CC', 'AA', 'BB') as $prefix) { $this->createFileEntity(array('filepath' => $prefix . $this->randomName(6), 'timestamp' => $i)); $i++; } // Test that the default sort by file_managed.timestamp DESC actually fires properly. $files_query = db_select('file_managed', 'fm') ->fields('fm', array('fid')) ->orderBy('timestamp', 'DESC') ->execute() ->fetchCol(); $files_form = array(); $this->drupalGet('admin/content/file/thumbnails'); foreach ($this->xpath('//ul[@class="media-list-thumbnails"]/li/div[@data-fid]/@data-fid') as $input) { $files_form[] = $input; } $this->assertEqual($files_query, $files_form, 'Files are sorted in the form according to the default query.'); // Compare the rendered HTML node list to a query for the files ordered by // filename to account for possible database-dependent sort order. $files_query = db_select('file_managed', 'fm') ->fields('fm', array('fid')) ->orderBy('filename') ->execute() ->fetchCol(); $files_form = array(); $this->drupalGet('admin/content/file/thumbnails', array('query' => array('sort' => 'asc', 'order' => 'Title'))); foreach ($this->xpath('//ul[@class="media-list-thumbnails"]/li/div[@data-fid]/@data-fid') as $input) { $files_form[] = $input; } $this->assertEqual($files_query, $files_form, 'Files are sorted in the form the same as they are in the query.'); } /** * Tests files overview with different user permissions. */ function testFilesAdminPages() { $files['public_image'] = $this->createFileEntity(array('scheme' => 'public', 'uid' => $this->base_user_1->uid, 'type' => 'image')); $files['public_document'] = $this->createFileEntity(array('scheme' => 'public', 'uid' => $this->base_user_2->uid, 'type' => 'document')); $files['private_image'] = $this->createFileEntity(array('scheme' => 'private', 'uid' => $this->base_user_1->uid, 'type' => 'image')); $files['private_document'] = $this->createFileEntity(array('scheme' => 'private', 'uid' => $this->base_user_2->uid, 'type' => 'document')); // Verify view and edit links for any file. $this->drupalGet('admin/content/file/thumbnails'); $this->assertResponse(200); foreach ($files as $file) { $this->assertLinkByHref('file/' . $file->fid); $this->assertLinkByHref('file/' . $file->fid . '/edit'); // Verify tableselect. $this->assertFieldByName('files[' . $file->fid . ']', '', t('Tableselect found.')); } // Verify no operation links are displayed for regular users. $this->drupalLogout(); $this->drupalLogin($this->base_user_1); $this->drupalGet('admin/content/file/thumbnails'); $this->assertResponse(200); $this->assertLinkByHref('file/' . $files['public_image']->fid); $this->assertLinkByHref('file/' . $files['public_document']->fid); $this->assertNoLinkByHref('file/' . $files['public_image']->fid . '/edit'); $this->assertNoLinkByHref('file/' . $files['public_document']->fid . '/edit'); // Verify no tableselect. $this->assertNoFieldByName('files[' . $files['public_image']->fid . ']', '', t('No tableselect found.')); // Verify private file is displayed with permission. $this->drupalLogout(); $this->drupalLogin($this->base_user_2); $this->drupalGet('admin/content/file/thumbnails'); $this->assertResponse(200); $this->assertLinkByHref('file/' . $files['private_document']->fid); // Verify no operation links are displayed. $this->assertNoLinkByHref('file/' . $files['private_document']->fid . '/edit'); // Verify user cannot see private file of other users. $this->assertNoLinkByHref('file/' . $files['private_image']->fid); $this->assertNoLinkByHref('file/' . $files['private_image']->fid . '/edit'); // Verify no tableselect. $this->assertNoFieldByName('files[' . $files['private_document']->fid . ']', '', t('No tableselect found.')); // Verify private file is displayed with permission. $this->drupalLogout(); $this->drupalLogin($this->base_user_3); $this->drupalGet('admin/content/file/thumbnails'); $this->assertResponse(200); // Verify user can see private file of other users. $this->assertLinkByHref('file/' . $files['private_document']->fid); $this->assertLinkByHref('file/' . $files['private_image']->fid); // Verify operation links are displayed for users with appropriate permission. $this->drupalLogout(); $this->drupalLogin($this->base_user_4); $this->drupalGet('admin/content/file/thumbnails'); $this->assertResponse(200); foreach ($files as $file) { $this->assertLinkByHref('file/' . $file->fid); $this->assertLinkByHref('file/' . $file->fid . '/edit'); } // Verify file access can be bypassed. $this->drupalLogout(); $this->drupalLogin($this->admin_user); $this->drupalGet('admin/content/file/thumbnails'); $this->assertResponse(200); foreach ($files as $file) { $this->assertLinkByHref('file/' . $file->fid); $this->assertLinkByHref('file/' . $file->fid . '/edit'); } } } /** * Tests the media hooks. */ class MediaHooksTestCase extends MediaFileFieldTestCase { public static function getInfo() { return array( 'name' => 'Media hooks test', 'description' => 'Tests the Media hooks.', 'group' => 'Media', ); } function setUp() { parent::setUp(); } /** * Tests that the media browser hooks. */ function testMediaBrowserHooks() { // Enable media_module_test.module's hook_media_browser_plugin_info() // implementation and ensure it is working as designed. variable_set('media_module_test_media_browser_plugin_info', TRUE); $this->drupalGet('media/browser'); $this->assertRaw(t('Media module test'), 'Custom browser plugin found.'); // Enable media_module_test.module's hook_media_browser_plugin_info_alter() // implementation and ensure it is working as designed. variable_set('media_module_test_media_browser_plugin_info_alter', TRUE); $this->drupalGet('media/browser'); $this->assertRaw(t('Altered plugin title'), 'Custom browser plugin was successfully altered.'); // Enable media_module_test.module's hook_media_browser_plugins_alter() // implementation and ensure it is working as designed. variable_set('media_module_test_media_browser_plugins_alter', TRUE); $this->drupalGet('media/browser'); $this->assertRaw(t('Altered browser plugin output.'), 'Custom browser plugin was successfully altered before being rendered.'); } } /** * Tests the media browser 'Library' tab. */ class MediaBrowserLibraryTestCase extends MediaFileFieldTestCase { public static function getInfo() { return array( 'name' => 'Media browser library test', 'description' => 'Tests the media browser library tab.', 'group' => 'Media', ); } function setUp() { parent::setUp(); $this->base_user_1 = $this->drupalCreateUser(array('access media browser', 'create files', 'administer files')); $this->base_user_2 = $this->drupalCreateUser(array('access media browser', 'create files', 'administer files', 'view own private files')); $this->base_user_3 = $this->drupalCreateUser(array('access media browser', 'create files', 'administer files', 'view private files')); } /** * Tests that the views sorting works on the media browser 'Library' tab. */ function testFilesBrowserSort() { // Load only the 'Library' tab of the media browser. $options = array( 'query' => array( 'enabledPlugins' => array( 'media_default--media_browser_1' => 'media_default--media_browser_1', ), ), ); $i = 0; foreach (array('dd', 'aa', 'DD', 'bb', 'cc', 'CC', 'AA', 'BB') as $prefix) { $this->createFileEntity(array('filepath' => $prefix . $this->randomName(6), 'timestamp' => $i)); $i++; } // Test that the default sort by file_managed.timestamp DESC actually fires properly. $files_query = db_select('file_managed', 'fm') ->fields('fm', array('fid')) ->orderBy('timestamp', 'DESC') ->execute() ->fetchCol(); $files_form = array(); $this->drupalGet('media/browser', $options); foreach ($this->xpath('//ul[@class="media-list-thumbnails"]/li/div[@data-fid]/@data-fid') as $input) { $files_form[] = $input; } $this->assertEqual($files_query, $files_form, 'Files are sorted in the form according to the default query.'); } /** * Tests media browser 'Library' tab with different user permissions. */ function testFilesBrowserLibrary() { // Load only the 'Library' tab of the media browser. $options = array( 'query' => array( 'enabledPlugins' => array( 'media_default--media_browser_1' => 'media_default--media_browser_1', ), ), ); $files['public_image'] = $this->createFileEntity(array('scheme' => 'public', 'uid' => $this->base_user_1->uid, 'type' => 'image')); $files['public_document'] = $this->createFileEntity(array('scheme' => 'public', 'uid' => $this->base_user_2->uid, 'type' => 'document')); $files['private_image'] = $this->createFileEntity(array('scheme' => 'private', 'uid' => $this->base_user_1->uid, 'type' => 'image')); $files['private_document'] = $this->createFileEntity(array('scheme' => 'private', 'uid' => $this->base_user_2->uid, 'type' => 'document')); // Verify all files are displayed for administrators. $this->drupalGet('media/browser', $options); $this->assertResponse(200); foreach ($files as $file) { $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $file->fid, )); $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $file->fid))); } // Verify public files are displayed. $this->drupalLogout(); $this->drupalLogin($this->base_user_1); $this->drupalGet('media/browser', $options); $this->assertResponse(200); $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $files['public_image']->fid, )); $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $files['public_image']->fid))); $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $files['public_document']->fid, )); $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $files['public_document']->fid))); // Verify private file is displayed with permission. $this->drupalLogout(); $this->drupalLogin($this->base_user_2); $this->drupalGet('media/browser', $options); $this->assertResponse(200); $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $files['private_document']->fid, )); $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $files['private_document']->fid))); // Verify user cannot see private file of other users. $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $files['private_image']->fid, )); $this->assertNoFieldByXPath($xpath, TRUE, format_string('File with file ID %fid not found.', array('%fid' => $files['private_image']->fid))); // Verify private file is displayed with permission. $this->drupalLogout(); $this->drupalLogin($this->base_user_3); $this->drupalGet('media/browser', $options); $this->assertResponse(200); // Verify user can see private file of other users. $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $files['private_document']->fid, )); $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $files['private_document']->fid))); $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $files['private_image']->fid, )); $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $files['private_image']->fid))); // Verify file access can be bypassed. $this->drupalLogout(); $this->drupalLogin($this->admin_user); $this->drupalGet('media/browser', $options); $this->assertResponse(200); foreach ($files as $file) { $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $file->fid, )); $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $file->fid))); } } } /** * Tests the media browser settings. */ class MediaBrowserSettingsTestCase extends MediaFileFieldTestCase { public static function getInfo() { return array( 'name' => 'Media browser settings test', 'description' => 'Tests the media browser settings.', 'group' => 'Media', ); } function setUp() { parent::setUp('file_test'); } /** * Tests the media browser settings. */ function testBrowserSettings() { $settings = array( 'scheme' => array('public', 'private'), 'type' => array('image', 'document'), 'extension' => array('jpg', 'txt'), ); // Perform the tests with unique permutations of $scheme, $type and // $extension. foreach ($settings['scheme'] as $scheme) { foreach ($settings['type'] as $type) { foreach ($settings['extension'] as $extension) { $file = $this->createFileEntity(array('scheme' => $scheme, 'uid' => $this->admin_user->uid, 'type' => $type, 'filemime' => media_get_extension_mimetype($extension))); // Some of the settings such as the scheme and extension are unsafe to // pass as query arguments, cache them and pass the cache ID. $options = array( 'enabledPlugins' => array( 'media_default--media_browser_1' => 'media_default--media_browser_1', ), 'schemes' => array($scheme), 'types' => array($type), 'file_extensions' => $extension, ); $cid = drupal_get_token(drupal_random_bytes(32)); cache_set('media_options:' . $cid, $options, 'cache_form', REQUEST_TIME + 21600); // Verify that the file is displayed. $this->drupalGet('media/browser', array('query' => array('options' => $cid))); $this->assertResponse(200); $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $file->fid, )); $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $file->fid))); // Verify that no other files are also displayed. $files = $this->xpath('//ul[@class="media-list-thumbnails"]/li/div[@data-fid]'); $this->assertEqual(count($files), 1, 'There is only one file that matches the current browser configuration.'); } } } // Perform the tests with none and all of the restrictions. foreach (array('none', 'all') as $restrictions) { $options = array( 'enabledPlugins' => array( 'media_default--media_browser_1' => 'media_default--media_browser_1', ), ); switch ($restrictions) { case 'none': $options['schemes'] = array(); $options['types'] = array(); $options['file_extensions'] = array(); break; case 'all': $options['schemes'] = $settings['scheme']; $options['types'] = $settings['type']; $options['file_extensions'] = implode(' ', $settings['extension']); break; } $cid = drupal_get_token(drupal_random_bytes(32)); cache_set('media_options:' . $cid, $options, 'cache_form', REQUEST_TIME + 21600); // Verify that all of the files are displayed. $this->drupalGet('media/browser', array('query' => array('options' => $cid))); $this->assertResponse(200); $files = $this->xpath('//ul[@class="media-list-thumbnails"]/li/div[@data-fid]'); $this->assertEqual(count($files), 8, format_string('All of the files were displayed when %restrictions of the restrictions were enabled.', array('%restrictions' => $restrictions))); } // Verify that extension restrictions do not affect remote files. $scheme = 'dummy-remote'; $type = 'video'; $extension = 'mp4'; $file = $this->createFileEntity(array('scheme' => $scheme, 'uid' => $this->admin_user->uid, 'type' => $type, 'filemime' => media_get_extension_mimetype($extension))); $options = array( 'enabledPlugins' => array( 'media_default--media_browser_1' => 'media_default--media_browser_1', ), 'schemes' => array($scheme, 'public'), // Include a local stream wrapper in order to trigger extension restrictions. 'types' => array($type), 'file_extensions' => 'fake', // Use an invalid file extension to ensure that it does not affect restrictions. ); $cid = drupal_get_token(drupal_random_bytes(32)); cache_set('media_options:' . $cid, $options, 'cache_form', REQUEST_TIME + 21600); // Verify that the file is displayed. $this->drupalGet('media/browser', array('query' => array('options' => $cid))); $this->assertResponse(200); $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $file->fid, )); $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $file->fid))); // Verify that no other files are also displayed. $files = $this->xpath('//ul[@class="media-list-thumbnails"]/li/div[@data-fid]'); $this->assertEqual(count($files), 1, 'There is only one file that matches the current browser configuration.'); } } /** * Tests the media browser 'My files' tab. */ class MediaBrowserMyFilesTestCase extends MediaFileFieldTestCase { public static function getInfo() { return array( 'name' => 'Media browser my files test', 'description' => 'Tests the media browser my files tab.', 'group' => 'Media', ); } function setUp() { parent::setUp(); $this->base_user_1 = $this->drupalCreateUser(array('access media browser', 'create files', 'administer files')); $this->base_user_2 = $this->drupalCreateUser(array('access media browser', 'create files', 'administer files', 'view own files')); $this->base_user_3 = $this->drupalCreateUser(array('access media browser', 'create files', 'administer files', 'view own files', 'view own private files')); } /** * Tests media browser 'My files' tab with different user permissions. */ function testFilesBrowserMyFiles() { // Load only the 'My files' tab of the media browser. $options = array( 'query' => array( 'enabledPlugins' => array( 'media_default--media_browser_my_files' => 'media_default--media_browser_my_files', ), ), ); $files['public_image'] = $this->createFileEntity(array('scheme' => 'public', 'uid' => $this->base_user_2->uid, 'type' => 'image')); $files['public_document'] = $this->createFileEntity(array('scheme' => 'public', 'uid' => $this->base_user_3->uid, 'type' => 'document')); $files['private_image'] = $this->createFileEntity(array('scheme' => 'private', 'uid' => $this->base_user_2->uid, 'type' => 'image')); $files['private_document'] = $this->createFileEntity(array('scheme' => 'private', 'uid' => $this->base_user_3->uid, 'type' => 'document')); // Verify administrators do not have any special access to files. $this->drupalGet('media/browser', $options); $this->assertResponse(200); foreach ($files as $file) { $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $file->fid, )); $this->assertNoFieldByXPath($xpath, TRUE, format_string('File with file ID %fid not found.', array('%fid' => $file->fid))); } // Verify users require the 'view own files' permission in order to access // the 'My files' tab. $this->drupalLogout(); $this->drupalLogin($this->base_user_1); $this->drupalGet('media/browser', $options); $this->assertResponse(200); $xpath = $this->buildXPathQuery('//div[@class="media_default--media_browser_my_files"]'); $this->assertNoFieldByXPath($xpath, TRUE, 'User with insufficient permissions was unable to view the My files tab.'); // Verify own public files are displayed. $this->drupalLogout(); $this->drupalLogin($this->base_user_2); $this->drupalGet('media/browser', $options); $this->assertResponse(200); $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $files['public_image']->fid, )); $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $files['public_image']->fid))); // Verify own private file is displayed with permission. $this->drupalLogout(); $this->drupalLogin($this->base_user_3); $this->drupalGet('media/browser', $options); $this->assertResponse(200); $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $files['private_document']->fid, )); $this->assertFieldByXPath($xpath, TRUE, format_string('File with file ID %fid found.', array('%fid' => $files['private_document']->fid))); // Verify user cannot see files of other users. $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $files['public_image']->fid, )); $this->assertNoFieldByXPath($xpath, TRUE, format_string('File with file ID %fid not found.', array('%fid' => $files['public_image']->fid))); $xpath = $this->buildXPathQuery('//ul[@class="media-list-thumbnails"]/li/div[@data-fid=:fid]/@data-fid', array( ':fid' => $files['private_image']->fid, )); $this->assertNoFieldByXPath($xpath, TRUE, format_string('File with file ID %fid not found.', array('%fid' => $files['private_image']->fid))); } } /** * Tests the 'media' element type settings. */ class MediaElementSettingsTestCase extends MediaFileFieldTestCase { public static function getInfo() { return array( 'name' => 'Media element settings test', 'description' => 'Tests the media element type JavaScript settings.', 'group' => 'Media', ); } /** * Tests the media element type settings. */ function testElementSettings() { $form = array( '#type' => 'media', ); drupal_render($form); $javascript = drupal_get_js(); $global = array( 'media' => array( 'global' => array( 'global' => array( 'types' => array(), 'schemes' => array(), ), ), ), ); $settings = drupal_json_encode(drupal_array_merge_deep_array($global)); $this->assertTrue(strpos($javascript, $settings) > 0, 'Rendered media element adds the global settings.'); } /** * Tests that the field widget does not contain the insecure settings. */ function testInsecureSettings() { // Use 'page' instead of 'article', so that the 'article' image field does // not conflict with this test. If in the future the 'page' type gets its // own default file or image field, this test can be made more robust by // using a custom node type. $type_name = 'page'; $field_name = strtolower($this->randomName()); $this->createFileField($field_name, $type_name); $this->drupalGet("node/add/$type_name"); $insecure_settings = array( 'file_directory', 'file_extensions', 'max_filesize', 'uri_scheme', ); foreach ($insecure_settings as $setting) { $this->assertNoRaw($setting, format_string('Media file field widget does not contain the insecure element-specific setting @setting.', array( '@setting' => $setting, ))); } } /** * Tests that insecure settings are not processed when sent via query parameters. */ function testBrowserInsecureQueryParameters() { // Test file directory override. $path = file_unmanaged_save_data('directorytest', 'temporary://directorytest.txt'); $data = array('files[upload]' => drupal_realpath($path)); $this->drupalPost('media/browser', $data, t('Upload'), array('query' => array('file_directory' => 'insecure_upload'))); // Verify that the file was placed in the normal public:// path instead of the folder we specified. $this->assertFalse(is_file('public://insecure_upload/directorytest.txt'), 'File was not uploaded to the directory specified in the query parameters.'); $this->assertTrue(is_file('public://directorytest.txt'), 'File was uploaded to the default public directory.'); // Test file_extensions override. $path = file_unmanaged_save_data('extensiontest', 'temporary://extensiontest.exe'); $data = array('files[upload]' => drupal_realpath($path)); $this->drupalPost('media/browser', $data, t('Upload'), array('query' => array('file_extensions' => 'exe'))); $this->assertFalse(is_file('public://extensiontest.exe'), 'File with extension passed via query parameter was not uploaded.'); // Test max_filesize override. variable_set('file_entity_max_filesize', '8 bytes'); $path = file_unmanaged_save_data('maxfilesize', 'temporary://maxfilesize.txt'); $data = array('files[upload]' => drupal_realpath($path)); $this->drupalPost('media/browser', $data, t('Upload'), array('query' => array('max_filesize' => '100 bytes'))); $this->assertFalse(is_file('public://maxfilesize.txt'), 'File larger than max file size was not uploaded with larger query parameter.'); variable_del('file_entity_max_filesize'); // Test uri_scheme override. $path = file_unmanaged_save_data('urischeme', 'temporary://urischeme.txt'); $data = array('files[upload]' => drupal_realpath($path)); $this->drupalPost('media/browser', $data, t('Upload'), array('query' => array('uri_scheme' => 'private'))); $this->assertFalse(is_file('private://urischeme.txt'), 'File was not uploaded to scheme set in URL.'); $this->assertTrue(is_file('public://urischeme.txt'), 'File was uploaded to default scheme instead of scheme set in URL.'); // Test upload_validators override. $path = file_unmanaged_save_data('uploadvalidators', 'temporary://uploadvalidators.txt'); $data = array('files[upload]' => drupal_realpath($path)); $this->drupalPost('media/browser', $data, t('Upload'), array('query' => array('upload_validators' => array('file_move' => array('public://exploit.php'))))); $this->assertFalse(is_file('public://exploit.php'), 'file_move() was not triggered by upload_validators parameter.'); $this->assertTrue(is_file('public://uploadvalidators.txt'), 'File was uploaded without triggering file_move().'); } /** * Tests the media file field widget settings. */ function testWidgetSettings() { // Use 'page' instead of 'article', so that the 'article' image field does // not conflict with this test. If in the future the 'page' type gets its // own default file or image field, this test can be made more robust by // using a custom node type. $type_name = 'page'; $field_name = strtolower($this->randomName()); $this->createFileField($field_name, $type_name); $field = field_info_field($field_name); $instance = field_info_instance('node', $field_name, $type_name); $javascript = $this->drupalGet("node/add/$type_name"); $multiselect = ($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED); $field_widget = array( 'elements' => array( '.js-media-element-edit-' . $field_name . '-' . LANGUAGE_NONE . '-0-upload' => array( 'global' => array( 'types' => array( 'image' => 'image', ), 'enabledPlugins' => array(), 'schemes' => array( 'public' => 'public', ), 'file_directory' => '', 'file_extensions' => 'txt', 'max_filesize' => '', 'uri_scheme' => 'public', 'multiselect' => $multiselect, 'field' => $field_name, ), ), ), ); $settings = drupal_json_encode(drupal_array_merge_deep_array($field_widget)); $string_with_options = '-0-upload":{"global":{"options":"'; $index_of_cid = strpos($javascript, $string_with_options) + strlen($string_with_options); $index_end_of_cid = strpos($javascript, '"', $index_of_cid + 1); $cid = substr($javascript, $index_of_cid, ($index_end_of_cid - $index_of_cid)); // Retrieve the security sensitive options from the cache using the cid parsed out from the $javascript variable $retrieved_settings = cache_get('media_options:' . $cid, 'cache_form'); $retrieved_settings = array('.js-media-element-edit-' . $field_name . '-' . LANGUAGE_NONE . '-0-upload' => array( 'global' => $retrieved_settings->data)); $retrieved_settings_json = drupal_json_encode($retrieved_settings); $this->assertTrue($retrieved_settings_json == $settings, 'Media file field widget retrieved from cache and has element-specific settings.'); $this->assertTrue(strpos($javascript, $cid) > 0, 'Media file field widget is cached and its` cache id is found.'); } } /** * Tests file handling with node revisions. */ class MediaFileFieldRevisionTestCase extends MediaFileFieldTestCase { public static function getInfo() { return array( 'name' => 'Media file field revision test', 'description' => 'Test creating and deleting revisions with files attached.', 'group' => 'Media', ); } /** * Tests creating multiple revisions of a node and managing attached files. * * Expected behaviors: * - Adding a new revision will make another entry in the field table, but * the original file will not be duplicated. * - Deleting a revision should not delete the original file if the file * is in use by another revision. * - When the last revision that uses a file is deleted, the original file * should be deleted also. */ function testRevisions() { $type_name = 'article'; $field_name = strtolower($this->randomName()); $this->createFileField($field_name, $type_name); $field = field_info_field($field_name); $instance = field_info_instance('node', $field_name, $type_name); // Attach the same fields to users. $this->attachFileField($field_name, 'user', 'user'); $test_file = $this->getTestFile('text'); $test_file->uid = $this->admin_user->uid; $test_file = file_save($test_file); // Create a new node with the uploaded file. $nid = $this->attachNodeFile($test_file, $field_name, $type_name); // Check that the file exists on disk and in the database. $node = node_load($nid, NULL, TRUE); $node_file_r1 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; $node_vid_r1 = $node->vid; $this->assertFileExists($node_file_r1, 'New file saved to disk on node creation.'); $this->assertFileEntryExists($node_file_r1, 'File entry exists in database on node creation.'); $this->assertFileIsPermanent($node_file_r1, 'File is permanent.'); // Upload another file to the same node in a new revision. $this->replaceNodeFile($test_file, $field_name, $nid); $node = node_load($nid, NULL, TRUE); $node_file_r2 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; $node_vid_r2 = $node->vid; $this->assertFileExists($node_file_r2, 'Replacement file exists on disk after creating new revision.'); $this->assertFileEntryExists($node_file_r2, 'Replacement file entry exists in database after creating new revision.'); $this->assertFileIsPermanent($node_file_r2, 'Replacement file is permanent.'); // Check that the original file is still in place on the first revision. $node = node_load($nid, $node_vid_r1, TRUE); $this->assertEqual($node_file_r1, (object) $node->{$field_name}[LANGUAGE_NONE][0], 'Original file still in place after replacing file in new revision.'); $this->assertFileExists($node_file_r1, 'Original file still in place after replacing file in new revision.'); $this->assertFileEntryExists($node_file_r1, 'Original file entry still in place after replacing file in new revision'); $this->assertFileIsPermanent($node_file_r1, 'Original file is still permanent.'); // Save a new version of the node without any changes. // Check that the file is still the same as the previous revision. $this->drupalPost('node/' . $nid . '/edit', array('revision' => '1'), t('Save')); $node = node_load($nid, NULL, TRUE); $node_file_r3 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; $node_vid_r3 = $node->vid; $this->assertEqual($node_file_r2, $node_file_r3, 'Previous revision file still in place after creating a new revision without a new file.'); $this->assertFileIsPermanent($node_file_r3, 'New revision file is permanent.'); // Revert to the first revision and check that the original file is active. $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r1 . '/revert', array(), t('Revert')); $node = node_load($nid, NULL, TRUE); $node_file_r4 = (object) $node->{$field_name}[LANGUAGE_NONE][0]; $node_vid_r4 = $node->vid; $this->assertEqual($node_file_r1, $node_file_r4, 'Original revision file still in place after reverting to the original revision.'); $this->assertFileIsPermanent($node_file_r4, 'Original revision file still permanent after reverting to the original revision.'); // Delete the second revision and check that the file is kept (since it is // still being used by the third revision). $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r2 . '/delete', array(), t('Delete')); $this->assertFileExists($node_file_r3, 'Second file is still available after deleting second revision, since it is being used by the third revision.'); $this->assertFileEntryExists($node_file_r3, 'Second file entry is still available after deleting second revision, since it is being used by the third revision.'); $this->assertFileIsPermanent($node_file_r3, 'Second file entry is still permanent after deleting second revision, since it is being used by the third revision.'); // Attach the second file to a user. $user = $this->drupalCreateUser(); $edit = (array) $user; $edit[$field_name][LANGUAGE_NONE][0] = (array) $node_file_r3; user_save($user, $edit); $this->drupalGet('user/' . $user->uid . '/edit'); // Delete the third revision and check that the file is not deleted yet. $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete', array(), t('Delete')); $this->assertFileExists($node_file_r3, 'Second file is still available after deleting third revision, since it is being used by the user.'); $this->assertFileEntryExists($node_file_r3, 'Second file entry is still available after deleting third revision, since it is being used by the user.'); $this->assertFileIsPermanent($node_file_r3, 'Second file entry is still permanent after deleting third revision, since it is being used by the user.'); // Delete the user and check that the file still exists. user_delete($user->uid); // TODO: This seems like a bug in File API. Clearing the stat cache should // not be necessary here. The file really exists, but stream wrappers // doesn't seem to think so unless we clear the PHP file stat() cache. clearstatcache(); // @todo Files referenced from entity revisions cannot currently be deleted after the entity is deleted. // @see https://drupal.org/node/1613290 // $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.'); // $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.'); // Delete the entire node and check that the original file is deleted. $this->drupalPost('node/' . $nid . '/delete', array(), t('Delete')); $this->assertFileNotExists($node_file_r1, 'Original file is deleted after deleting the entire node with two revisions remaining.'); $this->assertFileEntryNotExists($node_file_r1, 'Original file entry is deleted after deleting the entire node with two revisions remaining.'); } } /** * Tests various validations. */ class MediaFileFieldValidateTestCase extends MediaFileFieldTestCase { protected $field; public static function getInfo() { return array( 'name' => 'Media file field validation tests', 'description' => 'Tests validation functions such as required.', 'group' => 'Media', ); } /** * Tests the required property on file fields. */ function testRequired() { $type_name = 'article'; $field_name = strtolower($this->randomName()); $this->createFileField($field_name, $type_name, array(), array('required' => '1')); $field = field_info_field($field_name); $instance = field_info_instance('node', $field_name, $type_name); $test_file = $this->getTestFile('text'); $test_file->uid = $this->admin_user->uid; $test_file = file_save($test_file); // Try to post a new node without attaching a file. $langcode = LANGUAGE_NONE; $edit = array("title" => $this->randomName()); $this->drupalPost('node/add/' . $type_name, $edit, t('Save')); $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), 'Node save failed when required file field was empty.'); // Create a new node with the attached file. $nid = $this->attachNodeFile($test_file, $field_name, $type_name); $this->assertTrue($nid !== FALSE, format_string('attachNodeFile(@test_file, @field_name, @type_name) succeeded', array('@test_file' => $test_file->uri, '@field_name' => $field_name, '@type_name' => $type_name))); $node = node_load($nid, NULL, TRUE); $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; $this->assertFileExists($node_file, 'File exists after attaching to the required field.'); $this->assertFileEntryExists($node_file, 'File entry exists after attaching to the required field.'); // Try again with a multiple value field. field_delete_field($field_name); $this->createFileField($field_name, $type_name, array('cardinality' => FIELD_CARDINALITY_UNLIMITED), array('required' => '1')); // Try to post a new node without attaching a file in the multivalue field. $edit = array('title' => $this->randomName()); $this->drupalPost('node/add/' . $type_name, $edit, t('Save')); $this->assertRaw(t('!title field is required.', array('!title' => $instance['label'])), 'Node save failed when required multiple value file field was empty.'); // Create a new node with the attached file into the multivalue field. $nid = $this->attachNodeFile($test_file, $field_name, $type_name); $node = node_load($nid, NULL, TRUE); $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; $this->assertFileExists($node_file, 'File exists after attaching to the required multiple value field.'); $this->assertFileEntryExists($node_file, 'File entry exists after attaching to the required multiple value field.'); // Remove our file field. field_delete_field($field_name); } } /** * Tests that formatters are working properly. */ class MediaFileFieldDisplayTestCase extends MediaFileFieldTestCase { public static function getInfo() { return array( 'name' => 'Media file field display tests', 'description' => 'Test the display of file fields in node and views.', 'group' => 'Media', ); } /** * Tests normal formatter display on node display. */ function testNodeDisplay() { $field_name = strtolower($this->randomName()); $type_name = 'article'; $field_settings = array( 'display_field' => '1', 'display_default' => '1', ); $instance_settings = array( 'description_field' => '1', ); $widget_settings = array(); $this->createFileField($field_name, $type_name, $field_settings, $instance_settings, $widget_settings); $field = field_info_field($field_name); $instance = field_info_instance('node', $field_name, $type_name); // Create a new node *without* the file field set, and check that the field // is not shown for each node display. $node = $this->drupalCreateNode(array('type' => $type_name)); $file_formatters = array('file_default', 'file_table', 'file_url_plain', 'hidden'); foreach ($file_formatters as $formatter) { $edit = array( "fields[$field_name][type]" => $formatter, ); $this->drupalPost("admin/structure/types/manage/$type_name/display", $edit, t('Save')); $this->drupalGet('node/' . $node->nid); $this->assertNoText($field_name, format_string('Field label is hidden when no file attached for formatter %formatter', array('%formatter' => $formatter))); } $test_file = $this->getTestFile('text'); $test_file->uid = $this->admin_user->uid; $test_file = file_save($test_file); // Create a new node with the attached file. $nid = $this->attachNodeFile($test_file, $field_name, $type_name); $this->drupalGet('node/' . $nid . '/edit'); // Check that the media thumbnail is displaying with the file name. $node = node_load($nid, NULL, TRUE); $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; $thumbnail = media_get_thumbnail_preview($node_file); $default_output = drupal_render($thumbnail); $this->assertRaw($default_output, 'Default formatter displaying correctly on full node view.'); // Turn the "display" option off and check that the file is no longer displayed. $edit = array($field_name . '[' . LANGUAGE_NONE . '][0][display]' => FALSE); $this->drupalPost('node/' . $nid . '/edit', $edit, t('Save')); $this->assertNoRaw($default_output, 'Field is hidden when "display" option is unchecked.'); } }