Jump to content

Front end image uploader like admin


Frank Vèssia
 Share

Recommended Posts

That's a good question. It is feasible to do this, but I see possible security implications with making this sort of functionality accessible from the front end. When you've got something like this on the front end, you really want to be storing files they add in a non-web accessible location, and you want to make sure they aren't posting hundreds of giant files to fill up your hard drive, and using image resize functions as DDOS entry points, and so on. Lots of implications like that when you consider completely anonymous users. The current file/image tool isn't designed for anonymous/guest users. That's not to say that it's not plenty secure, but that I'm very paranoid about such functions (in any CMS) unless they are specifically designed for guest/anonymous use (like the one included in these forums). Once we have a more thorough users system in place, I think we should make these fieldtypes more accessible outside administrative use.

Link to comment
Share on other sites

  • 7 months later...

I think this is more likely to be a component of the 2.3 version with form builder. However, I don't think you need to wait for that. I would say to build something custom for your need, because this isn't particularly hard to do. When you are dealing with image uploads, you'll want the original uploaded image to be placed in a non-web accessible quarantine location. Then after confirming that it is indeed an image (by checking the extension and using php's get_image_size() to confirm it's valid) then you'll want to use ProcessWire to create a resized copy of it (using the ImageSizer class in /wire/core/). To the best of my knowledge, the resized copy is safe to be web accessible (i.e. okay to add to a page). Once you've done that, delete the original one that they uploaded. At this point, the main concerns would be copyrights (who owns the imagery) and content (inappropriate imagery, if applicable to your case). 

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

that's my code, i didn't use the ImageSizer class cause of my bad php knowledge  :P but it works... thanks.

<?
define ("MAX_SIZE","100"); 
function getExtension($str) {
    $i = strrpos($str,".");
    if (!$i) { return ""; }
    $l = strlen($str) - $i;
    $ext = substr($str,$i+1,$l);
    return $ext;
}
require_once('../index.php');
$input = 		wire('input');
$sanitizer =    wire('sanitizer');
$users = 		wire('users')
if($input->post->userid) {
$filename = stripslashes($_FILES['imageUpload']['name']);
	//get the extension of the file in a lower case format
 	$extension = getExtension($filename);
	$extension = strtolower($extension);
	if (($extension != "jpg") && ($extension != "jpeg") && ($extension != "png") && ($extension != "gif")) {
		print "File non valido";
		exit;
}else{
	$size=filesize($_FILES['imageUpload']['tmp_name']);
	if ($size > MAX_SIZE*1024){
		print 'File troppo grosso';
		exit;
	}
	//copy the image in secure folder
	$image_name=time().'.'.$extension;
	$newname="../site/tmpfiles/profiles/".$image_name;
	$copied = copy($_FILES['imageUpload']['tmp_name'], $newname);
	if ($copied) {
		$userid	= $sanitizer->text($input->post->userid);
		$u = $users->get($userid);
		$u->setOutputFormatting(false);
		$u->profilephoto->add("http://".$_SERVER['HTTP_HOST']."/site/tmpfiles/profiles/".$image_name);
		$u->save();
                       //delete copied image
		$tmpfile = $_SERVER["DOCUMENT_ROOT"]."/site/tmpfiles/profiles/".$image_name;
		if (file_exists($tmpfile)) { unlink ($tmpfile); }

	}else{
		print "Errore nel salvataggio del file.";
		exit;
	}
}
print $u->profilephoto->eq(1)->url;
}
?>

what is thew commando for remove an image? ->remove maybe?

Link to comment
Share on other sites

