1

I'm trying to upload multiple files to a SFTP site from a local directory.

I can get it to work for a single file but I would like to be able to upload multiple files which have variables names too.

$localFile_xml = "C:\xml\Race_" . $value; 
chdir($localFile_xml); 


//This successfully lists the files
foreach (glob("*.xml") as $filename) {
    echo "$filename size " . filesize($filename) . "\n";
}

$remote_XMLfiles = "/FTP/XML/Race_" . $value;

$xmlstream = fopen("ssh2.sftp://$sftp" . $remote_XMLfiles, 'w');

foreach (glob("*.xml") as $filename) {
    $xmlfile = file_get_contents($localFile_xml);
    fwrite($xmlstream, $xmlfile);
    fclose($xmlstream);

}

I believe its there but I cannot get the last bit correct.

Thank you so much in advance

Will B.
  • 17,883
  • 4
  • 67
  • 69
MdM
  • 81
  • 2
  • 9
  • 1
    Looks like you're calling `fclose`, but not `fopen` again. You would need to re-open the closed resource. – Will B. Feb 02 '19 at 04:32
  • Thanks for your help unfortunately that didn't help. – MdM Feb 02 '19 at 05:09
  • Confused as to what exactly you're trying to accomplish. Your code appears to be writing the same `$xmlfile` data repeatedly to the `$xmlstream`. but doesn't use `$filename` at all in the `foreach` iteration to the `$xmlstream`, or change files of the `$xmlstream`. `file_get_contents($localFile_xml)` should be returning false, as it appears to be a directory. Does the remote file already exist or should it be created if not? Please update the question with your functional single file code. – Will B. Feb 05 '19 at 02:19
  • Hi, You are probably right that my code is all over the place, I am new to coding and above my head in what I am trying to achieve. So, what I am trying to do is upload all XML files within a folder on a local server to an already created folder on a secure FTP server. I have managed to do a single file at the same time but this approach is ineffective. Thank you – MdM Feb 05 '19 at 03:23
  • What do you mean by "it didn't help", what was the effect? Because fyrye is right there. Before you move on, you should enable error reporting, because it's simply guessing otherwise. Guessing is for kids. – Daniel W. Feb 05 '19 at 15:21

1 Answers1

2

Assuming the remote SSH connection is valid, and that the method you used in your question works for single files, I believe your order of operations needs to be corrected.

As mentioned in my comments, your code appears to be attempting to use file_get_contents on a local directory, which is not permitted. It also appears you attempted the same on the $xmlstream, which must be performed per file, rather than directory. Assuming 'C:\\xml\\Race_' . $value; is a directory like C:\\xml\\Race_1 and not a file.

Some minor issues for validation of resources and Windows specific issues that need to be addressed:

  • Windows Directory Separators should be written as \\ (even when using single quotes), since \ is an escape sequence it causes \x \t \n \r \' \" \\ to be treated as special characters.

  • When using fopen($path, $mode) it is recommended to specify the b flag as the last character of the mode, to ensure the file is binary-safe (verbatim) and to avoid ambiguity between operating systems. Alternatively on Windows specify the t mode to transparently translate \n to \r\n (only desirable for plain-text files).

    • $mode = 'rb' (binary-safe read)
    • $mode = 'rt' (text-mode translation read)
  • When working with networking streams, it is recommended to test that the stream has successfully written all of its content. I provided the fwrite_stream function below from the PHP manual.

Example

