Jump to content

Create Pages (with File-Upload Field) via API


MatthewSchenker

Recommended Posts

Ok I copied your code over and starting taking closer look and test.

There was so many faults and things to change, I can't name them all.

First off, I changed the input submit and changed to if($input->post->submit){..., since contactname in the form isn't required and just personal preference and a good practice.

I made sure debug mode in true in /site/config.php.

First submit, BANG!!! error:

Fatal error: Exception: Can't save page 0: /about/: It has an empty 'name' field (in /Applications/XAMPP/xamppfiles/htdocs/pwprofile/wire/core/Pages.php line 514) #0 [internal function]: Pages->___save(Object(Page)) #1…..

So it can't create and save the page because you don't assign a title nor a "name" to the page before saving. I moved the code around to add those.

--

Then there was a wierd line:

$contact_photo = $input->post->contact_photo;

and later:

$np->contact_photo = $contact_photo; // being the file POST

Maybe a left off when trying something?

--

Then after that, the assignment of the upload to the page image:

foreach($files as $filename){
    $uploadpage->contact_photo = $upload_path . $filename;
}

$uploadpage?

should be

$np

--

At the end of when errors found you still have the contest_photo instead of contact_photo.

// get the errors
foreach($contest_photo->getErrors() as $error) $message .= "<p class='error'>$error</p>";

--

All in all nothing special and nothing wrong with the PW code in there, except the page name left out. But with debug mode on an easy spot.

I created a gist with a working version of your code here: https://gist.github.com/somatonic/5207537

(I changed template and parent to my default install but nothing to worry about. I also commented out a bunch not needed to make it work.)

Still with the validation of the form fields open to do, currently you only validate the upload and show errors.

  • Like 2
Link to comment
Share on other sites

Greetings Soma,

Thanks for taking a close look at the code!  I admit there were an unusual number of un-spotted bits in there.

I certainly don't think there's anything wrong with ProcessWire code.  It's just that this operation does require a bit more code than other "simple" operations in ProcessWire, so there's more potential for errors.

Still, I know this is something that a lot of people need to do with ProcessWire, so I'm glad it was out there and bashed around until it worked.

I'll grad your gist and test it out.

Thanks again!

Matthew

Link to comment
Share on other sites

Greetings,

Just following up...

I made all the changes suggested above, but still no luck.  The form goes through with no errors, but nothing gets saved in the page tree.

Soma: when you ran this with the code you described, were you able to get pages saved with images in the page tree?

Continuing to investigate.

Thanks,

Matthew

Link to comment
Share on other sites

Greetings,

I have used front-end forms to create pages without issues several times.  In my original post of this discussion, I showed the form with all the "basic" fields, and it works great.  it's only when I add the file-upload that things get thrown off.

If you have that working example you mentioned earlier, I'd be curious to see it.

I'm sure when this is done, it will all be obvious what the problem was!

Thanks,

Matthew

Link to comment
Share on other sites

Greetings,

Just following up...

I made all the changes suggested above, but still no luck.  The form goes through with no errors, but nothing gets saved in the page tree.

Soma: when you ran this with the code you described, were you able to get pages saved with images in the page tree?

Continuing to investigate.

Thanks,

Matthew

Yes it works. Page is added and image is saved to page. Working example is here: https://gist.github.com/somatonic/5207537

  • Like 1
Link to comment
Share on other sites

Greetings,

Thanks Soma for all your help.

I'm starting to wonder if there's something off in the server environment.  I have now tested literally the same form as the one you posted and it does not work.  I'm not sure what else would stop it from working.

Thanks,

Matthew

Link to comment
Share on other sites

Matthew,

Have you checked the template's family settings? 

Can this pages using this template have children?

Can this template be used for new pages?

I would imagine if either were set to "no" that you would get an error.

Link to comment
Share on other sites

Greetings,

Thanks renobird for helping!

There are no errors.  And pages get created successfully when the form only includes "regular" fields, without image uploads.  It's only when I add the image upload field that everything just stops working.

So it can't be an issue with the parent/children settings.

Still stumped,

Matthew

Link to comment
Share on other sites

Just to clarify,

Using the exact same code (just commenting out parts):

Works:

- When creating a new page that just has fields

- When you just upload an image (no additional fields)

Breaks:

- When you combine the 2

Edit

Also:

- Debug mode is set to true? (/site/config.php)

- no errors?

Link to comment
Share on other sites

Greetings,

Thank you for analyzing this with me.  I really appreciate the help.

Interesting... I am seeing no file in the temp folder.

Write permissions are as open as possible.

One off-the-wall idea: does ProcessWire prevent non-logged-in users from submitting files?  This form is open to anyone, even if not logged in.

Just a thought.

Thanks,

Matthew

Link to comment
Share on other sites

Greetings,

Thanks WillyC for helping...

I posted two versions of the form: (1) without file uploads and (2) with file uploads. The form with file uploads has the correct enctype.

So... Still searching for an answer.

If anyone wants to post forms where they have file uploads working along with other fields, plase feel free to do so!

Thanks,

Matthew

Link to comment
Share on other sites

Matthew, the primary problem I can see is this line:

if(!$contact_photo->getErrors()) {

The issue there is that getErrors() always returns an array, which always resolves to "true" in your if statement. In order for it to work properly, you would need to count() the return value of getErrors() rather than typecast it to a boolean like you are doing now. However, I don't think that getErrors() is a good way to check for success with WireUpload, because errors could occur while files were still uploaded. So it's better for your code to ask the question "were any files uploaded?" rather than "did any errors occur?". Then worry more about errors if no files were uploaded. 

There were also several other minor issues or concerns, but I think the issue mentioned above is the primary reason it wasn't working. I've attempted to re-factor your code and resolve all the issues/concerns that I would have had with it. This is written in the browser, so there may still be typos, but hopefully the intentions are clear:

<?php  

/**
 * Process the contact form
 *
 * Populate's ProcessWire's $notices API var with errors and messages
 *
 * @return bool Returns true on success, false on failure. 
 *
 */ 
function processContactForm() {

  $input = wire('input'); 
  $sanitizer = wire('sanitizer'); 
  
  // Set a temporary upload folder where the files are stored during form processing
  // RC: precede temp dir with a period to ensure it remains non-web accessible
  $upload_path = $config->paths->assets . "files/.temp/";
  
  // RC: create temp path if it isn't there already
  if(!is_dir($upload_path)) {
    if(!wireMkdir($upload_path)) throw new WireException("No upload path"); 
  }
  
  // New wire upload
  $contact_photo = new WireUpload('contact_photo'); // References name of HTML form field
  $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();
      
  // RC: use count($files) as your decision whether to proceed not not, rather than getErrors()  
  if(!count($files)) {
    $contact_photo->error("No files received, so not creating page.");
    return false; 
  }
  
  // Save in the ProcessWire page tree; map submission to the template fields
  $np = new Page(); // create new page object
  $np->template = "contact_submission"; 
  $np->parent = "/customer-service/contact-us/contact-submission-listing/"; 

  // RC: for safety, only add user uploaded files to an unpublished page, for later approval
  // RC: also ensure that using v2.3+, and $config->pagefileSecure=true; in your /site/config.php
  $np->addStatus(Page::statusUnpublished);
                    
  // Send all form submissions through ProcessWire sanitization or just map a variable
  // RC: No need to have intermediate variables, I removed them--populating the $np page instead
  $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); // RC: assuming this is a textarea fieldtype
  $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();
  
  return true; 
}


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

  if(processContactForm()) {
    echo "<h2>Your page was saved!</h2>";
  } else {
    echo "<h2>Sorry things didn't work out!</h2>";
  }

  foreach($notices as $notice) {
    $class = $notice instanceof NoticeError ? "error" : "message";
    echo "<p class='$class'>$notice->text</p>";    
  }

} else {
  // display contact form
}                            
  • Like 7
