Jump to content

(Another!) Image upload script issue thread


JayGee
 Share

Recommended Posts

I'm trying to implement a front-end image upload form for user profile pictures, to a field called 'profile_picture'. The code is based on the various examples found around these forums but isn't yet 100% right.

The form successfully submits the image to the server and updates the field in the dashboard. When you submit the firm, the page reloads and uploads the image but the template still loads the old image path (now broken path as the old image has been removed).

I can only get the new image to show if I hit enter in the address bar forcing the page to reload.

Any ideas? Is it a caching issue, or something to do with the order of the script? It makes no difference if I call the image after the upload form.

<?php
//Display current user image
	$userImg = $user->profile_picture->first();
	echo '<img src="'.$userImg->url.'">';

    $upload_path = $config->paths->assets . "files/avatar_uploads/"; 

    $f = new WireUpload('userimage'); 
    $f->setMaxFiles(1);
    $f->setMaxFileSize(1*1024*1024);
    $f->setOverwrite(true);
    $f->setDestinationPath($upload_path);
    $f->setValidExtensions(array('jpg', 'jpeg', 'png', 'gif'));

    if($input->post->form_submit) {
        if(!is_dir($upload_path)) {
            if(!wireMkdir($upload_path)) throw new WireException("No upload path!");
        }

        $files = $f->execute(); 
    
        if ($f->getErrors()) {    
            foreach($files as $filename) @unlink($upload_path . $filename);
            foreach($f->getErrors() as $e) echo $e;
        } else {
            $user->of(false);
            $user->profile_picture->removeAll(); // wirearray  (line added by @horst: explanation is three posts beneath)
            $user->profile_picture = $upload_path . $files[0];
            $user->save();
            $user->of(true);
            @unlink($upload_path . $files[0]);
        }
    }

?>
<form class="forum-form" accept-charset="utf-8" action="./" method="post" enctype="multipart/form-data" >
    <input type="file" id="attach" name="userimage" accept="image/jpg,image/jpeg,image/gif,image/png" /> 
    <input type="submit" name="form_submit" value="Submit"/> 
</form>


 

Edited by Guy Incognito
Tidied up code snippet
Link to comment
Share on other sites

15 minutes ago, dragan said:

I guess it's a caching issue. Instead of 

action="./"

I would add a pseudo query string to the form target (timestamp or random characters).

This was my original guess. Query string is a good idea but doesn't seem to make any difference. ? I've tried as follows:

action="./?<?=substr(base64_encode(mt_rand()), 0, 15);?>"

 

Link to comment
Share on other sites

ok this time is good, you need to put your markup after the if condition.

 

<?php
//Display current user image
    $upload_path = $config->paths->assets . "files/avatar_uploads/"; 

    $f = new WireUpload('userimage'); 
    $f->setMaxFiles(1);
    $f->setMaxFileSize(1*1024*1024);
    $f->setOverwrite(true);
    $f->setDestinationPath($upload_path);
    $f->setValidExtensions(array('jpg', 'jpeg', 'png', 'gif'));

    if($input->post->form_submit) {
        if(!is_dir($upload_path)) {
            if(!wireMkdir($upload_path)) throw new WireException("No upload path!");
        }

        $files = $f->execute(); 
    
        if ($f->getErrors()) {    
            foreach($files as $filename) @unlink($upload_path . $filename);
            foreach($f->getErrors() as $e) echo $e;
        } else {
            $user->of(false);
            $user->profile_picture->removeAll(); // wirearray  (line added by @horst: explanation is three posts beneath)
            $user->profile_picture = $upload_path . $files[0];
            $user->save();
            $user->of(true);
            @unlink($upload_path . $files[0]);
        }
    }

// here
$userImg = $user->profile_picture->first();
echo '<img src="'.$userImg->url.'">';

?>

<form class="forum-form" accept-charset="utf-8" action="./" method="post" enctype="multipart/form-data" >
    <input type="file" id="attach" name="userimage" accept="image/jpg,image/jpeg,image/gif,image/png" /> 
    <input type="submit" name="form_submit" value="Submit"/> 
</form>

 

avatar.gif.36a218232f6922fd847c54738ce05b52.gif

 

and if you want to avoid a warning in imageExtra, then you should assign a title to the picture_profile field :

[...]
$user->profile_picture->title = ''; // or whatever
$user->save()
[...]

 

Edited by flydev
working example
  • Like 1
Link to comment
Share on other sites

It's not the Browser cache or something, but the PW page cache in memory. You'd need to reload the $user after your upload and saving image...

$u = $users->get($user->id);
if($u->profile_picture) {
    $userImg = $u->profile_picture;
    echo '<img src="'.$userImg->url.'">';
}

