Solution for: #27: Please select a file before uploading
passing files through AEC
- vlad on March 24, 2009, 03:58 AM UTC
The reason image/file uploads don't work is that Community Builder uses the stand POST form to upload files. This uploads the file and creates and array called $_FILES containing information on the uploaded file and the state of the upload. Unfortunately, it seems that the file gets lost (garbage collected?) once another form is submitted (e.g. plan selection page). We have to do some hacking to keep the file from getting wiped and then some more hacking to get Community Builder to recognize the file after we have messed with it.
Let's get started.
Since the uploaded file gets wiped after a second form is submitted, we have to catch it right at the point when you first hit Register on the initial CB form. Luckily, the AEC controller provides a place for us to make special adjustments in case you are running Community Builder. When you first submit the CB registration form, the task that is run is called "saveregisters". So we go to AEC controller and add code that:
1) checks and renames/moves the uploaded file using the special functions that PHP provides for file upload (is_uploaded_file and move_uploaded_file()).
2) Saves the file upload information contained in $_FILES to $_POST so AEC can pass it to CB later. (AEC doesn't pass $_FILES)
I added the following to: components/com_acctexp/acctexp.php
I saved every field in the following format the prefix "cb_files_(filename)_(property)" so that they are easy retrieve later.
The $_FILES array typically looks like this and can have more than one file (hence the iterations above):
Array
(
[file1] => Array (
[name] => MyFile.txt (comes from the browser, so treat as tainted)
[type] => text/plain (not sure where it gets this from - assume the browser, so treat as tainted)
[tmp_name] => /tmp/php/php1h4j1o (could be anywhere on your system, depending on your config settings, but the user has no control, so this isn't tainted)
[error] => UPLOAD_ERR_OK (= 0)
[size] => 123 (the size in bytes)
}
)
Next, we set up the code to recreate the $_FILES array and remove all the extra $_POST variables right before AEC calls CB to save the user. This is necessary because $_FILES is used by the CB when working with the image.
Add the following into /components/com_acctexp/acctexp.class.php
As I mentioned earlier, Community Builder expects that it is saving a user directly after someone hits Register on the form. Because of this, they use two functions which only work right after the form was submitted. We used both of them earlier (is_uploaded_file() and move_uploaded_file()) but we have to stop CB from using them now because they will both return false an cause an error.
The is_uploaded_file() call is in the prepareFieldDataSave function in the CBfield_image class. NOTE: make sure it is in the CBfield_image class because that function is defined about a dozen times in various classes. Look for the switch statement where the case is 'upload' and remove or comment the is_uploaded_file check from the if statement that is checking the $_FILES array.
In components/com_comprofiler/plugin/user/plug_cbcore/cb.core.php:
Lastly, remove the move_uploaded_file() call. We want to use the standard PHP move function (rename()) instead move_uploaded_file. In /administrator/components/com_comprofiler/imgToolbox.class.php, you need to go to the proccessImage function and change the line that moves the uploaded file from the tmp directory to the directory that Community Builder uses to store images.
Replace:
You will alsoABSOLUTELY want to hardcode the temp ($image) and output ($file) paths in imgToolbox.class.php because both could have been modified by the user as they were passed through the POST variables:
Let's get started.
Since the uploaded file gets wiped after a second form is submitted, we have to catch it right at the point when you first hit Register on the initial CB form. Luckily, the AEC controller provides a place for us to make special adjustments in case you are running Community Builder. When you first submit the CB registration form, the task that is run is called "saveregisters". So we go to AEC controller and add code that:
1) checks and renames/moves the uploaded file using the special functions that PHP provides for file upload (is_uploaded_file and move_uploaded_file()).
2) Saves the file upload information contained in $_FILES to $_POST so AEC can pass it to CB later. (AEC doesn't pass $_FILES)
I added the following to: components/com_acctexp/acctexp.php
case 'saveregisters':
if(isset($_FILES) && !empty($_FILES)) {
foreach($_FILES as $key => $value) {
//if its uploaded rename the file so it isn't garbage collected otherwise set error to 1 so CB handles it later
if(is_uploaded_file($value['tmp_name'])) {
move_uploaded_file( $value['tmp_name'], $value['tmp_name'] . "tmp");
$value['tmp_name'] = $value['tmp_name'] . "tmp";
}
else {
$value['error']= 1;
}
foreach($value as $k => $v) {
$_POST['cbfiles_(' . $key . ")_(" . $k . ")"] = $v;
}
}
}
subscribe( $option );
break;
I saved every field in the following format the prefix "cb_files_(filename)_(property)" so that they are easy retrieve later.
The $_FILES array typically looks like this and can have more than one file (hence the iterations above):
Array
(
[file1] => Array (
[name] => MyFile.txt (comes from the browser, so treat as tainted)
[type] => text/plain (not sure where it gets this from - assume the browser, so treat as tainted)
[tmp_name] => /tmp/php/php1h4j1o (could be anywhere on your system, depending on your config settings, but the user has no control, so this isn't tainted)
[error] => UPLOAD_ERR_OK (= 0)
[size] => 123 (the size in bytes)
}
)
Next, we set up the code to recreate the $_FILES array and remove all the extra $_POST variables right before AEC calls CB to save the user. This is necessary because $_FILES is used by the CB when working with the image.
Add the following into /components/com_acctexp/acctexp.class.php
foreach($var as $key => $value) {
if(strlen($key) > 8) {
$fileprefix = substr($key, 0, 8);
if($fileprefix == "cbfiles_"){
$regexp = "\((.+)\)_\((.+)\)";
if(ereg($regexp, $key, $regs) ) {
if(count($regs) == 3) {
$fname = $regs[1];
$fproperty = $regs[2];
if(!isset($_FILES) || empty($_FILES)) {
$_FILES = array($fname => array());
}
if(!isset($_FILES[$fname]) || empty($_FILES[$fname])) {
$_FILES[$fname] = array("name" => null, "type" => null, "tmp_name" => null, "error" => null, "size" => null);
}
$_FILES[$fname][$fproperty] = $value;
}
}
unset( $var[$key]);
}
}
}
As I mentioned earlier, Community Builder expects that it is saving a user directly after someone hits Register on the form. Because of this, they use two functions which only work right after the form was submitted. We used both of them earlier (is_uploaded_file() and move_uploaded_file()) but we have to stop CB from using them now because they will both return false an cause an error.
The is_uploaded_file() call is in the prepareFieldDataSave function in the CBfield_image class. NOTE: make sure it is in the CBfield_image class because that function is defined about a dozen times in various classes. Look for the switch statement where the case is 'upload' and remove or comment the is_uploaded_file check from the if statement that is checking the $_FILES array.
In components/com_comprofiler/plugin/user/plug_cbcore/cb.core.php:
if ( ( ! isset( $_FILES[$col_file]['tmp_name'] ) )
|| empty( $_FILES[$col_file]['tmp_name'] )
|| ( $_FILES[$col_file]['error'] != 0 )
// || ( ! is_uploaded_file( $_FILES[$col_file]['tmp_name'] ) )
) {
Lastly, remove the move_uploaded_file() call. We want to use the standard PHP move function (rename()) instead move_uploaded_file. In /administrator/components/com_comprofiler/imgToolbox.class.php, you need to go to the proccessImage function and change the line that moves the uploaded file from the tmp directory to the directory that Community Builder uses to store images.
Replace:
if ( ! @move_uploaded_file( $image, $file ) )
with: if(! @rename ($image, $file)).
.You will alsoABSOLUTELY want to hardcode the temp ($image) and output ($file) paths in imgToolbox.class.php because both could have been modified by the user as they were passed through the POST variables:
Comments
Thanks for this, I need this solution. BUT, where in acctexp.class.php do you add your code???
— Laurence Cope on May 12, 2011, 02:35 PM UTCIn reply to my last question, I added it in the beginning of the function saveUserRegistration(), after the global variables.
— Laurence Cope on May 12, 2011, 02:49 PM UTCIn your first piece of code, you add a line subscribe( $option ); Is this needed? This shows my subscription plans TWICE. If I remove it it shows once, and still works OK. Thanks for this.
— Laurence Cope on May 12, 2011, 02:53 PM UTCSorry (I wish I could edit my previous comments!) I did not enter the break, so works Ok now I add that, after the subscribe( $option );
— Laurence Cope on May 12, 2011, 02:56 PM UTCThanks