MatthewSchenker

Create Pages (with File-Upload Field) via API

Recommended Posts

Greetings Everyone,

*************************************************
*************************************************
EDIT NOTE: This post started as a work-in-progress discussion as I was working out the elements of a successful form. After contributions from participants in this discussion, the code below has been tested and works well.

You can use the code as shown below in your ProcessWire templates!

Feel free to follow up with additional quesations/comments!
*************************************************
*************************************************

I have successfully built front-end forms with ProcessWire to add pages via the API. It works great -- until I had to include image uploads along with the "regular" form fields.  Then it temporarily got a bit complicated.

In this discussion, I show how to handle front-end submissions in ProcessWire with the goal of allowing us to create pages from custom forms.  I then go a step further and show how to use the same form to upload files (images and other files).

I'm hoping this discussion can illustrate the whole process. I know a lot of people are interested in using ProcessWire to do front-end submissions, and my goal for this discussion is to benefit others as well as myself!

First, here's my original contact form (no file uploads):

<form action="/customer-service/contact/contact-success/" method="post">
<p><label for="contactname">Name:</label></p>
<p><input type="text" name="contactname"></p>
<p><label for="email">E-Mail:</label></p>
<p><input type="email" name="email"></p>
<p><label for="comments">Comments:</label></p>
<p><textarea name="comments" cols="25" rows="6"></textarea></p>
<button type="submit">Submit</button></form>

And here's the "contact-success" page that picks up the form entry to create ProcessWire pages:

<?php // First, confirm that a submission has been made
if ($input->post->contactname) {

// Save in the ProcessWire page tree; map submission to the template fields
$np = new Page(); // create new page object
$np->template = $templates->get("contact_submission"); 
$np->parent = $pages->get("/customer-service/contact-us/contact-submission-listing/");

// Send all form submissions through ProcessWire sanitization
$title = $sanitizer->text($input->post->contactname);
$name = $sanitizer->text($input->post->contactname);
$contactname = $sanitizer->text($input->post->contactname);
$email = $sanitizer->email($input->post->email);
$comments = $sanitizer->textarea($input->post->comments);

// Match up the sanitized inputs we just got with the template fields
$np->of(false);
$np->title = $contactname;
$np->name = $contactname;
$np->contactname = $contactname;
$np->email = $email;
$np->comments = $comments;

// Save/create the page
$np->save();							    										    		

} ?>

This works great! After submitting the form, we go to the "Success" page, and new submissions show up in the ProcessWire page tree right away. Excellent!

Now I need to add a photo field. I altered the above form so it looks like this:

<form action="/customer-service/contact/contact-success/" method="post" enctype="multipart/form-data">
<p><label for="contactname">Name:</label></p>
<p><input type="text" name="contactname"></p>
<p><label for="email">E-Mail:</label></p>
<p><input type="email" name="email"></p>
<p><label for="comments">Comments:</label></p>
<p><textarea name="comments" cols="25" rows="6"></textarea></p>
<p>Click the "Select Files" button below to upload your photo.</p>
<input type="file" name="contact_photo" />
<button type="submit">Submit</button>
</form>

And here's the updated "contact-success" page:

<?php // First, confirm that a submission has been made
if($input->post->contactname) {

// Set a temporary upload location where the submitted files are stored during form processing
$upload_path = $config->paths->assets . "files/contact_files/";

// New wire upload
$contact_photo = new WireUpload('contact_photo'); // References the name of the field in the HTML form that uploads the photo
$contact_photo->setMaxFiles(5);
$contact_photo->setOverwrite(false);
$contact_photo->setDestinationPath($upload_path);
$contact_photo->setValidExtensions(array('jpg', 'jpeg', 'png', 'gif'));

// execute upload and check for errors
$files = $contact_photo->execute();

// Run a count($files) test to make sure there are actually files; if so, proceed; if not, generate getErrors()
if(!count($files)) {
$contact_photo->error("Sorry, but you need to add a photo!");
return false;
}

// Do an initial save in the ProcessWire page tree; set the necessary information (template, parent, title, and name)
$np = new Page(); // create new page object
$np->template = $templates->get("contact_submission"); // set the template that applies to pages created from form submissions
$np->parent = $pages->get("/customer-service/contact-us/contact-submission-listing/"); // set the parent for the page being created here

// Send all the form's $_POST submissions through ProcessWire's sanitization and/or map to a variable with the same name as the template fields we'll be populating
$np->title = $sanitizer->text($input->post->contactname);
$np->name = $np->title;
$np->contactname = $sanitizer->text($input->post->contactname);
$np->email = $sanitizer->email($input->post->email);
$np->comments = $sanitizer->textarea($input->post->comments);
$np->save();

// Run photo upload
foreach($files as $filename) {
$pathname = $upload_path . $filename;
$np->contact_photo->add($pathname);
$np->message("Added file: $filename");
unlink($pathname);
}

// Save page again
$np->save(); ?>

<p>Thank you for your contact information.</p>

<?php return true;
}

else { ?>
<p> Sorry, your photo upload was not successful...</P>
<?php } ?>


Replace the field references with your own field names, make sure to change the various paths to match yours, and change the various messages to be what you need for your project.

Read the entire discussion to see how we worked through getting to the solution shown above.

Thanks,
Matthew

  • Like 9

Share this post


Link to post
Share on other sites

You have written contest_photo instead of contact_photo at the beginning. Not sure if this solves your issue. Sorry on mobile...

  • Like 2

Share this post


Link to post
Share on other sites

Hey Wanze,

Yes, that shows you have a good eye for sure!

Unfortunately, that was just an error from me transferring the text to the forum... I edited the forms in my first post and fixed that word.

In other words, it still doesn't work.

Any other ideas?

Thanks,

Matthew

Share this post


Link to post
Share on other sites

$contact_photo->setDestinationPath($upload_path);

I could be overlooking it (late here), but I don't see $upload_path defined anywhere.

Share this post


Link to post
Share on other sites

Hey renobird,

Late here too, but difficult to sleep when working on a ProcessWire site!

You are correct... I was missing that piece!  Definitely, that should be there.

I added it, and holding my breath I tried the form again.  But it still doesn't work.  Same result: no errors, and we see the "success" page, but the new page does not appear in the page tree.

Other suggestions or ideas?

Thanks,

Matthew

PS: I've edited my post above to keep track of changes.  That way, when this discussion is done, there will be a complete example for others to reference.

  • Like 1

Share this post


Link to post
Share on other sites
$upload_path = "/site/assets/files/temp/";

I think the path is not correct, it's more an "url".

The complete path usually starts with the folder your site's in (public_html/www).

Try this:

$upload_path = $config->paths->assets . 'files/temp/';

And have you debug mode turned on to see any errors?

Cheers

Share this post


Link to post
Share on other sites

Greetings,

To renobird: Yes, I am basing my work on Soma's code.  But because I have more fields than just a file upload, I have to re-work the original code.  In my code above, you can see that what I have looks like Soma's code, but with the other fields included.

$upload_path = "/site/assets/files/temp/";

I think the path is not correct, it's more an "url".

The complete path usually starts with the folder your site's in (public_html/www).

Try this:

$upload_path = $config->paths->assets . 'files/temp/';

And have you debug mode turned on to see any errors?

I fixed the path reference (updated my post above).  But still no luck.  The page is not being created.  And no errors are being reported.

Based on a comment from Ryan in a related discussion, I wonder if I am simply saving the page at the wrong stage?

Here's the related discussion: http://processwire.com/talk/topic/2997-uploading-a-new-photo-to-a-member-profile-avatar-field/#entry30621

I really want to have a full, public example of how to put file uploads in a form with other fields, so I am hoping this discussion can become a live working out of the details.

Thanks,

Matthew

Share this post


Link to post
Share on other sites

I have a working example - doing essentially what you are after. I'll compare to yours when I get back to the office (on mobile now) and see if can spot the issue.

Share this post


Link to post
Share on other sites

Not the ideal way to debug like this code in a forum. Nothing against anyone personally. Now the code lost intendation and no way to go through it.

Please post code to some snippet site like Gist or bitbucket.

In general, we should provide some more practice and best ways HOW TO DEBUG YOUR CODE. Good coding is learning how to debug. There's simple steps you can go through to test all vars and parts of code to see where the problem lays. Sometimes it's simple sometimes it can get hard, as you when you don't have errors.

Ok.

What I spotted too is the last bit, where is the $u->getErrors() coming from?

Also as you say the page isn't created at all? I would start there and get that right first and work you way up.

Thanks,

Soma

  • Like 2

Share this post


Link to post
Share on other sites

Hey Soma,

I know what you're saying, but I wanted to use the forum as a way to work out what the general idea should be.  Ideally, it would be great to have things like this all in Github, but I think we often need to use a forum this way.  It's a more direct way -- in the end -- to have code that everyone here can use for their sites.

I think having a full working example of a form with various fields as well as file-uploads would be great for the forum.

Good catch on the $u->getErrors()... Yes, that should be $contest_photo->getErrors()

However... It still doesn't work.

I'm sure we'll solve this!

Thanks,

Matthew

Share this post


Link to post
Share on other sites

Matthew,

have you debug mode enabled to see PHP errors/warnings?

Also can you do a print_r($_FILES); to check if the PHP $_FILES array isn't empty?

Maybe the file isn't sent at all to the server.

Share this post


Link to post
Share on other sites

I also know what you're saying, but I have to strongly disagree. I could've write this in any other similar thread.

The problem is the code posted in a wysiwyg is "horrible" to read and even copy it.

Then after this is working (as with 1000 other code here in the forum), there comes the next who takes this code but need another feature.. then the game start again with same story same code but different bugs issues.

What I would like to do is helping how to code and debug, not have 1 million snippets that are sometimes very bad coded and not complete best practice or not even working spooking around the forum. It already happened many times and it will get worse by time. I know what you're wanting to do and I understand, but in the long run what we are practicing here since 2 years is very bad for newcomers. We better this energy and time to help with little tutorials and snippets that are good to start with in a dedicated site/place.

  • Like 2

Share this post


Link to post
Share on other sites

Hey Soma,

I get you!  I really do...  As someone who is attempting to document how to do things, it is difficult when there are pieces here and there for larger routines.  For example, with file-uploads and front-end forms in general, there are isolated chunks throughout the forum, and putting it all together as a single file can be difficult.  My goal with this discussion is to consolidate a whole routine in one coherent file.

For sure, your points about better ways to discuss such code is worthy of a larger discussion!  And yet, forums like this often have bits of code (many times, they are more than just "bits").  In fact, if you remove the code from a forum like this, what's left to discuss?

If I were to move this to GitHub, would it be as valuable to the ProcessWire community?

Thanks,

Matthew

Share this post


Link to post
Share on other sites

I didn't say move this discussion to github, but the snippet maybe?

Share this post


Link to post
Share on other sites

Im saying gist.github.com not github itself :-)

Share this post


Link to post
Share on other sites

Much better... and just now I spoted (starting from top)  an error

