Save file to alternate location

December 4th, 2015

Common issue: the plugin saves files that are submitted via forms to the database…but I want to save the file to a special directory.

To do this, you create a filter as described on the page about Changing Form Data Before it is Saved but you use the following code.

Change the values of $formName, $fieldName and $uploaddir to suit your needs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
function cfdbFilterSaveFile($formData)
{
    // CHANGE THIS: CF7 form name you want to manipulate
    $formName = 'your-form'; 
 
    // CHANGE THIS: upload field name on your form
    $fieldName = 'form-file'; 
 
    // CHANGE THIS: directory where the file will be saved permanently
    $uploaddir = '/root/htdocs/wp-content/uploads/path/to/save/dir/';
 
    if ($formData && $formName == $formData->title && isset($formData->uploaded_files[$fieldName])) {
        // make a copy of data from cf7
        $formCopy = clone $formData;
 
        // breakdown parts of uploaded file, to get basename
        $path = pathinfo($formCopy->uploaded_files[$fieldName]);
        // directory of the new file
        $newfile = $uploaddir . $path['basename'];
 
        // check if a file with the same name exists in the directory
        if (file_exists($newfile)) {
            $dupname = true;
            $i = 2;
            while ($dupname) {
                $newpath = pathinfo($newfile);
                $newfile = $uploaddir . $newpath['filename'] . '-' . $i . '.' . $newpath['extension'];
                if (file_exists($newfile)) {
                    $i++;
                } else {
                    $dupname = false;
                }
            }
        }
 
        // make a copy of file to new directory
        copy($formCopy->uploaded_files[$fieldName], $newfile);
        // save the path to the copied file to the cfdb database
        $formCopy->posted_data[$fieldName] = $newfile;
 
        // delete the original file from $formCopy
        unset($formCopy->uploaded_files[$fieldName]);
 
        return $formCopy;
    }
    return $formData;
}
 
add_filter('cfdb_form_data', 'cfdbFilterSaveFile');

A More Sophisticated Example

Example where we save the file and add a URL to the file location into the form submission. Based on forum discussion.

This example is good if you have multiple forms to save file from. Here we have a reusable function “saveFilesInForm” and a “cfdbFilterSaveFiles” function that calls it. If you have multiple forms:

  1. create a new version of “cfdbFilterSaveFiles” with a unique function name for each form
  2. Add a new “add_filter” call for each form, passing the name of the new function
  3. The “saveFilesInForm” function appears only once
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
function cfdbFilterSaveFiles($formData) {
    // CHANGE THIS: CF7 form name you want to manipulate
    $formName = 'your-form';
 
    if ($formData && $formName == $formData->title) {
 
        // CHANGE THIS: directory where the file will be saved permanently
        $uploadDir = '/root/htdocs/wp-content/uploads/path/to/save/dir/';
 
        // CHANGE THIS: URL to the above directory
        $urlDir = 'http://your-site.com/uploads/path/to/save/dir/';
 
        // CHANGE THIS: upload field names on your form
        $fieldNames = array('upload-field-1', 'upload-field-2', 'upload-field-3');
 
        return saveFilesInForm($formData, $uploadDir, $urlDir, $fieldNames);
    }
 
    return $formData;
}
 
add_filter('cfdb_form_data', 'cfdbFilterSaveFiles');
 
 
function saveFilesInForm($formData, $uploadDir, $urlDir, $fieldNames) {
 
    // make a copy of data from cf7
    $formCopy = clone $formData;
 
    foreach ($fieldNames as $fieldName) {
        if (isset($formData->uploaded_files[$fieldName])) {
 
            // breakdown parts of uploaded file, to get basename
            $path = pathinfo($formCopy->uploaded_files[$fieldName]);
            // directory of the new file
            $newfile = $uploadDir . $path['basename'];
 
            // check if a file with the same name exists in the directory
            if (file_exists($newfile)) {
                $dupname = true;
                $i = 2;
                while ($dupname) {
                    $newpath = pathinfo($newfile);
                    $newfile = $uploadDir . $newpath['filename'] . '-' . $i . '.' . $newpath['extension'];
                    if (file_exists($newfile)) {
                        $i++;
                    } else {
                        $dupname = false;
                    }
                }
            }
 
            // make a copy of file to new directory
            copy($formCopy->uploaded_files[$fieldName], $newfile);
 
            // save the path to the copied file to the cfdb database
            $formCopy->posted_data[$fieldName] = $newfile;
 
            $path = pathinfo($newfile);
            $formCopy->posted_data[$fieldName . '-url'] = $urlDir . $path['basename'];
 
            // delete the original file from $formCopy
            unset($formCopy->uploaded_files[$fieldName]);
        }
 
    }
    return $formCopy;
}
  1. brian
    February 6th, 2013 at 13:45 | #1

    Hi,
    I tried using the code above replacing $formName and $uploaddir but it cannot seem to get files to upload into the directory. I’ve tried different variation on the directory path but nothing seems to work. The files are still posted to the database. Any suggestions would be greatly appreciated.

    Cheers,
    Brian

  2. sebi
    March 12th, 2013 at 20:27 | #3

    Hi

    I got this part working, but having troubles showing the records in the admin.
    In the tables of the admin it links only the files stored in the db for download.

    Is there any config or easy way to show download links in the admin for files stored in the filesystem as well?

    Cheers, Sebi

    • Michael Simpson
      March 13th, 2013 at 11:39 | #4

      In the code above, I would set a new field to point to the uploaded file. After:
      $formCopy->posted_data['form-file'] = $newfile;
      I would add something like
      $formCopy->posted_data['file-url'] = "http://your-site.com/path/to/$newfile";

  3. Shuki
    April 16th, 2013 at 10:55 | #5

    Please remove above message, it was a mistake.

    I am getting this message

    PHP Warning: copy(): Filename cannot be empty in /html/wp-content/themes/xxx/functions.php on line 406

    And the file is nowhere to be found.

    Thanks in advance,
    Shuki

    • Michael Simpson
      April 16th, 2013 at 14:17 | #6

      I think the issue is you need to change ‘form-file’ to the name of the form field on your form. I’ll update that in the code example on this page.

  4. r1987
    May 7th, 2013 at 10:29 | #7

    Hello,

    This snippet seems to not work in my situation also.

    Some friendly observations using Contact Form 7:

    -Email is sent out and it will be saved to the db, but on the front-end after submiting the sending proccess freezes (ajax-gif circles forever).
    -File is not saved to the specified folder.
    -In DB extension, attachment field shows the path of the file, but its not clickable and its not on ftp also.

    Im running on WordPress 3.5.1, clean install, twentytwelve theme.

    It would be really cool to get this snippet working somehow…

    • Michael Simpson
      May 7th, 2013 at 10:51 | #8

      Check if it is throwing an exception. Wrap all the code inside the function in a try-catch and print to your error log.

      1
      2
      3
      4
      5
      6
      7
      8
      
      function cfdbFilterSaveFile($formData) {
          try {
              // ... code
          }
          catch (Exception $ex) {
              error_log($ex->getMessage());
          }
      }

      PS. Will need your php.ini file to have
      error_log = /path/to/a/file/errors.txt

  5. r1987
    May 7th, 2013 at 11:57 | #9

    @Michael Simpson

    Tried this, created the php.ini file and errors.txt file. errors.txt file is empty after I submit the form.

    But I checked the WP error_log, and there was this line:
    [07-May-2013 15:51:46 UTC] PHP Warning: copy(/public_html/wordpress/wp-content/uploads/pictures/Picture-1.png) [function.copy]: failed to open stream: No such file or directory in /home/rannoait/public_html/wordpress/wp-content/themes/twentytwelve/functions.php on line 488

    Any help?

  6. Michael Simpson
    May 7th, 2013 at 12:05 | #10

    @r1987
    Does /public_html/wordpress/wp-content/uploads/pictures/ exist and is it writeable?

  7. r1987
    May 7th, 2013 at 12:27 | #11

    @Michael Simpson

    Yes, it exsists and its writable.

  8. Michael Simpson
    May 8th, 2013 at 22:06 | #12

    @r1987
    If the copy line does not work, you could try this as an alterative and see if you get an error with more information

    1
    2
    3
    4
    5
    
            $fsrc = fopen($formCopy->uploaded_files[$fieldName],'r'); 
            $fdest = fopen($newfile,'w+'); 
            stream_copy_to_stream($fsrc,$fdest); 
            fclose($fsrc); 
            fclose($fdest);

    PS, be sure the directory is writeable by the user that the web server runs as (like “apache”).

  9. r1987
    May 9th, 2013 at 07:00 | #13

    Well, Michael. I have to appologize because of my dumbness.

    What I did:
    -I copied the path location from my FTP and it was /public_html/…
    -I watched the error_log again now, and noticed, it showed /home/mydomainname/public_html/…

    And I got the very first code working, it was the wrong path all the time.

    Sorry for waisting your time. May-be it would be good to really make it red, that people some how copy the “real” root.

    But there’s one more question that I have:

    Lets say, that I have two forms with the upload field and I want both form uploads to go to the same folder, then I just copy/paste the same filter with just the new form name and field name right?

    • Michael Simpson
      May 9th, 2013 at 08:46 | #14

      Regarding having two forms, I would try not to duplicate the code. Instead I would parameterize the function we have and create wrapper functions for each form submission. In other words, the following:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      
      // Added parameters to this function
      function cfdbFilterSaveFile($formData, $formName, $fieldName, $uploaddir)
      {
          if ($formData && $formName == $formData->title) {
              // make a copy of data from cf7
              $formCopy = clone $formData;
       
              // breakdown parts of uploaded file, to get basename
              $path = pathinfo($formCopy->uploaded_files[$fieldName]);
              // directory of the new file
              $newfile = $uploaddir . $path['basename'];
       
              // check if a file with the same name exists in the directory
              if (file_exists($newfile)) {
                  $dupname = true;
                  $i = 2;
                  while ($dupname) {
                      $newpath = pathinfo($newfile);
                      $newfile = $uploaddir . $newpath['filename'] . '-' . $i . '.' . $newpath['extension'];
                      if (file_exists($newfile)) {
                          $i++;
                      } else {
                          $dupname = false;
                      }
                  }
              }
       
              // make a copy of file to new directory
              copy($formCopy->uploaded_files[$fieldName], $newfile);
              // save the path to the copied file to the cfdb database
              $formCopy->posted_data[$fieldName] = $newfile;
       
              // delete the original file from $formCopy
              unset($formCopy->uploaded_files[$fieldName]);
       
              return $formCopy;
          }
          return $formData;
      }
       
      // Wrapper function for form1 to be registered as a filter
      function form1_save_file($formData)
      {
          // CHANGE THIS: CF7 form name you want to manipulate
          $formName = 'form1';
       
          // CHANGE THIS: upload field name on your form
          $fieldName = 'form-file';
       
          // CHANGE THIS: directory where the file will be saved permanently
          $uploaddir = '/root/htdocs/wp-content/uploads/path/to/save/dir/';
       
          cfdbFilterSaveFile($formData, $formName, $fieldName, $uploaddir);
      }
       
      // Wrapper function for form2 to be registered as a filter
      function form2_save_file($formData)
      {
          // CHANGE THIS: CF7 form name you want to manipulate
          $formName = 'form2';
       
          // CHANGE THIS: upload field name on your form
          $fieldName = 'form-file';
       
          // CHANGE THIS: directory where the file will be saved permanently
          $uploaddir = '/root/htdocs/wp-content/uploads/path/to/save/dir/';
       
          cfdbFilterSaveFile($formData, $formName, $fieldName, $uploaddir);
      }
       
      // Register wrapper functions
      add_filter('cfdb_form_data', 'form1_save_file');
      add_filter('cfdb_form_data', 'form2_save_file');
  10. korzex
    July 3rd, 2014 at 09:30 | #15

    @Michael Simpson
    Hi
    Thanks for this, very good work, but not work with new WordPress properly. If i registering two filter work only first, and not to end. File be saved to disk and saved blob to db.
    For me better work this:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    function cfdbFilter($formData){
        //identyfying form1
        if($formData && 'Form1' == $formData->title){
          $uploaddir='/path/to/uploads/dir/';
          $script='/root/somescript1.sh'; //use for transfer file via rsync
        }
        //or another
        if($formData && 'Form2' == $formData->title){
          $uploaddir='/another/path/to/uploads/dir/';
          $script='/root/anotherscript.sh';
        }
        //processing file
        $formCopy=clone $formData;
        $path=pathinfo($formCopy->uploaded_files['form-file']);
        $newfile=$uploaddir.$path['basename'];
        copy($formCopy->uploaded_files['form-file'],$newfile);
        $formCopy->posted_data['form-file']=$newfile;
        unset($formCopy->uploaded_files['form-file']);
        exec("sudo $script &");
        return $formCopy;    
    }
    add_filter('cfdb_form_data','cfdbFilter');

    when $script do:

    1
    2
    3
    4
    
    cd $path
    for i in *; do
    rsync --remove-source-files * $server/$path
    done

    Thanks anyway πŸ™‚

  11. Michael Simpson
    July 3rd, 2014 at 09:58 | #16

    @korzex
    Thanks for your comment. I suggest you add an “if” guarding “processing file” so that the function doesn’t try to save the file for other forms.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    function cfdbFilter($formData){
        $processFile = false;
        //identifying form1
        if($formData && 'Form1' == $formData->title){
          $uploaddir='/path/to/uploads/dir/';
          $script='/root/somescript1.sh'; //use for transfer file via rsync
          $processFile = true;
        }
        //or another
        if($formData && 'Form2' == $formData->title){
          $uploaddir='/another/path/to/uploads/dir/';
          $script='/root/anotherscript.sh';
          $processFile = true;
        }
        //processing file
        if ($processFile) {
          $formCopy=clone $formData;
          $path=pathinfo($formCopy->uploaded_files['form-file']);
          $newfile=$uploaddir.$path['basename'];
          copy($formCopy->uploaded_files['form-file'],$newfile);
          $formCopy->posted_data['form-file']=$newfile;
          unset($formCopy->uploaded_files['form-file']);
          exec("sudo $script &");
          return $formCopy;    
      }
      return $formData;
    }
    add_filter('cfdb_form_data','cfdbFilter');

    It is possible that two filters did not work when posting a submission to Form2 is because the first filter for Form1 had an error so the filter for Form2 did not run. I’m not sure about this.

  12. korzex
    July 22nd, 2014 at 04:30 | #17

    Hi
    Works very well πŸ™‚ Thanks

  13. esyalaa
    September 7th, 2014 at 22:18 | #18

    Hi.
    This example code works very well πŸ™‚

    This example code will save the path to the copied file to the cfdb database,
    but how if i want to save the url of the file that i uploaded to the database?

    For example,
    http://www.your-site.com/wordpress/wp-content/uploads/wpcf7_uploads/video.mp4

    Thanks πŸ™‚

    • Michael Simpson
      September 7th, 2014 at 22:28 | #19

      In addition to or instead of:
      $formCopy->posted_data[$fieldName] = $newfile;
      You could do something like:
      $formCopy->posted_data['fileurl'] = 'http://.....';

  14. esyalaa
  15. Michael Simpson
    September 7th, 2014 at 23:17 | #21

    @esyalaa
    …then you must modify the code to make it behave as you wish.

Comments are closed.  Go To Support Forum