Commit f0902810 authored by Dries's avatar Dries

- Patch #413732 by brianV: database code clean-up.

parent 10931908
......@@ -516,7 +516,7 @@ public function getLogger() {
* A table prefix-parsed string for the sequence name.
*/
public function makeSequenceName($table, $field) {
return $this->prefixTables('{'. $table .'}_'. $field .'_seq');
return $this->prefixTables('{' . $table . '}_' . $field . '_seq');
}
/**
......@@ -594,7 +594,7 @@ public function query($query, array $args = array(), $options = array()) {
else {
$query_string = $query;
}
throw new PDOException($query_string . " - \n" . print_r($args,1) . $e->getMessage());
throw new PDOException($query_string . " - \n" . print_r($args, 1) . $e->getMessage());
}
return NULL;
}
......@@ -1006,7 +1006,7 @@ public function supportsTransactionalDDL() {
* overridable lookup function. Database connections should define only
* those operators they wish to be handled differently than the default.
*
* @see DatabaseCondition::compile().
* @see DatabaseCondition::compile()
* @param $operator
* The condition operator, such as "IN", "BETWEEN", etc. Case-sensitive.
* @return
......@@ -2191,8 +2191,9 @@ function db_drop_table(&$ret, $table) {
* table along with adding the field. The format is the same as a
* table specification but without the 'fields' element. If you are
* adding a type 'serial' field, you MUST specify at least one key
* or index including it in this array. @see db_change_field for more
* or index including it in this array. See db_change_field() for more
* explanation why.
* @see db_change_field()
*/
function db_add_field(&$ret, $table, $field, $spec, $keys_new = array()) {
return Database::getConnection()->schema()->addField($ret, $table, $field, $spec, $keys_new);
......@@ -2616,7 +2617,7 @@ function db_rewrite_sql($query, $primary_table = 'n', $primary_field = 'nid', $
if ($where) {
$n = strlen($matches[1]);
$second_part = substr($query, $n);
$first_part = substr($matches[1], 0, $n - 5) ." $join WHERE $where AND ( ";
$first_part = substr($matches[1], 0, $n - 5) . " $join WHERE $where AND ( ";
foreach (array('GROUP', 'ORDER', 'LIMIT') as $needle) {
$pos = strrpos($second_part, $needle);
if ($pos !== FALSE) {
......
<?php
// $Id$
/**
* @file
* Installation code for MySQL embedded database engine.
*/
// MySQL specific install functions
class DatabaseInstaller_mysql extends DatabaseInstaller {
......
......@@ -6,6 +6,12 @@
* @{
*/
/**
* @file
* Query code for MySQL embedded database engine.
*/
class InsertQuery_mysql extends InsertQuery {
public function execute() {
......@@ -64,16 +70,16 @@ public function __toString() {
$new_placeholder = $max_placeholder + count($insert_values);
for ($i = $max_placeholder; $i < $new_placeholder; ++$i) {
$placeholders[] = ':db_insert_placeholder_'. $i;
$placeholders[] = ':db_insert_placeholder_' . $i;
}
$max_placeholder = $new_placeholder;
$values[] = '('. implode(', ', $placeholders) .')';
$values[] = '(' . implode(', ', $placeholders) . ')';
}
}
else {
// If there are no values, then this is a default-only query. We still need to handle that.
$placeholders = array_fill(0, count($this->defaultFields), 'default');
$values[] = '(' . implode(', ', $placeholders) .')';
$values[] = '(' . implode(', ', $placeholders) . ')';
}
$query .= implode(', ', $values);
......
......@@ -224,7 +224,7 @@ protected function createKeysSql($spec) {
}
if (!empty($spec['unique keys'])) {
foreach ($spec['unique keys'] as $key => $fields) {
$keys[] = 'UNIQUE KEY ' . $key .' ('. $this->createKeysSqlHelper($fields) . ')';
$keys[] = 'UNIQUE KEY ' . $key . ' (' . $this->createKeysSqlHelper($fields) . ')';
}
}
if (!empty($spec['indexes'])) {
......@@ -352,7 +352,7 @@ public function prepareComment($comment, $length = NULL) {
// Work around a bug in some versions of PDO, see http://bugs.php.net/bug.php?id=41125
$comment = str_replace("'", '’', $comment);
// Truncate comment to maximum comment length.
if (isset($length)) {
// Add table prefixes before truncating.
......
......@@ -72,7 +72,7 @@ public function query($query, array $args = array(), $options = array()) {
else {
$query_string = $query;
}
throw new PDOException($query_string . " - \n" . print_r($args,1) . $e->getMessage());
throw new PDOException($query_string . " - \n" . print_r($args, 1) . $e->getMessage());
}
return NULL;
}
......
<?php
// $Id$
/**
* @file
* Install functions for PostgreSQL embedded database engine.
*/
// PostgreSQL specific install functions
class DatabaseInstaller_pgsql extends DatabaseInstaller {
......
......@@ -6,6 +6,12 @@
* @{
*/
/**
* @file
* Query code for PostgreSQL embedded database engine.
*/
class InsertQuery_pgsql extends InsertQuery {
public function __construct($connection, $table, array $options = array()) {
......@@ -51,7 +57,7 @@ public function execute() {
++$blob_count;
}
else {
$stmt->bindParam(':db_insert_placeholder_'. $max_placeholder++, $insert_values[$idx]);
$stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $insert_values[$idx]);
}
}
}
......@@ -101,7 +107,7 @@ public function __toString() {
else {
// If there are no values, then this is a default-only query. We still need to handle that.
$placeholders = array_fill(0, count($this->defaultFields), 'default');
$values[] = '(' . implode(', ', $placeholders) .')';
$values[] = '(' . implode(', ', $placeholders) . ')';
}
$query .= implode(', ', $values);
......
......@@ -19,7 +19,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
* This is collected by DatabaseConnection_pgsql->queryTableInformation(),
* by introspecting the database.
*
* @see DatabaseConnection_pgsql->queryTableInformation().
* @see DatabaseConnection_pgsql->queryTableInformation()
* @var array
*/
protected $tableInformation = array();
......@@ -46,7 +46,7 @@ public function queryTableInformation($table) {
if (!isset($this->tableInformation[$key])) {
// Split the key into schema and table for querying.
list($schema,$table_name) = explode('.', $key);
list($schema, $table_name) = explode('.', $key);
$table_information = (object) array(
'blob_fields' => array(),
'sequences' => array(),
......@@ -56,7 +56,7 @@ public function queryTableInformation($table) {
if ($column->data_type == 'bytea') {
$table_information->blob_fields[$column->column_name] = TRUE;
}
else if (preg_match("/nextval\('([^']+)'/", $column->column_default, $matches)) {
elseif (preg_match("/nextval\('([^']+)'/", $column->column_default, $matches)) {
// We must know of any sequences in the table structure to help us
// return the last insert id. If there is more than 1 sequences the
// first one (index 0 of the sequences array) will be used.
......@@ -311,8 +311,10 @@ public function dropTable(&$ret, $table) {
* table along with adding the field. The format is the same as a
* table specification but without the 'fields' element. If you are
* adding a type 'serial' field, you MUST specify at least one key
* or index including it in this array. @see db_change_field for more
* or index including it in this array. See db_change_field() for more
* explanation why.
*
* @see db_change_field()
*/
public function addField(&$ret, $table, $field, $spec, $new_keys = array()) {
$fixnull = FALSE;
......
<?php
// $Id $
// $Id$
/**
* @file
......@@ -89,7 +89,7 @@ class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface
/**
* Holds the current fetch style (which will be used by the next fetch).
* @see PDOStatement::fetch.
* @see PDOStatement::fetch()
*
* @var int
*/
......@@ -177,7 +177,8 @@ public function execute($args = array(), $options = array()) {
$this->rowCount = $statement->rowCount();
$this->data = $statement->fetchAll(PDO::FETCH_ASSOC);
// Destroy the statement as soon as possible.
// @see DatabaseConnection_sqlite::PDOPrepare for explanation.
// See DatabaseConnection_sqlite::PDOPrepare() for explanation.
// @see DatabaseConnection_sqlite::PDOPrepare()
unset($statement);
$this->resultRowCount = count($this->data);
......@@ -236,7 +237,7 @@ public function getQueryString() {
}
/**
* @see PDOStatement::setFetchMode.
* @see PDOStatement::setFetchMode()
*/
public function setFetchMode($fetchStyle, $a2 = NULL, $a3 = NULL) {
$this->defaultFetchStyle = $fetchStyle;
......
<?php
// $Id$
/**
* @ingroup database
* @{
*/
/**
* @file
* Non-specific Database query code. Used by all engines.
*/
/**
* Interface for a conditional clause in a query.
*/
......@@ -462,7 +468,7 @@ public function __toString() {
$placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
$placeholders = array_pad($placeholders, count($this->insertFields), '?');
return 'INSERT INTO {'. $this->table .'} ('. implode(', ', $insert_fields) .') VALUES ('. implode(', ', $placeholders) .')';
return 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES (' . implode(', ', $placeholders) . ')';
}
}
......
......@@ -25,7 +25,7 @@ public function __construct(array $connection_options = array()) {
// This driver defaults to transaction support, except if explicitly passed FALSE.
$this->transactionSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE;
parent::__construct('sqlite:'. $connection_options['database'], '', '', array(
parent::__construct('sqlite:' . $connection_options['database'], '', '', array(
// Force column names to lower case.
PDO::ATTR_CASE => PDO::CASE_LOWER,
));
......@@ -162,8 +162,10 @@ public function distinctField($table, $field, $query) {
/**
* Specific SQLite implementation of DatabaseConnection.
*
* @see DatabaseConnection_sqlite::PDOPrepare for reasons why we must prefetch
* See DatabaseConnection_sqlite::PDOPrepare() for reasons why we must prefetch
* the data instead of using PDOStatement.
*
* @see DatabaseConnection_sqlite::PDOPrepare()
*/
class DatabaseStatement_sqlite extends DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface {
......
<?php
// $Id $
// $Id$
/**
* @file
* Query code for SQLite embedded database engine.
*/
/**
* @ingroup database
......@@ -27,14 +32,14 @@ public function execute() {
return parent::execute();
}
else {
return $this->connection->query('INSERT INTO {'. $this->table .'} DEFAULT VALUES', array(), $this->queryOptions);
return $this->connection->query('INSERT INTO {' . $this->table . '} DEFAULT VALUES', array(), $this->queryOptions);
}
}
public function __toString() {
// Produce as many generic placeholders as necessary.
$placeholders = array_fill(0, count($this->insertFields), '?');
return 'INSERT INTO {'. $this->table .'} ('. implode(', ', $this->insertFields) .') VALUES ('. implode(', ', $placeholders) .')';
return 'INSERT INTO {' . $this->table . '} (' . implode(', ', $this->insertFields) . ') VALUES (' . implode(', ', $placeholders) . ')';
}
}
......@@ -87,7 +92,7 @@ public function execute() {
// The IS NULL operator is badly managed by DatabaseCondition.
$condition->where($field . ' IS NULL');
}
else if (is_null($data)) {
elseif (is_null($data)) {
// The field will be set to NULL.
// The IS NULL operator is badly managed by DatabaseCondition.
$condition->where($field . ' IS NOT NULL');
......
......@@ -66,7 +66,7 @@ protected function createColumsSql($tablename, $schema) {
// Add the SQL statement for each field.
foreach ($schema['fields'] as $name => $field) {
if ($field['type'] == 'serial') {
if (isset($schema['primary key']) && ($key = array_search($name, $schema['primary key'])) !== false) {
if (isset($schema['primary key']) && ($key = array_search($name, $schema['primary key'])) !== FALSE) {
unset($schema['primary key'][$key]);
}
}
......@@ -294,7 +294,8 @@ protected function alterTable(&$ret, $table, $new_schema) {
* @param $table
* Name of the table.
* @return
* An array representing the schema, @see drupal_get_schema.
* An array representing the schema, from drupal_get_schema().
* @see drupal_get_schema()
*/
protected function introspectSchema($table) {
$mapped_fields = array_flip($this->getFieldTypeMap());
......
......@@ -157,7 +157,7 @@ function file_check_directory(&$directory, $mode = 0, $form_item = NULL) {
// Check if directory exists.
if (!is_dir($directory)) {
if (($mode & FILE_CREATE_DIRECTORY) && @mkdir($directory)) {
@chmod($directory, 0775); // Necessary for non-webserver users.
drupal_chmod($directory);
}
else {
if ($form_item) {
......@@ -172,7 +172,7 @@ function file_check_directory(&$directory, $mode = 0, $form_item = NULL) {
if (!is_writable($directory)) {
// If not able to modify permissions, or if able to, but chmod
// fails, return false.
if (!$mode || (($mode & FILE_MODIFY_PERMISSIONS) && !@chmod($directory, 0775))) {
if (!$mode || (($mode & FILE_MODIFY_PERMISSIONS) && !drupal_chmod($directory))) {
if ($form_item) {
form_set_error($form_item, t('The directory %directory is not writable', array('%directory' => $directory)));
watchdog('file system', 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => $directory), WATCHDOG_ERROR);
......@@ -183,9 +183,8 @@ function file_check_directory(&$directory, $mode = 0, $form_item = NULL) {
if ((file_directory_path() == $directory || file_directory_temp() == $directory) && !is_file("$directory/.htaccess")) {
$htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks";
if (($fp = fopen("$directory/.htaccess", 'w')) && fputs($fp, $htaccess_lines)) {
fclose($fp);
chmod($directory . '/.htaccess', 0664);
if (file_put_contents("$directory/.htaccess", $htaccess_lines)) {
drupal_chmod("$directory/.htaccess");
}
else {
$variables = array('%directory' => $directory, '!htaccess' => '<br />' . nl2br(check_plain($htaccess_lines)));
......@@ -483,11 +482,8 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST
return FALSE;
}
// Give everyone read access so that FTP'd users or
// non-webserver users can see/read these files,
// and give group write permissions so group members
// can alter files uploaded by the webserver.
@chmod($destination, 0664);
// Set the permissions on the new file.
drupal_chmod($destination);
return $destination;
}
......@@ -996,6 +992,9 @@ function file_save_upload($source, $validators = array(), $destination = FALSE,
return FALSE;
}
// Set the permissions on the new file.
drupal_chmod($file->filepath);
// If we are replacing an existing file re-use its database record.
if ($replace == FILE_EXISTS_REPLACE) {
$existing_files = file_load_multiple(array(), array('filepath' => $file->filepath));
......@@ -1889,6 +1888,43 @@ function file_get_mimetype($filename, $mapping = NULL) {
return 'application/octet-stream';
}
/**
* @} End of "defgroup file".
* Set the permissions on a file or directory.
*
* This function will use the 'file_chmod_directory' and 'file_chmod_file'
* variables for the default modes for directories and uploaded/generated files.
* By default these will give everyone read access so that users accessing the
* files with a user account without the webserver group (e.g. via FTP) can read
* these files, and give group write permissions so webserver group members
* (e.g. a vhost account) can alter files uploaded and owned by the webserver.
*
* @param $path
* String containing the path to a file or directory.
* @param $mode
* Integer value for the permissions. Consult PHP chmod() documentation for
* more information.
* @return
* TRUE for success, FALSE in the event of an error.
*/
function drupal_chmod($path, $mode = NULL) {
if (!isset($mode)) {
if (is_dir($path)) {
$mode = variable_get('file_chmod_directory', 0775);
}
else {
$mode = variable_get('file_chmod_file', 0664);
}
}
if (@chmod($path, $mode)) {
return TRUE;
}
watchdog('file', 'The file permissions could not be set on %path.', array('%path' => $path), WATCHDOG_ERROR);
return FALSE;
}
/**
* @} End of "defgroup file".
*/
\ No newline at end of file
......@@ -376,10 +376,9 @@ function image_save(stdClass $image, $destination = NULL) {
clearstatcache();
$image->info = image_get_info($destination);
if (@chmod($destination, 0664)) {
if (drupal_chmod($destination)) {
return $return;
}
watchdog('image', 'Could not set permissions on destination file: %file', array('%file' => $destination));
}
return FALSE;
}
......
......@@ -455,7 +455,7 @@ function _color_save_stylesheet($file, $style, &$paths) {
$paths['files'][] = $filepath;
// Set standard file permissions for webserver-generated files.
@chmod($file, 0664);
drupal_chmod($file);
}
/**
......@@ -513,7 +513,7 @@ function _color_render_images($theme, &$info, &$paths, $palette) {
$paths['files'][] = $image;
// Set standard file permissions for webserver-generated files
@chmod(realpath($image), 0664);
drupal_chmod($image);
// Build before/after map of image paths.
$paths['map'][$file] = $base;
......
......@@ -861,7 +861,9 @@ protected function setUp() {
$this->originalFileDirectory = file_directory_path();
variable_set('file_directory_path', file_directory_path() . '/' . $db_prefix);
$directory = file_directory_path();
file_check_directory($directory, FILE_CREATE_DIRECTORY); // Create the files directory.
// Create the files directory.
file_check_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
set_time_limit($this->timeLimit);
}
......
......@@ -101,15 +101,35 @@ class FileTestCase extends DrupalWebTestCase {
* Optional message.
*/
function assertFilePermissions($filepath, $expected_mode, $message = NULL) {
// Clear out PHP's file stat cache to be sure we see the current value.
clearstatcache();
// Mask out all but the last three octets.
$actual_mode = fileperms($filepath) & 511;
if (is_null($message)) {
if ($actual_mode == $expected_mode) {
$message = t('File permissions set correctly.');
}
else {
$message = t('Expected file permission to be %expected, actually were %actual.', array('%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode)));
}
if (!isset($message)) {
$message = t('Expected file permission to be %expected, actually were %actual.', array('%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode)));
}
$this->assertEqual($actual_mode, $expected_mode, $message);
}
/**
* Helper function to test the permissions of a directory.
*
* @param $directory
* String directory path.
* @param $expected_mode
* Octal integer like 0664 or 0777.
* @param $message
* Optional message.
*/
function assertDirectoryPermissions($directory, $expected_mode, $message = NULL) {
// Clear out PHP's file stat cache to be sure we see the current value.
clearstatcache();
// Mask out all but the last three octets.
$actual_mode = fileperms($directory) & 511;
if (!isset($message)) {
$message = t('Expected directory permission to be %expected, actually were %actual.', array('%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode)));
}
$this->assertEqual($actual_mode, $expected_mode, $message);
}
......@@ -493,7 +513,7 @@ class FileUnmanagedSaveDataTest extends FileTestCase {
$this->assertEqual(file_directory_path(), dirname($filepath), t("File was placed in Drupal's files directory."));
$this->assertEqual('asdf.txt', basename($filepath), t('File was named correctly.'));
$this->assertEqual($contents, file_get_contents(realpath($filepath)), t('Contents of the file are correct.'));
$this->assertFilePermissions($filepath, 0664);
$this->assertFilePermissions($filepath, variable_get('file_chmod_file', 0664));
}
}
......@@ -681,8 +701,8 @@ class FileDirectoryTest extends FileTestCase {
// Test directory permission modification.
$this->assertTrue(file_check_directory($directory, FILE_MODIFY_PERMISSIONS), t('No error reported when making directory writeable.'), 'File');
// Verify directory actually is writeable.
$this->assertTrue(is_writeable($directory), t('Directory is writeable.'), 'File');
// Test directory permission modification actually set correct permissions.
$this->assertDirectoryPermissions($directory, variable_get('file_chmod_directory', 0775));
// Remove .htaccess file to then test that it gets re-created.
@unlink(file_directory_path() .'/.htaccess');
......@@ -1077,7 +1097,7 @@ class FileUnmanagedMoveTest extends FileTestCase {
$this->assertEqual($new_filepath, $desired_filepath, t('Returned expected filepath.'));
$this->assertTrue(file_exists($new_filepath), t('File exists at the new location.'));
$this->assertFalse(file_exists($file->filepath), t('No file remains at the old location.'));
$this->assertFilePermissions($new_filepath, 0664);
$this->assertFilePermissions($new_filepath, variable_get('file_chmod_file', 0664));
// Moving with rename.
$desired_filepath = file_directory_path() . '/' . $this->randomName();
......@@ -1088,7 +1108,7 @@ class FileUnmanagedMoveTest extends FileTestCase {
$this->assertNotEqual($newer_filepath, $desired_filepath, t('Returned expected filepath.'));
$this->assertTrue(file_exists($newer_filepath), t('File exists at the new location.'));
$this->assertFalse(file_exists($new_filepath), t('No file remains at the old location.'));
$this->assertFilePermissions($newer_filepath, 0664);
$this->assertFilePermissions($newer_filepath, variable_get('file_chmod_file', 0664));
// TODO: test moving to a directory (rather than full directory/file path)
}
......@@ -1149,17 +1169,17 @@ class FileUnmanagedCopyTest extends FileTestCase {
$this->assertEqual($new_filepath, $desired_filepath, t('Returned expected filepath.'));
$this->assertTrue(file_exists($file->filepath), t('Original file remains.'));
$this->assertTrue(file_exists($new_filepath), t('New file exists.'));
$this->assertFilePermissions($new_filepath, 0664);
$this->assertFilePermissions($new_filepath, variable_get('file_chmod_file', 0664));
// Copying with rename.
$desired_filepath = file_directory_path() . '/' . $this->randomName();
$this->assertTrue(file_put_contents($desired_filepath, ' '), t('Created a file so a rename will have to happen.'));
$newer_filepath = file_unmanaged_copy($new_filepath, $desired_filepath, FILE_EXISTS_RENAME);
$newer_filepath = file_unmanaged_copy($file->filepath, $desired_filepath, FILE_EXISTS_RENAME);
$this->assertTrue($newer_filepath, t('Copy was successful.'));
$this->assertNotEqual($newer_filepath, $desired_filepath, t('Returned expected filepath.'));
$this->assertTrue(file_exists($file->filepath), t('Original file remains.'));
$this->assertTrue(file_exists($new_filepath), t('New file exists.'));
$this->assertFilePermissions($new_filepath, 0664);
$this->assertTrue(file_exists($newer_filepath), t('New file exists.'));
$this->assertFilePermissions($newer_filepath, variable_get('file_chmod_file', 0664));
// TODO: test copying to a directory (rather than full directory/file path)
}
......@@ -1188,6 +1208,7 @@ class FileUnmanagedCopyTest extends FileTestCase {
$this->assertNotEqual($new_filepath, $file->filepath, t('Copied file has a new name.'));
$this->assertTrue(file_exists($file->filepath), t('Original file exists after copying onto itself.'));
$this->assertTrue(file_exists($new_filepath), t('Copied file exists after copying onto itself.'));
$this->assertFilePermissions($new_filepath, variable_get('file_chmod_file', 0664));
// Copy the file onto itself without renaming fails.
$new_filepath = file_unmanaged_copy($file->filepath, $file->filepath, FILE_EXISTS_ERROR);
......@@ -1205,11 +1226,10 @@ class FileUnmanagedCopyTest extends FileTestCase {
$this->assertNotEqual($new_filepath, $file->filepath, t('Copied file has a new name.'));
$this->assertTrue(file_exists($file->filepath), t('Original file exists after copying onto itself.'));
$this->assertTrue(file_exists($new_filepath), t('Copied file exists after copying onto itself.'));
$this->assertFilePermissions($new_filepath, variable_get('file_chmod_file', 0664));
}
}
/**
* Deletion related tests.
*/
......
......@@ -199,6 +199,9 @@ class UploadTestCase extends DrupalWebTestCase {
$this->drupalGet($base_url . '/' . file_directory_path() . '/' . $filename, array('external' => TRUE));
$this->assertResponse(array(200), 'Uploaded ' . $filename . ' is accessible.');
$this->assertEqual(file_get_contents($file), $this->drupalGetContent(), 'Uploaded contents of ' . $filename . ' verified.');
// Verify file actually is readable and writeable by PHP.
$this->assertTrue(is_readable($file), t('Uploaded file is readable.'));
$this->assertTrue(is_writeable($file), t('Uploaded file is writeable.'));
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment