Jump to content
MatthewSchenker

Create Pages (with File-Upload Field) via API

Recommended Posts

Greetings,

OK, I think we must be very close...  I've reworked my code so it looks the way Ryan posted above.

But there is one thing that continues to happen that seems odd...

I receive this error on the "success" page after submitting the form:

Destination path does not exist temp

I've tried making the reference in various ways, including...

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

The "temp" folder definitely exists in this location:

/site/assets/files/temp

It's got permissions set to 777.

I assume I am missing something really obvious, but can't see what.

Thanks,

Matthew

Share this post


Link to post
Share on other sites

And what do you think it is? What have you done to debug the upload path, now that you know it's something wrong there? The answer is even in the error itself.

Share this post


Link to post
Share on other sites

Greetings,

OK, I am willing to take the risk here of making an obvious error.

Just confirming that this code:

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

Corresponds to this directory:

/site/assets/files/temp

Thanks,

Matthew

Share this post


Link to post
Share on other sites

Confirming means you tested it? Echo it out in your code and add line "exit;" after it.

Share this post


Link to post
Share on other sites

Greetings Soma,

I really appreciate your patience.  I admit this has been a rather long discussion...

I replaced the reference with this:
 

$upload_path = '../assets/files/temp/';

And now it works...

Yes, you read that correctly. It works.  I mean the whole form -- file uploads and everything.

I'll post the complete code shortly.

Thanks for helping me through this.  What kind of beer do you like?

Thanks,

Matthew

Share this post


Link to post
Share on other sites

Thanks Matthew, well I like all good beers :D

Have you tried my suggestion to output the upload path to debug? However, to make it short, Ryan made an small error in that he uses "$config" in a function scope. That would leave you with a upload path "temp". 

It would have to be "wire("config")->paths->assets" to get it to work, as wire() is global and can be used everywhere even in functions.

Share this post


Link to post
Share on other sites

Edit: Just wanted to add that hardcoding the paths isn't exactly a good solution. Better use the config vars.

Share this post


Link to post
Share on other sites

... to make it short, Ryan made an small error in that he uses "$config" in a function scope. That would leave you with a upload path "temp". 

Aha!  There's the thing... I was wondering about that.  Also, probably due to the same point, I was getting an error using simple ProcessWire "get" reference to a template.

I actually took the code out of a function, and then -- as you described -- used the config reference instead.

Everything works.

Thanks again,

Matthew

Share this post


Link to post
Share on other sites

I posted 3 or was it 4 examples codes that work already, so what is the deal ? :P

Spent lots of hours btw...

  • Like 1

Share this post


Link to post
Share on other sites

I have to agree with Soma here. There are several working versions already.

Rather than introduce yet another example, it seems like we should just wrap up the discussion with links to the already provided working examples.

  • Like 1

Share this post


Link to post
Share on other sites

Maybe remove the code in the first post and link to a working code at least. :)

  • Like 1

Share this post


Link to post
Share on other sites

Maybe remove the code in the first post and link to a working code at least. :)

Why not :)

Anyway I'm finding already my way to go through this topics thanks to you.

Share this post


Link to post
Share on other sites

Greetings,

I just edited the first post so it now contains the updated code.

I have left two versions of the form and success page posted, so people can see what everything looks like without file uploads.  Then there are the form and success page with file uploads.  Of course, be careful to change the references to match your own fields, paths, and messages.

There are of course a few other steps that others may want to add to the forms I posted.  But they work.

And of course Soma has posted some great examples of how to do this.

I'm glad to have worked through this so we can arrive at some code that handles file uploads.

Thanks,

Matthew

Share this post


Link to post
Share on other sites

Hi guys.

I just happened to need a method of allowing image uploads and what do you know? There's a whole thread dedicated to it. ^-^

Matthew, nice work. Thank you. I have only 2 things to add:

1. The code as is makes the assumption that each user will only ever upload one image or that there are no people with the same name. Since the title is derived from the contactname an error is thrown if a second upload is made with the same name. I added:

$n = 0;
$np->title = $sanitizer->text($input->post->contactname)  . "-" . (++$n);
 

It ensures each upload will get a unique ID.

2. I noticed the images uploaded to the .temp directory don't get deleted. With extensive use this will lead to lots of wasted server space as the .temp folder gets more and more bloated with duplicate images. Is there a way to solve this?

You made my job very easy anyway thanks again.

  • Like 3

Share this post


Link to post
Share on other sites

2. I noticed the images uploaded to the .temp directory don't get deleted. With extensive use this will lead to lots of wasted server space as the .temp folder gets more and more bloated with duplicate images. Is there a way to solve this?

Here is code from Ryan in a post of this thread:

 // Run photo upload
foreach($files as $filename) {
  $pathname = $upload_path . $filename;
  $np->contact_photo->add($pathname);
  $np->message("Added file: $filename");
  unlink($pathname);  // This deletes the uploaded file after it was added to the new page ($np)
}

unlink();

If you work with another code, you also add the files to the page. After that you have to unlink('the temp-file');

  • Like 1

Share this post


Link to post
Share on other sites

Greetings,

When I started this discussion, my goal was to pull together various renditions of code that were being discussed in forum discussions and create a single full example people can use to make front-end page submissions, and to also do this with photos.  This is a vital part of building the kinds of sites that are really exciting with ProcessWire.

I have now used the techniques detailed in this discussion for several projects, including one where I am building a completely custom front-end interface with page submission and page editing (more on that soon).

1. The code as is makes the assumption that each user will only ever upload one image...
2. I noticed the images uploaded to the .temp directory don't get deleted. With extensive use this will lead to lots of wasted server space as the .temp folder gets more and more bloated with duplicate images. Is there a way to solve this?

In my first post in this discussion, I have the "unlink($pathname)" included in the routine.  This removes the temporary image.  Horst makes reference to the same code posted by Ryan.

HANDLING MULTIPLE FILE-UPLOAD FIELDS IN THE SAME FORM

Since starting this discussion, I have also added multiple file uploads in the same form.  The steps are pretty easy.

Below is my original code from post #1, with additional code for a second file-upload field (indicated with "[NEW CODE]"):

<?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'); // Reference field name in HTML form that uploads photos
$contact_photo->setMaxFiles(5);
$contact_photo->setOverwrite(false);
$contact_photo->setDestinationPath($upload_path);
$contact_photo->setValidExtensions(array('jpg', 'jpeg', 'png', 'gif'));

// Second wire upload (other_photos) [NEW CODE]
$other_photos = new WireUpload('other_photos'); // Reference field name in HTML form that uploads photos
$other_photos->setMaxFiles(10);  // Allow 10 other photos
$other_photos->setOverwrite(false);  // Use the temporary location set above
$other_photos->setDestinationPath($upload_path);
$other_photos->setValidExtensions(array('jpg', 'jpeg', 'png', 'gif'));

// execute upload and check for errors
$files = $contact_photo->execute();
$other_files = $other_photos->execute(); // [NEW CODE]

// 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;
}

// Set up submissions in the ProcessWire page tree
$np = new Page(); // create new page object
$np->template = $templates->get("contact_submission"); // Set template for pages created from form submissions
$np->parent = $pages->get("/customer-service/contact-us/contact-submission-listing/"); // Set parent for pages created from form submissions

// Send form submissions through ProcessWire sanitization and apply each to a template field for the new page
$np->of(false);
$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 for "contact_photo"
foreach($files as $filename)
{
$pathname = $upload_path . $filename;
$np->contact_photo->add($pathname);
$np->message("Added file: $filename");
unlink($pathname);
}

// Run photo upload for "other_photos" [NEW CODE]
foreach($other_files as $other_file)
{
$pathname = $upload_path . $other_file;
$np->other_photos->add($pathname);
$np->message("Added file: $other_file");
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}
?>

And here is the updated submission form that would connect with the above code:

<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" />
<input type="file" name="other_photos[]" multiple />
<button type="submit">Submit</button>
</form>

Points to keep in mind:

1. Add a WireUpload action for each photo-upload field, setting the rules for each one.

2. Add a separate "execute" action for each photo-upload field.

3. Add a separate foreach loop for each photo field you need.

4. If the file-upload field is for multiple files, make sure that the setMaxFiles() action matches what you set in the admin panel for that field.

5. In your form, if the file-upload field is for multiple files, specify the name with square brackets, and add the "multiple" designation.

Thanks,

Matthew

  • Like 3

Share this post


Link to post
Share on other sites

I have the unlink ($pathname) in the code I'm using. The uploaded files are not being deleted though. Funny. I'll have to reexamine it. Everything works except for the file removal at the end.

Share this post


Link to post
Share on other sites

Gretings,

As a test, I just removed the "unlink" from one of my foreach loops, but left it in the other one.  It seems to work as expected.  If I have "unlink" at the end of the loop, the files are removed; if I don't include it, the files are not removed.  It's strange that it isn't working for you.

I actually am wondering whether in some cases it might be a good idea to keep the temporary files?  In those situations, I'm considering building a new sub-folder in the temp directory for each page -- maybe auto-create it based on the page ID?

Thanks,

Matthew

Share this post


Link to post
Share on other sites

Gretings,

As a test, I just removed the "unlink" from one of my foreach loops, but left it in the other one.  It seems to work as expected.  If I have "unlink" at the end of the loop, the files are removed; if I don't include it, the files are not removed.  It's strange that it isn't working for you.

I actually am wondering whether in some cases it might be a good idea to keep the temporary files?  In those situations, I'm considering building a new sub-folder in the temp directory for each page -- maybe auto-create it based on the page ID?

Thanks,

Matthew

Yes. I didn't even see the unlink when I incorporated the code but after reading yours and Horst's post i double checked. It's all in order and works like a charm with the exception that it doesn't delete. It's not a big deal as the server space is more than enough to accommodate the images and I can manually remove them. In my case it is a photo contest and entrants will be uploading high res images so I will have to monitor it.

Since the submissions are open to the public now (it was a rush which is why I was so happy to see complete code ready to use out of the box) I won't trouble shoot much until the deadline expires.

EDIT: I did a new test with a low res image (about 82k total) and it did delete on completion. Maybe it is working just not so well with the large files I was testing with previously.

Share this post


Link to post
Share on other sites

Thanks for posting this Matthew. A couple things I found that I wanted to mention, since this is a front-end form: 

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

You want your temporary upload path to be a non web accessible path. Also, if you've got multiple users uploading at the same time, it seems like there is potential for filename collisions, though not sure how big of a concern that is in this case. But you can at least make the directory non web accessible by preceding it with a period, i.e. ".contact_files" rather than "contact_files". 

$other_photos->setValidExtensions(array('jpg', 'jpeg', 'png', 'gif'));

Keep in mind that WireUpload is only validating the extension. It is not telling you for sure that it's an image. It could very well be some kind of executable with a jpg extension. I don't necessarily know how such a thing could be exploited, other than that I'm not so comfortable with letting a front-end user upload files that end up untouched in a publicly accessible directory. What would probably be safer is to make a new copy of any uploaded images before adding them to the page, and validate the size is within allowed limits while you are at it. 

$error = '';
$imageInfo = getimagesize($pathname);
if($imageInfo) list($width, $height) = $imageInfo; 

if(!$imageInfo) {
  $error = "Image is not valid";
} else if($width < 105 || $height < 105) {
  $error = "Image is too small";
} else if($width > 1600 || $height > 1200) {
  $error = "Image is too big";
} else {
  // create your own, slightly smaller copy
  $sizer = new ImageSizer($pathname); 
  if(!$sizer->resize($width-10, $height-10)) $error = "Unable to resize image";
}

if($error) {
  unlink($pathname); 
  echo "<p>Error: $error</p>";
} else {
  // add image to page
}
  • Like 4

Share this post


Link to post
Share on other sites

@Digitex

Try altering the following:

$pathname = $upload_path . $filename; 

to

$pathname = $upload_path.$filename; 

no space between _path and $filename

Greetings.

Share this post


Link to post
Share on other sites

 Try altering the following:

$pathname = $upload_path . $filename; 

to

$pathname = $upload_path.$filename; 

 no space between _path and $filename

@videokid: Sorry, but this is nonsense.

And for better readability you should use the first one with spaces.

  • Like 1

Share this post


Link to post
Share on other sites

Maybe...

but could you explain why that did the trick for me?

As I have noticed more then once....

$upload_path . $filename

file is still in the folder

$upload_path.$filename

file is gone in the temp folder...

About readability, I guess that's personal, to be honest strings and dots I NEVER use spaces, I simply don't like that...

