Jump to content

How do I save contact form submissions as pages?


Jim Yost
 Share

Recommended Posts

Hi Ryan,

How do you recommend I store data in ProcessWire? To keep it simple for example, I'd like to collect results from a Contact Form and store them in PW.

If I had to do it, I would create a hidden "Contact Form Results" page. Whenever someone submits the contact form, I would generate a new child page to the "Contact Form Results" Page with the information I've collected. I would then create a template with the fields I need so they are browse-able from the admin interface. Does this seem like a good way to collect the data? It seems like once it's collected, it would be simple to use PW's API to create reports and do anything else I needed to with that data.

-Jim

Link to comment
Share on other sites

How do you recommend I store data in ProcessWire? To keep it simple for example, I'd like to collect results from a Contact Form and store them in PW. If I had to do it, I would create a hidden "Contact Form Results" page. Whenever someone submits the contact form, I would generate a new child page to the "Contact Form Results" Page with the information I've collected. I would then create a template with the fields I need so they are browse-able from the admin interface. Does this seem like a good way to collect the data? It seems like once it's collected, it would be simple to use PW's API to create reports and do anything else I needed to with that data.

I think your idea is a good way to go. Setting up a page structure should be a fine place to maintain submitted forms. And it should be fairly easy to do. Just be aware that in your templates, you have full unrestricted access to the API. As a result, you shouldn't include anything like: where the page is saved (i.e. parent page or template) in the data that is user-submitted, unless it's well validated. Ideally, the data that gets saved should go exclusively in custom fields that you've created, rather than any built-in ProcessWire fields.

For all custom fields that contain text, make sure you enable the entities text formatting filter. You'll see the text formatting filters in the field settings when you create it. I also recommend performing some basic validation/sanitization of the data before adding it to the page's fields. Granted there is built-in validation, but I always recommend sanitizing/validating data when it's first accessed, even if it will be done again later automatically. 

Lastly, make sure the parent page where you'll be storing these contact submissions has the guest role removed (via the 'settings' tab on the page). You don't want these submissions being accessible to anyone but you.

Here's a basic example to get started. This assumes that you are storing submissions in a child page of /form-results/ and that you have a template called "contact_submission" that contains the fields we are populating. We're populating the name, title (with the fullname field), email and comments fields into a page, then saving it.

/site/templates/contact-form.inc

<form action='/contact/' method='post'>
    <p>
    <label>Full Name</label>
    <input type='text' name='fullname' />
    </p>

    <p>
    <label>E-Mail</label>
    <input type='email' name='email' />
    </p>

    <p>
    <label>Comments</label>
    <textarea name='comments'></textarea>
    </p>

    <p>
    <input type='submit' name='submit' value='Submit' />
    </p>
</form>

/site/templates/contact.php

<?php

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

    // create a new Page instance
    $p = new Page();

    // set the template and parent (required)
    $p->template = $templates->get("contact_submission");
    $p->parent = $pages->get("/form-results/");

    // populate the page's fields with sanitized data
    // the page will sanitize it's own data, but this way no assumptions are made
    $p->title = $sanitizer->text($input->post->fullname); 
    $p->email = $sanitizer->email($input->post->email); 
    $p->comments = $sanitizer->textarea($input->post->comments); 

    // PW2 requires a unique name field for pages with the same parent
    // make the name unique by combining the current timestamp with title
    $p->name = $sanitizer->pageName(time() . $p->title);

    if($p->fullname && $p->email) {
        // our required fields are populated, so save the page
        // you might want to email it here too
        $p->save(); 
        echo "<h2>Thank you, your submission has been received.</h2>";

    } else {
        // they missed a required field
        echo "<p class='error'>One or more required fields was missing.</p>";  
        include("./contact-form.inc");     
    }

} else {
    // no form submitted, so render the form
    include("./contact-form.inc"); 
}

Link to comment
Share on other sites

  • 4 weeks later...

I'm trying to make almost the same form but i need the parent page is not the same for all submissions but is choosen by user in the form.

I created a field in my form with the name "category" and populated with some pages i created in admin.

in the php code you wrote at this line:

$p->parent = $pages->get("/form-results/");

i tried to make this:

$p->parent = $input->post->category;

With no luck... ???

There is a way to obtain this?

Link to comment
Share on other sites

The reason your snippet doesn't work is because you are trying to set the page's parent to be a string. You actually need to set the parent as a Page object, so you'd need to retrieve the page and then set it to be the parent.

However, even if you did that correctly, you need to be concerned about the security of this. Make sure you define what are the allowed/valid parents. You don't want someone manipulating the posted values and then adding pages in the wrong place. :) I would suggest mapping your valid parents to numbers, which are simpler to validate from a post. You should never put user-submitted values into a selector unless you've validated/sanitized them first. Or better yet, map them like we do here:

<?php

$categories = array(
   1 => '/form-results/',
   2 => '/categories/something/', 
   3 => '/categories/something-else/', 
   );

if($input->post->category) {
   // get the parent page (category) and save the form
   $n = (int) $input->post->category;
   if(isset($categories[$n])) $category = $categories[$n]; 
        else $category = $categories[1]; // default
   $parent = $pages->get($category); 
   // now that you have the parent, you can save the form like in previous examples

} else {
   // draw the form and category selection
   echo "<select name='category'>";
   foreach($categories as $n => $category) {
       echo "<option value='$n'>$category</option>";
   }
   echo "</select>";
   // print the rest of the form
}
Link to comment
Share on other sites

  • 4 weeks later...

Are you saving images using ProcessWire's image field, or your own? I would recommend using your own, just because ProcessWire's file/image fieldtypes were designed specific to the PW2 admin. Granted it might work well, but it was designed for administrative use rather than anonymous use, and I'd like to certify it for that use before I'd feel 100% comfortable with using it in that context. I'm always paranoid with anything that involves uploading files to a server.

I've always felt that a best practice is not to allow anonymous users to upload files to the server. :) But in lieu of that practice, here's a few best practices I follow, though most are probably obvious.

1. Make sure that anything they upload matches known file types/extensions. Ensure that your extension check can't be mislead, as people will try to upload filenames with two extensions, nulls, dots, slashes, misleading UTF8 characters and other strategies to disguise the real extension. 

2. Make sure that the uploaded files go into a non-web accessible location until confirmed that they are valid. If possible, have a real person involved to determine if they are valid. Depending on the context, this may not be possible. But if it is, then do it.

3. When files are ultimately placed in their web accessible location, make sure that no files can be executed in that location. This is just an extra safety measure. For instance, see ProcessWire's htaccess file to see how it disallows some file types from being requested in certain locations. With file uploads, I might only allow files of type GIF, JPG, JPEG, or PNG to be requested from the target dir (assuming the file upload were for images).

4. Convert your filenames to your own format. If there is some id number associated with the upload, use that number for the basename. If you want to retain the user's original filename, then convert it to ascii before placing in the target location. ProcessWire's $sanitizer->pageName() function is a good way to do that.

5. Check your PHP max_upload_size and max_post_size to be sure they are set consistently with the max file size you want to allow. Or, check the file size after upload, and delete anything bigger than what you allow. The max upload size that you can set in the POST vars is pointless from a security standpoint.

6. Be prepared for a million copies of c99.php disguised as other types of files/images. :)

Link to comment
Share on other sites

Thanks Ryan. Very good post and pretty comprehensive answer again!

I was thinking about using PW's image field, but not sure about it anymore. It would be just perfect in this use case. I probably end up doing both: using custom upload, doing security checks, renaming & saving to non-web accessible location and after that attaching that to PW's image field.

$p->images->add("/path/to/image.jpg"); 

If I have image on server and on non-web accessible location, can I then use that add method? Does it accept paths also? Or do I need to transfer it to web accessible location first?

Link to comment
Share on other sites

It should accept paths -- that's the way I usually use it.

You may be fine to use the PW InputfieldImage for your file uploads. But what I just wanted to communicate is that it was designed for a different context (an administrative context). So if you do use it, double check and understand what it's doing ... making sure that it's providing the security that you want relative to your server environment. If you see anything that you think should be changed or added to it, of course I'll be glad to upgrade it. 

If you decide you want to use it, let me know and I will also take a magnifying glass to it and walk through it in the context of non-administrative/anonymous use.

Thanks,

Ryan

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...