Link to comment
Share on other sites

Thanks Ryan for the heads up, there's some nice examples for advanced coders.

Strange. Now, I also would have thought that with !$u->getErrors() it wouldn't work, but it still does! I went and did many tests after you posted this and I can't see any difference and it all works as it should. The code works with both and I haven't looked into it further (core). I wish to be proven wrong but it really does (at least for me here) :/

I think you got a valid point with checking for if there's files really uploaded at all... However this wouldn't work if image upload would be not required in your form. Also the example you posted is also not showing how you could make required fields and inline error/repopulating fields, prevent CSRF and double posts. It's still a great example but as said, not a "complete" example that can be used in a public front-end form. May or may not suiteable for people not knowing what they're doing and only copy paste.

What I also think could be the problem on Matthews side is that the PHP upload and post max size isn't enough to upload the file. I also tested this and it seems to fails silently with no errors and just shows the form again. Maybe there's a way to get around it, but thought it might be an issue since there's no error thrown as it's apache upload interrupting?

---

It can get somehow complex to add all checks and validation to a form server side, and the following post example is also just for showing what all there's involved to make a pure php form (using some PW internals).

Yet another example

I've spent couple hours creating a form upload example

 - with files (images) upload to a page file field
 - adds new page on the fly and adds uploaded images
 - prevents CRSF attacks, this also prevents double post by refresh page after submit
 - has required fields with error messages inline
 - repopulates form field in case an error happened or a required field was not filled in
 - sanitizing and saving values to a page
 - jquery example with disabled submit button on form submit

