Frank Vèssia Posted January 30, 2011 Share Posted January 30, 2011 I have a form that stores data in db adding a page. It could be nice to add also a field image with the same behavior like the one in the admin, with maximum image uploaded and option for resize. Can i access to some internal functions for making this? Link to comment Share on other sites More sharing options...
ryan Posted January 31, 2011 Share Posted January 31, 2011 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 More sharing options...
Frank Vèssia Posted January 31, 2011 Author Share Posted January 31, 2011 Yes, that's the point, security... but i really need this in my form...maybe for now i can save the field like a string using the name of the file and store the file with my own function in a folder on the server... :-\ Link to comment Share on other sites More sharing options...
Adam Kiss Posted January 31, 2011 Share Posted January 31, 2011 Well, theoretically, you should be able to upload file on server, add it to page and save it [or reject it based on your rules]... Link to comment Share on other sites More sharing options...
Frank Vèssia Posted January 31, 2011 Author Share Posted January 31, 2011 yes, manually i can do this, but i hoped to reuse some good internal functions and not make this handmade... Link to comment Share on other sites More sharing options...
Adam Kiss Posted January 31, 2011 Share Posted January 31, 2011 I understand. If I may ask, what you wanted to used that? We may find another solution to your problem. Link to comment Share on other sites More sharing options...
Frank Vèssia Posted January 31, 2011 Author Share Posted January 31, 2011 My website is a classified website and every users (also guest) can add an ad with photo. So it's important to have a complete form with images uploader. Link to comment Share on other sites More sharing options...
Frank Vèssia Posted September 27, 2011 Author Share Posted September 27, 2011 I'm resuming this post cause now pw 2.1 is out with new user system ;D. So is there now a way to add image from disk from my external form using api? Link to comment Share on other sites More sharing options...
ryan Posted September 28, 2011 Share Posted September 28, 2011 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). 1 1 Link to comment Share on other sites More sharing options...
Frank Vèssia Posted September 28, 2011 Author Share Posted September 28, 2011 thanks, i will try to build something... Link to comment Share on other sites More sharing options...
Pete Posted September 28, 2011 Share Posted September 28, 2011 Hypothetically, would it be relatively easy to use the new fancy uploader in the front-end? Link to comment Share on other sites More sharing options...
Frank Vèssia Posted September 29, 2011 Author Share Posted September 29, 2011 that's my code, i didn't use the ImageSizer class cause of my bad php knowledge 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 More sharing options...
ryan Posted September 29, 2011 Share Posted September 29, 2011 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 1 Link to comment Share on other sites More sharing options...
Frank Vèssia Posted September 29, 2011 Author Share Posted September 29, 2011 woow.. thank you so much Link to comment Share on other sites More sharing options...
Frank Vèssia Posted September 30, 2011 Author Share Posted September 30, 2011 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 More sharing options...
Pete Posted September 30, 2011 Share Posted September 30, 2011 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 More sharing options...
Frank Vèssia Posted September 30, 2011 Author Share Posted September 30, 2011 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 More sharing options...
Soma Posted September 30, 2011 Share Posted September 30, 2011 then someone goes with js enabled to know where your form processing is and attacks that directly. Link to comment Share on other sites More sharing options...
Frank Vèssia Posted September 30, 2011 Author Share Posted September 30, 2011 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 More sharing options...
Pete Posted September 30, 2011 Share Posted September 30, 2011 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 More sharing options...
Frank Vèssia Posted September 30, 2011 Author Share Posted September 30, 2011 Your point is correct but i have to say that all my forms have action="" and js call external php pages for making all so nobody can access them...however adding 2 more lines of php for validation code don't cost anything... ;D Link to comment Share on other sites More sharing options...
ryan Posted September 30, 2011 Share Posted September 30, 2011 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 More sharing options...
Frank Vèssia Posted September 30, 2011 Author Share Posted September 30, 2011 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 More sharing options...
Frank Vèssia Posted September 30, 2011 Author Share Posted September 30, 2011 I tried, i can delete images from api..but not from admin... Link to comment Share on other sites More sharing options...
ryan Posted October 3, 2011 Share Posted October 3, 2011 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 More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now