When calling file_move and destination directory does not exist, it does not work.
Just copying the relevant lines for better understanding:
Calling file move with a destination directory that does not exist:
file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME)
This calls:
if ($uri = file_unmanaged_move($source->uri, $destination, $replace)) {
Wich calls:
$filepath = file_unmanaged_copy($source, $destination, $replace);
And finally inside file_unmanaged_copy:
// Prepare the destination directory.
if (file_prepare_directory($destination, FILE_CREATE_DIRECTORY)) {
// The destination is already a directory, so append the source basename.
$destination = file_stream_wrapper_uri_normalize($destination . '/' . drupal_basename($source));
}
else {
// Perhaps $destination is a dir/file?
$dirname = drupal_dirname($destination);
if (!file_prepare_directory($dirname)) {
// The destination is not valid.
watchdog('file', 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => $dirname));
drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $original_source)), 'error');
return FALSE;
}
}
So, if your destination URI when calling file_move is in a directory that does not exist, the following condition will fail:
if (!file_prepare_directory($dirname))
Because in order for the directory to be created and file_prepare_directory to return TRUE, it should be called this way:
if (!file_prepare_directory($dirname, FILE_CREATE_DIRECTORY))
OR..... if it was designed to behave this way, documentation should state that before calling file_move you should prepare the destination directory and make sure it does exist.
I'll be working on a proper patch in the next days, just wanted to write this down now that I have it fresh.
| Comment | File | Size | Author |
|---|---|---|---|
| #37 | interdiff.txt | 8.23 KB | quietone |
| #33 | drupal-test_copy_and_move_to_directory-2211657-33.patch | 5.32 KB | Anonymous (not verified) |
| #24 | file_unmanaged_copy_not-2211657-24.patch | 5.16 KB | gnuget |
| #24 | 2211657-interdiff-21-24.txt | 3 KB | gnuget |
| #21 | interdiff.txt | 2.42 KB | quietone |
Comments
Comment #1
david_garcia commentedComment #2
david_garcia commentedI need some opinion on this....
The file_unmanaged_move method prepares destination director this way:
But some things do not make sense to me:
1. ¿Why is it assuming that $destination is a directory and not a full file path?
2. ¿Why is it not telling file_prepare_directory to create de destination directory if unexisting by means of FILE_CREATE_DIRECTORY?
So I ended up rewriting the logic that takes care of preparing the directory:
I attach patch in NON git, just for review...
Comment #3
david_garcia commentedComment #5
david_garcia commentedComment #6
david_garcia commentedNo way.... proposed solution is a NO GO, there is no reliable way I can think of telling if $destination is a file or a directory.
So, before consuming file_unmanaged_move or any of it's parents (file_move, file_copy, etc..) one must make sure that the destination directory is available and coherent with the $destination path it is sending.
Let's take it this way:
I want to move:
file://aa.docx
To:
file://directoryA/bb.docx
But, how is file_unmanaged_move going to know if I want this other destination:
file://directoryA/bb.docx/aa.docx
The only way is to not let file_unmanaged_move create directories at all (original implementation) and make sure your destination directory structure is properly populated before the call.
Yet, this will lead to ambiguous behaviours.
On the previous example, ¿what if a directory I am unaware of already exists that is coincident with my destination filename? Then it will be asumed that $destination is a directory and it will use that, combined with original filename, to create de real destination.
I could not find this documented anywhere, but it does not look trivial to me.
Comment #7
klaasvw commentedI had a similar issue:
When uploading a file to this field and selecting the private stream wrapper the file will never be copied to sites/sitename/private/sub because file_unmanaged_copy doesn't support creating directories.
The issue above is also caused by the same behavior because file_unmanaged_move relies on file_unmanaged_copy.
The attached patch tells file_prepare_directory to create the directory if the destination doesn't exist.
Comment #8
Julian Maurice commentedHi,
I tested the last patch (in comment #7) and it works for me (I was having issues when changing file destination from private to public).
Does it have a chance to be in the next release ?
Comment #9
oadaeh commentedI have just applied the patch in #7 to a project I am working on.
When attaching a file to a privatemsg, the file would get saved to the private directory but not the public one, as desired. The public directory did not exist, and would not get created.
The patch fixed that for me, so that the functionality does what it's expected to, and there are no errors in watchdog.
Is there some reason that code change should not be included in core?
Comment #10
micnap commentedWe were having problems uploading because the files subdirectories weren't being created (media/users/[uid] in our case) because the second parameter to file_prepare_directory function was never getting passed in from file_unmanaged_copy, preventing new directories from being created. Applied the patch #7 and it corrects the issue.
Comment #11
David_Rothstein commentedThis would need to go in Drupal 8 first.
Probably could use a test also?
Comment #12
oadaeh commentedI've attached a patch against the latest dev of Drupal 8.
Now I'll take a look at if a test is needed and what it would take to create it.
Comment #14
catchJust ran into this.
Just creating a file in public:// thrn moving it to public://foo and then a couple of asserts wpuld be enough test coverage.
Tagging RC target since this is a straightforward, backportable fix.
Comment #16
oadaeh commentedI'm going to do my best to follow @catch's test recommendation as soon as I can, but I haven't done anything with this since my last comment, and I'm not doing anything with it at the moment, so I'm un-assigning myself for now.
Comment #17
gnugetComment #18
quietone commentedThis caught my eye because I've been working on a migration file issue. The patch didn't apply, so did the reroll. And then I just keep going and added some tests.
Comment #19
gnugetI just added an extra test using php7 and myqsl 5.5 because in my local
file_unmanaged_moveis throwing errors.Thanks for your patch quiteone.
Comment #20
gnugetToday I was able to give it a proper review to this patch.
In general, looks great, just a few nits:
drupal_dirname and drupal_basename are deprecated functions, can we use
FileSystem::dirname() and FileSystem::basename() instead?
A missing space before the "=".
Thanks for your patch quiteone.
Comment #21
quietone commented@gnuget, thx. Both fixed. However, note that I also changed two of the assertions to not use deprecated functions.
Comment #22
gnugetLooks great. this is RTBC to me.
Thanks!
Comment #23
alexpottWe have tests nice - but I think we need a change record to inform people about the behaviour change.
Seems unnecessary given file_exists(FALSE) is false...
PHPunit has $this->assertFileExists() and assertFileNotExists() we should use them.
Comment #24
gnugetPoint 1 and 2 fixed in this new patch.
I will write the change record now.
Comment #25
gnugetHere the changed record:
https://www.drupal.org/node/2786505
(It's my first time doing change records, sorry for any mistake :D)
Comment #26
quietone commented@gnuget, thx. I;m new to change records as well. But When I read the description in the change record the term 'missing directories are needed' is unusual, at least to me.. How about just referring to them as the parent directories (like 'man mkdir'). file_unmanaged_copy and file_unmanaged_move now make parent directories as needed. And shouldn't the 'before' sections mention that an error message was generated?
Comment #27
gnugetI think this is ready, the change record was already edited by AlexPott, so this just needs another review before to be RTBC. :-)
Comment #28
Anonymous (not verified) commentedThe file_unmanaged_prepare() function doc block states that both path arguments should be a file (see `filepath` and `destination file`), from this I would judge that you should never pass in a directory as a 2nd parameter as david_garcia suggested in #6.
But then again, in
\Drupal\KernelTests\Core\File\UnmanagedCopyTest::testNormal()we have this TODO:I believe that we should remove that TODO, add some tests and maybe make the comments more clear.
Allowing the 2nd argument to be a directory path would probably require an extra argument, or a convention like `if the path ends with a DIRECTORY_SEPARATOR, then...`, but this will only cause issues.
Attached tests for copying to an existing and a missing directory, test for move are still needed.
P.S. I would like to suggest that we shouldn't change
file_unmanaged_prepare()Comment #30
Anonymous (not verified) commentedFixed spelling and UnmanagedCopyTest test and, added UnmanagedMoveTest tests.
Comment #31
Anonymous (not verified) commentedComment #33
Anonymous (not verified) commentedFixing UnmanagedMoveTest.
Sorry for the noise/load.
Comment #35
gnugetHi @SchWalter.
Thanks for your work on this issue.
Can you please provide an interdiff? so we can see more easily what you did?
Thanks!
Comment #36
20th commentedComment #37
quietone commentedHere is an interdiff between the patches in #24 AND #33.
Comment #40
joseph.olstadComment #43
joseph.olstadComment #44
joseph.olstadrerolled , trigger tests
Comment #45
joseph.olstadThis issue is a duplicate issue of these two issues:
D8:
#1068266: drupal_mkdir does not set permissions to directories it created recursively
D7:
#2789723: [D7 backport] drupal_mkdir does not set permissions to directories it created recursively
The above D7 issue is marked as RTBC.
Previously I was using the patch for D7 from this issue and it was working but now I am aware of the RTBC issue, so I'm going to swap out the patch from this issue with the RTBC patch from 2789723 that already has tests and is already fully backported and ready for commit