The gist repo can be seen here https://gist.github.com/somatonic/5233338

You can configure some variables and array's at the top and add remove fields as you wish to the html form markup.

Like this:

// --- Some default variables ---
$success_message = "<p class='message'>Thanks for your message!</p>";

// --- All form fields as nested array ---
// using html form field name => template field nam, from the page you're going to create
$form_fields = array(
  'fullname' => array('type' => 'text', 'value' => '', 'required' => true),
  'email' => array('type' => 'email', 'value' => '', 'required' => true),
  'message' => array('type' => 'textarea', 'value' => '', 'required' => true),
  'newsletter_subscribe' => array('type' => 'checkbox', 'value' => 0, 'required' => false),
  'images' => array('type' => 'file', 'required' => true)
);

// --- WireUpload settings ---
$upload_path = $config->paths->assets . "files/.tmp_uploads/"; // tmp upload folder
$file_extensions = array('jpg', 'jpeg', 'gif', 'png');
$max_files = 3;
$max_upload_size = 1*1024*1024; // make sure PHP's upload and post max size is also set to a reasonable size
$overwrite = false;

// --- Page creation settings ---
$template = "upload-entry"; // the template used to create the page
$parent = $pages->get("/uploads/");
$file_field = "images";
$page_fields = array('fullname','email','message','newsletter_subscribe');

// $page_fields = define the fields (except file) you want to save value to a page
// this is for the form process to populate page fields.
// Your page template must have the same field names existent

// ------------------------------ FORM Processing ---------------------------------------

include("./form-process.php");
 
To set this up.
 
1. Create a template upload-entry used to save the form submissions. With all the fields you'll have in the form, and name them the same as in the $form_fields
 

2. Create a form-upload.php template used to hold the form config and markup code:

(create a template in PW with this name and the page the form should be rendered)

https://gist.github.com/somatonic/5233338#file-form-upload-php

3. Create a form-process.php file in the templates folder with the processing code

(This is included in the template file above after the configuration part)

https://gist.github.com/somatonic/5233338#file-form-process-php

There's a basic styling CSS:

https://gist.github.com/somatonic/5233338#file-form-css

And the jQuery snippet used to prevent double posting when double clicking the submit.

https://gist.github.com/somatonic/5233338#file-form-js

This is some screen showing the form:

post-100-0-55537400-1364160069_thumb.png

post-100-0-73960600-1364160102_thumb.png

  • Like 7
Link to comment
Share on other sites

Greetings,

Thank you extremely Ryan!!! And thank you Soma for patiently analyzing all the details.

Of course, file uploads in any system are complex actions and should not be taken lightly.

Just wondering: would it be better to access the ProcessWire "files" module (the same one that controls file uploads in the admin form)? Not sure if this is even possible...

Again, my goal (besides using it in my projects) is to capture this routine and document it so others can use it. Together with "regular" fields, I think this moves us closer to a co,pkete picture of front-end page creation/management.

Thanks,

Matthew

Link to comment
Share on other sites

Thank you extremely Ryan!!! And thank you Soma for patiently analyzing all the details.

Of course, file uploads in any system are complex actions and should not be taken lightly.

Just wondering: would it be better to access the ProcessWire "files" module (the same one that controls file uploads in the admin form)? Not sure if this is even possible...

So you got scared by that long complex basic form script? :)

Creating form using ProcessWire as a framework and it's form and inputfield capabilities.

Yes this would be possible and it's the way I use to make forms in front and backend whenever possible.

Then we are finally here http://processwire.com/talk/topic/2089-create-simple-forms-using-api/ where I explained how to do it along with some examples. 

Yet another form example

Since uploading files is a little special I went ahead and tried using the InputfieldFile to upload images to the temp folder and finally create a page and add those images same way as in the other examples. Then after success delete the temp files.

Now this example does all the previous "pure" php example does, just using PW forms API.

- required fields

- CSRF preventing

- inline error

Form example snippet: https://gist.github.com/somatonic/5236008

There's also a CSS file example to style the form, and for example hide the "Drag files here...".

This technique is the most simple to make front-end forms, and doing it this way allows you do add hooks to the form in case you need a custom validation or anything directly from within the template code.

For example, the $form can be used to attach any hooks to the form:

/* form hook example */
function hookEmail($event){
     $file = $event->return;
     echo "email value: ". $file->value;
}
$form->get("email")->addHookAfter("processInput", null, 'hookEmail');

---

Little change to InputfieldFile.module to throw error if maxFiles is reached

It works all well, but the maxFiles setting doesn't throw an error message when you choose more images than allowed. Since the InputfieldFile module is usually ajax based, it throws error via ajax and thus not in $form->getErrors() at all. It just proceeds and uploads only the max files and skips the rest. This could be changed in the module to also throw error on the field when no ajax is used. For this to maxFiles error to work I changed the /wire/modules/Inputfield/InputfieldFile.module a little:

https://github.com/ryancramerdesign/ProcessWire/blob/master/wire/modules/Inputfield/InputfieldFile/InputfieldFile.module#L288

From:

if($this->config->ajax) foreach($ul->getErrors() as $error) {
    $this->ajaxResponse(true, $error);
}

To:

if($this->config->ajax) foreach($ul->getErrors() as $error) {
    $this->ajaxResponse(true, $error);
} else {
    foreach($ul->getErrors() as $error) {
        $this->error($error);
    }
}

Maybe if Ryan can implement those to the Inputfieldfile to allow for front-end error to be shown, it would be great.

  • Like 4
Link to comment
Share on other sites

Greetings,

I'm not afraid of long forms and code. But of course I am interested in the most concise code possible. Also, I'm thinking from the point of view of documentation and putting it all in the context of general "front-end form" tutorials for all users.

Soma: I read your post on using the inputfield modules. It was on my mind when I started this discussion. The way I am documenting this, I want to show two general procedures for front-end forms: using modules or using "plain" HTML/PHP.

But you also wrote this: "Also file uploads isn't there, but maybe at some point there will be more options."

That's what made me wonder.

The other thing is, as I use ProcessWire, I simultaneously use Laravel. I am constantly impressed with how ProcessWire provides methods that do what a full framework does. Building forms, and all that go with that, is a crucial piece. So I'm looking at the way Laravel does file uploads as I write all this and looking for the equivalent methods in ProcessWire.

As long as we keep a good goal in mind, we'll be all right.

Thanks,

Matthew

Link to comment
Share on other sites

Ryan and Soma,

I would like to extend a huge thanks to both of you. You guys (along with many others) consistently take the time to not only decipher and debug code, but go well beyond that by trying to figure out what the person is "actually" trying to do and provide them with a revised/better solution—I applaud you both.

Soma, your examples above are amazingly thorough — I really appreciate the time you devoted to create them, and your willingness to share. 

Cheers.

  • Like 2
Link to comment
Share on other sites

Greetings,
I agree with renobird. This is a pretty complex area, so it's great to have people with the expertise and patience to work through it all.

I'm hoping that this discussion can be a source for documenting these methods for people at all levels of development expertise.

My goal is to be able to present a clear set of directions and explanations for the whole front-end form effort. With this discussion, I have some great material that I can put together and present down the line.

Thanks,
Matthew

  • Like 1
Link to comment
Share on other sites

I'd seen the original post a while ago, but at the time I was new to ProcessWire—so it didn't really sink in.

I just used that method to rebuild a frontend system that allows faculty to upload and edit course syllabi.

Took less than 10 minutes to get it up and running, and maybe another 20 to troubleshoot a few things unique to my needs.

The API was already allowing me to run with forms, now I feel like I have a jetpack.

:)

  • Like 1
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
×
×
  • Create New...