if ($input->get->contactname){

Your form is method post and not get!

So you would have easily spoted this already if you add an pseudo echo "Hello" after the if() to see it gets to after the check. As I said it's really simple if you go step by step and output vars to test and see if it really get's there. 

Share this post


Link to post
Share on other sites

Hi Soma,

I used "GET" for the original form (the one with no file uploads).  I then switched to "POST" for the one with file uploads.  But to make it easier, I will switch all of them to the "POST" method.

Thanks,

Matthew

Share this post


Link to post
Share on other sites

Dont want to be mean but have to say that I already built multiple working front end form examples that work. The one Reno linked would be what you do. So this is not so much about making a working example but to help you debug your code you messed up. :-P #hides behind stone#

Of course yours has some more fields, but there's no validation happen on those, which I think should be the lesson here. To make it easy on this subject I would suggest looking at my other front end forms using PW forms and field that will make it more streamlined and easy. Its also in my gists examples.

Share this post


Link to post
Share on other sites

Hey Soma,

Yes, I admit the code I posted has problems.  I just can't seem to pinpoint the problem because building these kinds of forms gets into a lot of PW-specific steps.  I haven't seen any examples to follow for forms with image-upload fields along with "regular" fields.

I actually based my code above on your very helpful gist for uploading images to newly created pages (https://gist.github.com/somatonic/4150974).  But somewhere in the process of mixing in "regular" fields with the image upload requirements, something is off.

I realize I must be doing something wrong, but I can't tell what.

I'm hoping in the end we'll have a working example of how to do this that can be beneficial to others, since I know other need (or will need) to do this.

Thanks,

Matthew

Share this post


Link to post
Share on other sites

Hi Matthew,

have you tried my debugging steps? =)

Matthew,


have you debug mode enabled to see PHP errors/warnings?


Also can you do a print_r($_FILES); to check if the PHP $_FILES array isn't empty?


Maybe the file isn't sent at all to the server at all.

Share this post


Link to post
Share on other sites

Greetings Wanze,

Sorry, I meant to respond to that earlier.

Yes, I am seeing a result from that.  For example, I uploaded a photo called "cloudphoto.jpg.  Here's the message I'm seeing:

Array ( [contact_photo] => Array ( [name] => cloudphoto.jpg [type] => image/jpeg [tmp_name] => /tmp/phpdRhJLN [error] => 0 [size] => 1864092 ) ) 

Thanks,

Matthew

Share this post


Link to post
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

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By louisstephens
      So I have a form, once completed, will create new pages. All in all, this is eazy-peezy for me now. I guess I need a bit of guidance on how to actually structure the rest of my code.  I thought I could just write a function (_func.php) and pass the fields to the function and let it do its' thing. However, I am kinda hitting a road block when I do it this way. 
      I currently am passing first name, last name, city, state (options field), and making pages based on the first/last names. I guess where I run into some issues is I am trying to check to see if the "page" already exists, and if it does, throw out an error:
      In the home template:
      if(isset( $_POST['submit'])) { $firstName =Trim (stripslashes($_POST['firstname'])); $lastName = Trim(stripslashes($_POST['lastname'])); $fullName = $firstName . $lastName; $city = Trim(stripslashes($_POST['city'])); $state = Trim(stripslashes($_POST['state'])); $lowerCaseName = strtolower($fullName); $people = $pages->find("template=person"); foreach ($people as $person) { $checkFirstName = $person->first_name; $checkLastName = $person->last_name; $checkFullName = $checkFirstName . $checkLastName; if ($checkFullName === $lowerCaseName) { echo "<p>" . "This person has already created a page. Please choose a different name." . "</p>"; } else { echo "hey"; processNewPerson(need_to_pass_person_details_to_function); } } // end foreach In _func.php:
      function processNewPerson($list) { $u = new Page(); $u->template = "person"; $u->parent = wire('pages')->get("/people/"); $u->title = ; $u->first_name = ; $u->last_name = ; $u->state = ; $u->city = ; $u->save(); $u->setOutputFormatting(false); } I am a little unsure of how to actually pass all the information to the template, as well as if this is even the best approach to do this. Would it make more sense to do this in a class, or keep it the way it is?
    • By louisstephens
      I have done a bit of searching, but I can not seem to find an actual answer. I have a list of services as child pages under "Services". I can output the services just fine, but I cant wrap my head around how to group them "alphabetically" like:
      Services A - Service "A" 1 - Service "A" 2 - Service "A" 3 B - Service "B" 1 - Service "B" 2 - Service "B" 3 C - Service "C" 1 - Service "C" 2 - Service "C" 3 Has anyone achieved this type of functionality before?
    • By quickjeff
      Hey guys, 
      Looking for another dev to help on multiple processwire projects as well as some other PHP apps. 
      First project is a directory style site with lots of components to come, including stripe implementation  and other fun stuff.
      Need someone ASAP to help out. 
       
      Thanks.
    • By louisstephens
      I am wondering, how do you pass a variable into wire('page')->get() inside a function? I have been looking through the forums, but unfortunately I have not found the answer yet.
      My current set up is:
      function generateNewPages($parentPageName) { $p = new Page(); $p->template = "parent"; $p->parent = wire('pages')->get('/home/'); $p->name = $parentPageName; $p->title = $parentPageName; $p->of(false); $p->save(); $p2 = new Page(); $p2->template = "child"; $p2->parent = wire('pages')->get('$parentPageName'); $p2->name = "child"; $p2->title = "Child Page"; $p2->of(false); $p2->save(); } When I try to run it by passing in a title like generateNewPages('Demo');, "Demo" is created, but when it gets to the child page I get:
      Unknown Selector operator: '$' -- was your selector value properly escaped?
      Is there a way to pass the $parentPageName to "wire('pages')->get('$parentPageName')" ?
    • By thomasaull
      I recently started to build Vue SPAs with ProcessWire as the backend, connected with a REST API. Thanks to code and the help of @LostKobrakai (How to use FastRoute with ProcessWire) and @clsource (REST-Helper) I got it up and running pretty quickly and now have put all of it in a site profile for others to use. It includes the REST API with routing for different endpoints, JWT Auth and a simple Vue SPA which shows the process of logging in a user (nevertheless, you don't have to use the Vue part, the API will work on it's own).
      Check it out here: https://github.com/thomasaull/RestApiProfile
      I'm pretty sure, it's not the perfect or most sophisticsted solution, but it gets the job done for me…
      Feedback or Improvements are very welcome
      Update: This site profile is a module now: https://github.com/thomasaull/RestApi