Grtz...

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By VeiJari
      Hello forum, this is my first security related post, so I'm a bit of a newbie.
      I understand that when I have direct front-input from user I should sanitize the input, but how about when I use a secret key for showing a API for a third-party supplier? Should I sanitize the input->get() key?
      I've tested this issue and I tried ?key=<?php echo $page->field; ?> And without adding any sanitization it comes back: /?key=<?php%20echo%20$page->field;%20?>
      So can I rely on this, or should I still use $sanitizer just in case?
       
      Thanks for the help!
    • By EyeDentify
      I have been experimenting with the new $page->meta() method and find it useful.

      Once i figured out that the data i "save" with it is tied to the page where i called the method from.

      So this is not obvious at least not for me in the documentation:
      https://processwire.com/api/ref/page/meta/
       
      So i just wanted to share that revelation with the community so you don´t get as confused as i was.

      Happy Coding Everyone.
    • By louisstephens
      Going through my long quest to get better with ajax and utilizing the api, I have hit yet another roadblock. I currently have a form with an image field (thanks to flydev for getting that sorted), "title" text input, and a select field set to multiple. In my ajax call, I added in:
      tags = $("#select-tags").val(); form_data.append('tags', tags); $.ajax({ type: 'POST', data: form_data, contentType: false, processData: false, url: '/ajax/upload-preview/', success: function(data) { console.log("Woo"); }, error: function(xhr, ajaxOptions, thrownError) { alert(xhr.responseText); } }); And in the ajax template: 
      $tags = $sanitizer->text($_POST['tags']); $image = $sanitizer->text($_POST['image']); $p = new Page(); $p->template = "preview"; $p->parent = $pages->get("/previews/"); $p->name = $title; $p->title = $title; $p->tags = $tags; $p->save(); If I select a "tag" from the select input and submit, it does indeed add it to the Page Reference field in the backend. However, this does not work with an array being passed to it of multiple options.

      So it does appear that my ajax call is trying to submit multiple options, but I am really just unsure how to get these two added in. I saw in other forums posts of add($page) and even add(array()). Do I need to handle this js array differently or do  I need to foreach through the $tags to add it like:
      foreach($tags as $tag) { $p->tags->add($tag); $p->save(); } I tried this approach, but apparently I am still missing something.
       
      Edit:
      I was doing some tweaking, and I know I can split the js array out like:
      for (i = 0, len = tags.length; i < len; i++) { console.log(tags[i]); } However, I am not sure then how to handle the POST in php if I were to split it out.
    • By louisstephens
      I have been messing around with creating pages from ajax requests, and it has gone swimmingly thus far. However, I am really struggling with creating a page and saving an image via ajax. 
      The form:
      <form action="./" role="form" method="post" enctype="multipart/form-data"> <div> <input type="text" id="preview" name="preview" placeholder="Image Title"> </div> <div> <input type="file" id="preview-name" name="preview-name"> </div> <div> <select id="select-tags" name="select-tags"> <?php $tags = $pages->find("template=tag"); ?> <option value="">Select Your Tags</option> <?php foreach ($tags as $tag) : ?> <option value="<?= $tag->name; ?>"><?= $tag->name; ?></option> <?php endforeach; ?> </select> </div> <div> <button type="button" id="submit-preview" name="submit" class="">Upload Images</button> </div> </form>  
      The ajax in my home template:
      $('#submit-preview').click(function(e) { e.preventDefault(); title = $("#preview").val(); image = $("input[name=preview-name]"); console.log(title); console.log(image); data = { title: title, image: image //not sure if this is actually needed }; $.ajax({ type: 'POST', data: data, url: '/development/upload-preview/', success: function(data) { console.log("Woo"); }, error: function(xhr, ajaxOptions, thrownError) { alert(xhr.responseText); } }); }); And finally in my ajax template:
      $imagePath = $config->paths->assets . "files/pdfs/"; //was from an older iteration $title = $sanitizer->text($_POST['title']); $image = $sanitizer->text($_POST['image']); $p = new Page(); $p->template = "preview"; $p->parent = $pages->get("/previews/"); $p->name = $title; $p->title = $title; $p->save(); $p->setOutputFormatting(false); $u = new WireUpload('preview_image'); $u->setMaxFiles(1); $u->setOverwrite(false); $u->setDestinationPath($p->preview_image->path()); $u->setValidExtensions(array('jpg', 'jpeg', 'gif', 'png', 'pdf')); foreach($u->execute() as $filename) { $p->preview_image->add($filename); } $p->save(); I can complete the file upload but just using a simple post to the same page and it it works well, but I was really trying to work out the ajax on this so I could utilize some modals for success on creation (and to keep my templates a little cleaner). When I do run the code I have, a new/blank folder is created under assets, and a new page is created with the correct title entered. However, no image is being processed. I do get a 200 status in my console. I have searched google for help, but everything seems to be slightly off from my needs. If anyone could help point me in the right direction I would greatly appreciate it. 
    • By louisstephens
      This might be a completely dumb question, but I cant seem to wrap my head around it. I have a page reference field that allows users to select "Tags". In the front end I would like to use the titles as class names for a single item. ie:
      <?php $previews = $pages->find("template=preview"); ?> <?php foreach($previews as $preview): ?> <div class="tagOne TagTwo tagThree"> <!-- use another foreach to output--> <img src="<?=$preview->preview_image->url; ?>" /> </div> <?php endforeach; ?> I am little stumped as I know I need a foreach loop to produce each tag title, but how do I insert them all into one corresponding div class with spaces?
      Whelp, that was the easiest thing, but my brain just didnt "get it". Just put the foreach in the "class" inside of the overall foreach. Ugh 😓
×
×
  • Create New...