Anyway I would recommend to redirect to the page after submit, but then you need to handle errors differently. Like this you would upload the form with a refresh or going back.

Also I would rethink the upload folder to be in a folder not accessible from public for security reasons. You can simply use a folder in "site/assets/cache/.uploads" as . folders will get blocked by PW .htaccess.

Also you could move the part with the WireUpload into your if($input->post->form_submit) { .... as it's not needed before that.

 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

17 minutes ago, Soma said:

It's not the Browser cache or something, but the PW page cache in memory. You'd need to reload the $user after your upload and saving image...


$u = $users->get($user->id);
if($u->profile_picture) {
    $userImg = $u->profile_picture;
    echo '<img src="'.$userImg->url.'">';
}

Anyway I would recommend to redirect to the page after submit, but then you need to handle errors differently. Like this you would upload the form with a refresh or going back.

Also I would rethink the upload folder to be in a folder not accessible from public for security reasons. You can simply use a folder in "site/assets/cache/.uploads" as . folders will get blocked by PW .htaccess.

Also you could move the part with the WireUpload into your if($input->post->form_submit) { .... as it's not needed before that.

 

Ah perfect thanks - all makes perfect sense now you've spelled it out to me - reloading the user object solves the issue ? 

And yes - addressing the security issues above is in my todo list - this was just a prototype based on examples on this forum and it was bugging the hell out of me why the image wouldn't load first time! 

 

  • Like 1
Link to comment
Share on other sites

3 hours ago, Guy Incognito said:

Also I would rethink the upload folder to be in a folder not accessible from public for security reasons. You can simply use a folder in "site/assets/cache/.uploads" as . folders will get blocked by PW .htaccess.

I'm just experimenting with your idea @Soma as I hadn't thought about this approach for securing the upload folder before. Using the path as you described throws an error though, even if I manually create the .folder.

Error: Exception: No upload path!

Should PW automatically handle this path or does it need me to configure this folder somewhere? Or perhaps the server doesn't allow the .folders?

Link to comment
Share on other sites

  • 1 month later...

Can someone please help me, I am using the same code as mentioned above with some additional security check; even though the result is successful the image file is not being stored in the avatar folder within site/asset.

<?php namespace ProcessWire;

error_reporting(E_ALL);
ini_set('display_errors', 1);

ob_start();
require_once('/var/www/rajibde/index.php');
ob_end_clean();

$input = wire('input');
$sanitizer = wire('sanitizer');
$config = wire('config');
$session = wire('session');

$upload_path = $config->paths->assets . 'avatar/';
chmod($upload_path, 0777);

if ($input->post && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {

	$user = $session->get('memberName');

	$img = $input->post->image;
	
	list($type, $img) = explode(';', $img);
	list(, $img)      = explode(',', $img);
	list($type,) = explode('/',$type);

	$img = base64_decode($img);

	//$img = implode($img,'.',$type);

	$img = $img . '.' . $type;
	
	//print_r('Successful' . $img);

	$f = new WireUpload($img); 
	$f->setMaxFiles(1);
	$f->setMaxFileSize(1*1024*1024);
	$f->setOverwrite(true);
	$f->setDestinationPath($upload_path);
	$f->setValidExtensions(array('jpg', 'jpeg', 'png', 'gif'));

	if(!is_dir($upload_path)) {
		if(!wireMkdir($upload_path)) throw new WireException("No upload path!");
	}

	$files = $f->execute();
	echo $files[0];

	if ($f->getErrors()) {    
		foreach($files as $filename) @unlink($upload_path . $filename);
			foreach($f->getErrors() as $e) echo $e;
	}
	else {
		//Save the photo to the avatar field
		$u = $users->get($user->id);
		$u->of(false);
		$u->avatar = $upload_path . $files[0];
		//echo $u->avatar;
		$u->save();
		$u->of(true);
		@unlink($upload_path . $files[0]);
		print_r('Successful' . $img . $files[0]);
		echo 'Cropped image uploaded successfully.';
	}
}
else {
	echo '<div class="w3-row-padding w3-wide w3-text-red w3-text-jumbo"><strong>Unauthorised access. Your access to this site might be permanently blocked if tresspassing continues</strong></div>';
	die();
}

?>

 

Link to comment
Share on other sites

38 minutes ago, Raj said:

Can someone please help me, I am using the same code as mentioned above with some additional security check; even though the result is successful the image file is not being stored in the avatar folder within site/asset.

Does the user field get updated successfully?

I’m not in front of my computer at the moment to check but from my memory the file doesn’t stay in the upload folder it gets moved to the folder where all your site image uploads go.

Link to comment
Share on other sites

49 minutes ago, Guy Incognito said:

Does the user field get updated successfully?

I’m not in front of my computer at the moment to check but from my memory the file doesn’t stay in the upload folder it gets moved to the folder where all your site image uploads go.

Yes, only with the upload location but the file name and extension is missing.... In other words "echo files[0]" doesn't yield any value.

Link to comment
Share on other sites

8 minutes ago, Raj said:

Yes, only with the upload location but the file name and extension is missing.... In other words "echo files[0]" doesn't yield any value.

Still only on my iPad at mo so just guessing but don’t you need todo something like $files[0]->url to get the complete path?

Link to comment
Share on other sites

Path is getting populated and stored in DB but the file name and extension is missing in DB. Even the image file doesn't get stored in the upload location -> site/assets/avatar. ? And I am not able to figure out why it is not saving the image

Link to comment
Share on other sites

Nothing too obvious. Maybe if you can provide a complete code example of your setup, form and scripts it would be possible to help. Why is this a boostrap file with ob_start and ob_end_clean? And sending the form with ajax? This seems it is not a regular setup.

I would test your form getting send and the getting through. I think the problem is not the image not getting saved but not even getting uploaded whatever.

Link to comment
Share on other sites

This is the code that is responsible for invoking the AJAX call.

<?php namespace ProcessWire;

$session = wire('session');

?>
<span class="w3-col m5">

    <div id="upload_img" class="w3-margin w3-border-teal w3-round w3-opacity-min w3-display-container" style="border-style: groove;">
        <!--<i class="fa fa-edit w3-xxxlarge w3-hover-red w3-round-large w3-display-bottomright" style="font-weight:bolder;"></i>-->
    </div>

    <div class="w3-margin">
        <label class="w3-button w3-blue-gray w3-border w3-border-green w3-round-large w3-wide w3-bar uploadimg" style="font-weight:bolder;"><span><i class="fa fa-cloud-upload fa-lg w3-left">&nbsp;|</i></span>Upload Avatar<input type="file" id="upload" accept="image/*" class="avatar"></label>
        <!--<a class="w3-button w3-blue-gray w3-border w3-border-green w3-round-large w3-wide w3-bar actionUpload"><span><i class="fa fa-cloud-upload fa-lg w3-left">&nbsp;|</i>Change Avatar</span><input type="file" id="upload" value="Choose Image" accept="image/*"></a>-->
        <button class="w3-button w3-blue-gray w3-border w3-border-green w3-round-large w3-wide w3-bar actionDone" style="font-weight:bolder;"><i class="fa fa-check-circle-o fa-lg w3-left">&nbsp;|</i>Done</button>
    </div>
</span>

and this is the AJAX query which is responsible for calling the upload.php page

$('#upload').on('change', function () { 
	var reader = new FileReader();
	
	reader.onload = function (e) {
		$uploadCrop.croppie('bind', {
			url: e.target.result
		}).then(function(){
			console.log('jQuery bind complete');
		});
		$('.actionDone').toggle();
      		$('.uploadimg').toggle();
	}
	reader.readAsDataURL(this.files[0]);
});

$('.actionDone').on('click', function (ev) {

	$('.actionDone').toggle();
      	$('.uploadimg').toggle();

	$uploadCrop.croppie('result', {
		type: 'canvas',
		size: 'viewport'
	}).then(function (response) {
		$.ajax({
			url: '/site/templates/upload.php',
			type: "POST",
			data: {"image":response},
			success: function (data) {
				if (data == 'Cropped image uploaded successfully') {
					html = '<img src="' + response + '" id="img_resp" />';
					$("#upload_img").html(html);
				}
				else {
					$("body").append("<div class='upload-error'>" + data + "</div>");
				}
			}
		});
	});
});

Finally this is code in upload.php file that is responsible for uploading the image.

<?php namespace ProcessWire;

error_reporting(E_ALL);
ini_set('display_errors', 1);

ob_start();
require_once('/var/www/rajibde/index.php');
ob_end_clean();

$input = wire('input');
$sanitizer = wire('sanitizer');
$config = wire('config');
$session = wire('session');

$upload_path = $config->paths->assets . 'avatar/';
chmod($upload_path, 0777);

if ($input->post && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {

	$user = $session->get('memberName');

	$img = $input->post->image;
	$log->message("image ->" . $img);
	
	list($type, $img) = explode(';', $img);
	list(, $img)      = explode(',', $img);
	list($type,) = explode('/',$type);

	$img = base64_decode($img);

	//$img = implode($img,'.',$type);

	$img = $img . '.' . $type;
	$log->message("IMG TYPE ->" . $img);

	$f = new WireUpload($img); 
	$f->setMaxFiles(1);
	$f->setMaxFileSize(2*1024*1024);
	$f->setOverwrite(true);
	$f->setDestinationPath($upload_path);
	$f->setValidExtensions(array('jpg', 'jpeg', 'png', 'gif'));

	if(!is_dir($upload_path)) {
		if(!wireMkdir($upload_path)) throw new WireException("No upload path!");
	}

	$files = $f->execute();

	if ($f->getErrors()) {    
		foreach($files as $filename) unlink($upload_path . $filename);
			foreach($f->getErrors() as $e) echo $e;
	}
	else {
		$log->message("1 ->" . $_FILES);
		//Save the photo to the avatar field
		$u = $users->get($user->id);
		$log->message("2 ->" . $u);
		$u->of(false);
		$u->avatar = $upload_path . $files[0];
		$log->message("3 ->" . $u->avatar);
		$u->save();
		$u->of(true);
		unlink($upload_path . $files[0]);
		print 'Cropped image uploaded successfully';
	}
}
else {
	echo '<div class="w3-row-padding w3-wide w3-text-red w3-text-jumbo"><strong>Unauthorised access. Your access to this site might be permanently blocked if tresspassing continues</strong></div>';
	die();
}

?>

 

Link to comment
Share on other sites

I dont think this is working. Theres no form and input fields.

Edit: And you can`t call a php in templates folder directly in PW. Why don't you just do it using a real page?

Link to comment
Share on other sites

54 minutes ago, Soma said:

I dont think this is working. Theres no form and input fields.

Edit: And you can`t call a php in templates folder directly in PW. Why don't you just do it using a real page?

Hello Soma,

Input field is there in the html form...here is the code , please see after <span> field in the below code -> 

Could you help guide what you meant by "Why don't you just do it using a real page?" please?

<label class="w3-button w3-blue-gray w3-border w3-border-green w3-round-large w3-wide w3-bar uploadimg" style="font-weight:bolder;"><span><i class="fa fa-cloud-upload fa-lg w3-left">&nbsp;|</i></span>Upload Avatar<input type="file" id="upload" accept="image/*" class="avatar"></label>
Link to comment
Share on other sites

I mean to use a PW page (/some/url/) with its template file to handle all that instead of pointing directly to a .php file. You can't call a .php in templates folder, it would have to be somewhere in the webroot.

Link to comment
Share on other sites

2 hours ago, Soma said:

I mean to use a PW page (/some/url/) with its template file to handle all that instead of pointing directly to a .php file. 

Sure Soma, let me try that ?

with re to "You can't call a .php in templates folder, it would have to be somewhere in the webroot." not sure if I am doing it the correct way...all my PHP files are actually residing in site/templates folder, after saving them in this folder I add them as template using the Admin page and access them...and most of my validation pages are called by ajax method defined in custom JS files...

I am actually very new to CMS and PHP; learning on my own with abundant help from this forum and Google ?

If it doesn't take much time or there is already a page on this, can you help me understand the best way to build a custom site in processwire with custom styles and JS...mainly around where do I place my custom php files...how to utilize processwire to it's capactiy.

 

Link to comment
Share on other sites

Im sorry i cant make a full from 0 course of how to build with php html js css and PW. Theres plenty of guides and tutorials around here.

It's just you dont need to request a php file in a form. Youd just use the current page url as the action url. Them have your processing code in the template file that page uses. 

Why dont you start with a simple form without ajax? Once you have that down you can still try to use ajax.I never done a image upload with ajax so not even sure it would work the way you try it.

Maybe someone else had the time to give further advise or links.

Link to comment
Share on other sites

@Raj I second @Soma‘s advice. There’s a lot of great PW tutorials listed here:

https://processwire.com/docs/tutorials/

You don’t sound like you’re a million miles away from what you’re looking to achieve and as per Soma’s comment I would just reload your script to check for submission rather than worrying about Ajax upload.

Definitely recommend checking the tuts above out, it’s where I started with PW and I’ve not looked back!

I’ve found PW to be the easiest CMS to get to grips with and it’s also really pushing my PHP skills to new places too :-) because the core is so well organised it allows you to be more creative with what you want to do.

Link to comment
Share on other sites

22 hours ago, Guy Incognito said:

@Raj I second @Soma‘s advice. There’s a lot of great PW tutorials listed here:

https://processwire.com/docs/tutorials/

You don’t sound like you’re a million miles away from what you’re looking to achieve and as per Soma’s comment I would just reload your script to check for submission rather than worrying about Ajax upload.

Definitely recommend checking the tuts above out, it’s where I started with PW and I’ve not looked back!

I’ve found PW to be the easiest CMS to get to grips with and it’s also really pushing my PHP skills to new places too ? because the core is so well organised it allows you to be more creative with what you want to do.

Agree with you mate. I have already started going through the doc section once again, hopefully this time I will get the right essence. ?

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...