try {
    //--- example purposes only ---
    //use your own ssh2_connnect, ssh2_auth_password, ssh2_sftp
    if (!$ssh2 = ssh2_connect('192.168.56.101', 22)) {
        throw new \RuntimeException('Unable to connect to remote host');
    }
    if (!ssh2_auth_password($ssh2, 'root', '')) {
        throw new \RuntimeException('Unable to Authenticate');
    }
    if (!$sftp = ssh2_sftp($ssh2)) {
        throw new \RuntimeException('Unable to initialize SFTP');
    }

    $value = '1'; 
   //--- end example purposes only ---

    $localFile_xml = 'C:\\xml\\Race_' . $value;
    if (!$localFile_xml || !is_dir($localFile_xml)) {
        throw new \RuntimeException('Unable to retrieve local directory');
    }

    //retrieve list of XML files
    $iterator = new \GlobIterator($localFile_xml . '/*.xml',
        \FilesystemIterator::KEY_AS_PATHNAME |
        \FilesystemIterator::CURRENT_AS_FILEINFO |
        \FilesystemIterator::SKIP_DOTS
    );
    if (!$iterator->count()) {
        throw new \RuntimeException('Unable to retrieve local files');
    }

    $success = [];
    $remote_XMLfiles = '/FTP/XML/Race_' . $value;
    $remote_XMLpath = "ssh2.sftp://$sftp" . $remote_XMLfiles;

    //ensure the remote directory exists
    if (!@mkdir($remote_XMLpath, 0777, true) && !is_dir($remote_XMLpath)) {
        throw new \RuntimeException(sprintf('Unable to create remote directory "%s"', $remote_XMLpath));
    }

    /**
     * @var string $filepath
     * @var \SplFileInfo $fileinfo
     */
    foreach ($iterator as $filepath => $fileinfo) {
        $filesize = $fileinfo->getSize();
        printf("%s size %d\n", $filepath, $filesize);
        try {
            //open local file resource for binary-safe reading
            $xmlObj = $fileinfo->openFile('rb');
            //retrieve entire file contents
            if (!$xmlData = $xmlObj->fread($filesize)) {
                //do not permit empty files
                printf("No data found for \"%s\"\n", $filepath);
                continue;
            }
        } finally {
            //shortcut to close the opened local file resource on success or fail
            $xmlObj = null;
            unset($xmlObj);
        }

        try {
            $remote_filepath = $remote_XMLpath . '/' . $fileinfo->getBasename();
            //open a remote file resource for binary-safe writing
            //using current filename, overwriting the file if it already exists
            if (!$xmlstream = fopen($remote_filepath, 'wb')) {
                throw new \RuntimeException(sprintf('Unable to create remote file "%s"', $remote_filepath));
            }
            //write the local file data to the remote file stream
            if (false !== ($bytes = fwrite_stream($xmlstream, $xmlData))) {
                $success[] = [
                    'filepath' => $filepath,
                    'remote_filepath' => $remote_filepath,
                    'bytes' => $bytes,
                ];
            }
        } finally {
            //shortcut to ensure the xmlstream is closed on success or failure
            if (isset($xmlstream) && is_resource($xmlstream)) {
                fclose($xmlstream);
            }
        }
    }

    //testing purposes only to show the resulting uploads
    if (!empty($success)) {
        var_export($success);
    }
} finally {
    //shortcut to disconnect the ssh2 session on success or failure
    $sftp = null;
    unset($sftp);
    if (isset($ssh2) && is_resource($ssh2)) {
        ssh2_disconnect($ssh2);
    }
}
/*
 * Taken from PHP Manual
 * Writing to a network stream may end before the whole string is written.
 * Return value of fwrite() may be checked
 */
function fwrite_stream($fp, $string)
{
    for ($written = 0, $writtenMax = strlen($string); $written < $writtenMax; $written += $fwrite) {
        $fwrite = fwrite($fp, substr($string, $written));
        if (false === $fwrite) {
            return $written;
        }
    }

    return $written;
}

NOTE

All file operations will be created using the ssh2_auth_password user as the owner/group. You must ensure the specified user has read and write access to the desired directories.

Use the appropriate file masks to ensure desired file/directory permissions

  • 0777 (default) allows everyone to read, write, execute!
  • 0750 is typically desired for directories
  • 0640 is typically desired for individual files
  • use chmod($path, 0750) to change permissions on the remote file(s)
  • use chown($path, 'user') to change the owner on the remote file(s)
  • use chgrp($path, 'group') to change the group on the remote file(s)

Result

C:\xml\Race_1\file1.xml size 9
C:\xml\Race_1\file2.xml size 11
array (
  0 =>
  array (
    'filepath' => 'C:\\xml\\Race_1\\file1.xml',
    'remote_filepath' => 'ssh2.sftp://Resource id #5/FTP/XML/Race_1/file1.xml',
    'bytes' => 9,
  ),
  1 =>
  array (
    'filepath' => 'C:\\xml\\Race_1\\file2.xml',
    'remote_filepath' => 'ssh2.sftp://Resource id #5/FTP/XML/Race_1/file2.xml',
    'bytes' => 11,
  ),
)
Will B.
  • 17,883
  • 4
  • 67
  • 69
  • 1
    Hi Fyrye ... First of all thank you, you have helped me out not only with my issue but took the time to explain where i went wrong. I really appreciate your time, effort and guidance. thank you again. – MdM Feb 16 '19 at 00:23