if($input->post->userid) {

I would suggest changing that to:

if($input->post->userid && isset($_FILES['imageUpload']['name'])) {

But some concerns about your $userid variable (further down).

$image_name=time().'.'.$extension;

This may not be specified enough. I'd suggest getting the $user first, and making the image_name be based on the user ID (perhaps with the time() component too, if you want it).

$newname="../site/tmpfiles/profiles/".$image_name;

Just a note here, but you want to be really certain that /site/tmpfiles/profiles/ is a non-web-accessible directory. Further in your code, you were adding it to PW with an HTTP URL, making me think it is web accessible.

$copied = copy($_FILES['imageUpload']['tmp_name'], $newname);

It's better to use PHP's move_uploaded_file() rather than copy, i.e.

$copied = move_uploaded_file($_FILES['imageUpload']['tmp_name'], $newname);

$userid = $sanitizer->text($input->post->userid);

There is no authentication here, so any user can substitute another user's info in the POST vars... so the method you are using is not safe. But we'll get to that later. Lets assume for the moment that it was okay to include this in your POST vars (which it's not, but...) you would want to sanitize it differently. If that user ID is the user's ID number (i.e. integer), then you'd do this:

$userid = (int) $input->post->userid; 

If it's the user's NAME (string) then do this:

$userid = $sanitizer->pageName($input->post->userid); 

Also, after you get the $user, make sure that the user exists before adding stuff to it, like this:

if(!$user->id) die("Invalid user");

Back to the security issue here. Since you are getting the current user from a POST var, it will be possible for any user to substitute another user's name or ID and change their photo. So I would suggest that you avoid using user ID's at all. You would only want to operate on a user that's already logged in. Or, if that's not an option, then you'd want them to provide their login and password as the user id rather than something else. If you are dealing with a user that's already logged in, you can do this:

$user = wire('user'); 

No need to send that info in POST vars when it's already safely stored in the session.

But if your situation is one where the user isn't going to be already logged in, then you'll want your front end form to have login and password inputs, and then get the user that way:

<?php
$u = $session->login($input->post->username, $input->post->pass); 
if($u->id) {
    // they can upload a photo
}

Before adding the image to your site, I would suggest making a resized copy of it just so that you are dealing with a real image and not something with anything fishy going on in the image format:

<?php

$filename = "../site/tmpfiles/profiles/$image_name";

try {
    // if image does not contain valid image data, the following will throw an exception
    $image = new ImageSizer($filename); 

    // set some boundaries on the size of the image you are willing to work with
    $w = $image->getWidth();
    $h = $image->getHeight();
    if($w < 20 || $h < 20 || $w > 3000 || $h > 3000) throw new WireException("Image is out of bounds"); 

    // overwrite old image with new resized version
    $image->resize($width - 10); 

    // now add to the PW field
    $u->profilephoto->add($filename); 

    // remove the original
    unlink($filename); 

} catch(Exception $e) {
    unlink($filename); 
    echo "Error:" . $e->getMessage(); 
}

Note that there's no need to specify the URL like this:

$u->profilephoto->add("http://".$_SERVER['HTTP_HOST']."/site/tmpfiles/profiles/".$image_name);

That makes me think the filename is web accessible–make sure the uploaded files aren't web accessible until after you are certain all is good. When you are adding to PW, you can just specify the local filename rather than the URL (like in my example above).

$tmpfile = $_SERVER["DOCUMENT_ROOT"]."/site/tmpfiles/profiles/".$image_name;

Don't bother with DOCUMENT_ROOT. Just use the same $filename as earlier. If you must have document_root, then use the one from PW instead: $config->paths->root

  • Like 1
Link to comment
Share on other sites

This is what i've done till now. I'm using jquery validation for image type and size so my php now is more clean. I still need to implement the ImageSizer class because i have some problems with your code but i will explain later.

Now i have another problem.

$("#profilephotoform").validate({
	debug: false,
	rules: {
		imageUpload: {
			required: true,
			accept: true,
			filesize: 524288 
		}
	},
	messages: {
		imageUpload: "Solo immagini JPG, GIF o PNG di massimo 500 Kb",
	}
}); 

<?
function getExtension($str) {
    $i = strrpos($str,".");
    if (!$i) { return ""; }
    $l = strlen($str) - $i;
    $ext = substr($str,$i+1,$l);
    return $ext;
}
require_once('../index.php');
$input 		 =  wire('input');
$sanitizer 	 =  wire('sanitizer');
$users		 =  wire('users');
$user		 =  wire('user');
if($_FILES['imageUpload']['name']) {
$filename = stripslashes($_FILES['imageUpload']['name']);
$extension = getExtension($filename);
$image_name=time().$user->id.'.'.$extension;
$newname="../site/tmpfiles/profiles/".$image_name;
$copied = move_uploaded_file($_FILES['imageUpload']['tmp_name'], $newname);
if ($copied) {
	$user->setOutputFormatting(false);
	$user->profilephoto->add($config->paths->root."site/tmpfiles/profiles/".$image_name);
	$user->save();
	//delete copied image
	$tmpfile = $config->paths->root."/site/tmpfiles/profiles/".$image_name;
	if (file_exists($tmpfile)) { unlink ($tmpfile); }
}else{
	print "Errore nel salvataggio del file";
	exit;
}
print $user->profilephoto->last()->size(250,0)->url;
}
?>

The code works good now, i can add images but when i try to delete one of these images from the admin i get this error;

Notice: Trying to get property of non-object in /home/librolo/public_html/wire/core/Pagefiles.php on line 207

Fatal error: Call to a member function path() on a non-object in /home/librolo/public_html/wire/core/Pagefiles.php on line 207

P.S.: How can i delete images using api??

Getting back to ImageSizer i tried to implement it but in your code you wrote:

$u->profilephoto->add($filename); 

$filename is the path of the image saved in tmp folder, so i changed to $image that is the new image object created from imageSizer class, but $image return an object and it doesn't work. I printed the $image array and i found $image->filename but also this is not good because is protected, like all properties of $image...

Link to comment
Share on other sites

This is what i've done till now. I'm using jquery validation for image type and size so my php now is more clean.

You should still have PHP validation - if someone disables Javascript in their browser to try and sneakily upload something they shouldn't be uploading then the jQuery validation won't take place.

Link to comment
Share on other sites

You should still have PHP validation - if someone disables Javascript in their browser to try and sneakily upload something they shouldn't be uploading then the jQuery validation won't take place.

If someone don't have javascript they cannot view the website... and i show up an alert message. I have too many js controls and javascript is essential

Link to comment
Share on other sites

come on...my website will be not so huge to entering in the target of some hacker or similar, however without js you cannot enter in the website, just the homepage.

Try to "use" Facebook without js...

I want to deliver the best experience to my users and i decided to use a lot of javascript and css with special features, i also will not support IE6. I don't think the 1% of users with js disabled will affect my activities...i want to focus on a particular target of people.

Link to comment
Share on other sites

I get that the website would be unusable without JS, but I also used to think that a site of mine was so small that it would never be spammed or attacked, and it was attacked and quite a lot of damage was done that could have been stopped by 2 lines of PHP validation on a form.

Assuming you go as far as hiding the menu of the site somehow from thosevisitors  without JS enabled, it's a simple matter of finding the page links via Google. It's also possible that spam bots or other more serious bots could crawl your site with JS enabled and then come back and attack with JS turned off. If I was going to write such a bot, that's the way I would do it so that with JS enabled it can find all possible attack vectors and then try and exploit them with JS both enabled and disabled at a later date. Not that I'd ever do such a thing (or even know how :)).

Unfortunately when building websites, I've learned to build them with nasty hackers in mind as well as my target audience. It's just one of those things, and many sites that get attacked are attacked by clever bots automatically, so it's not even as though it's a personal attack, just a real headache when it does eventually happen.

Link to comment
Share on other sites

This is what i've done till now. I'm using jquery validation for image type and size so my php now is more clean.

You'll definitely still want to have server-side validation. The JS validation will only keep out the good guys that accidentally selected the wrong file. The bad guys (many of which are automated) will blow right by the JS validation. However, if files start in a quarantine (non-accessible) area, and you use the ImageSizer class, that will throw an exception if something isn't an image. As long as you catch the exception and unlink the file (like in the previous example) that may take care of your server side validation. Still though, I would recommend checking the file extension before doing your move_uploaded_file(), because you can do it with just one (albeit long) line of code:

if(!in_array(strtolower(getExtension($_FILES['imageUpload']['name'])), array('jpg', 'jpeg', 'png', 'gif'))) die("Invalid file"); 

The code works good now, i can add images but when i try to delete one of these images from the admin i get this error;

I actually have no idea why you are getting this error. Are you able to see the image on the page, but just can't delete it? Let me know if it's consistently reproducible, and make sure you've got the latest PW version, just in case.

P.S.: How can i delete images using api??

Assuming profilephoto is a multi-image field.

<?php
$photo = $user->profilephoto->first(); // or whichever one you want to delete
$user->profilephoto->delete($photo); 
$user->save();

// You can also do this, to delete all the photos in the field:
$user->profilephoto->deleteAll();

$filename is the path of the image saved in tmp folder, so i changed to $image that is the new image object created from imageSizer class, but $image return an object and it doesn't work. I printed the $image array and i found $image->filename but also this is not good because is protected, like all properties of $image...

The ImageSizer is taking care of moving things around behind-the-scenes, so you can treat it as if it's operating on the original image. Technically, it's creating a new image, resizing it, deleting the original, and renaming the new one to the original's filename. But the point is, your $filename is staying the same so just keep using that. You don't need to retrieve the filename from ImageSizer since you already have it.

Link to comment
Share on other sites

Ok, i've added the php validation and imageSizer correctly.

Regarding the error in the admin i'm using PW 2.1 latest commit, cannot say more.  :(

I'm adding images to user profile in a multi-image field, that's all. It happens for every users, i can see the images both in front end and in the admin but i cannot delete them from the admin, now i will try to delete from api...i will let you know.

Link to comment
Share on other sites

I was able to duplicate that here too. I'm not 100% sure why this issue only affects images in user profiles (as opposed to other pages), but the internal uncaching feature causing the problem was not crucial to keep, so I've committed a fix and it's available now. Thanks for finding this.

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

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...