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 Vigilante
      In general, is there an easy way to know which method should be used to access the API?
      For example, when _ini.php is used in the theme, it would seem you have to use wire()->addHookBefore(...). But on other sites where they used ready.php, I've seen it go straight to just doing $this->addHookAfter() even though there is no class or namespace set up in the ready.php file.
      So how do I know when I can do $this, or $wire, or wire() to access things? And any other variables I'm not aware of.
      Thanks!
    • By Peter Knight
      I have a few web forms which require testing on a weekly basis and I don't want the recipients (administrators) to receive these test emails.
      What would be a good way to test approx 15 forms from the front end and have the test delivered a list of secondary administrator recipients?
      I'm thinking that I could have some kind of config file which watches for a trigger word or email and then understands that it's a test and to bypass the normal admins?
      All of the forms ask for an email address so I could setup an email such as 'testform@email.not' etc which my config file (hook?) would watch for.
      Or is there a better way to do this?
      Additionally, I have a few extra requirements...
      Forms should goto an alternative success page. This is because I don't want my test to skew my Google Analytics conversion tracking Forms would need to be tested from the front-end and not the PW admin area Any advice appreciated.
      BTW I realise this should be posted in the proper FormBuilder support forum. I am in the process of renewing my license for access to that support forum.
       
    • By VeiJari
      Hello forum,
      This is really a weird one, because front end editing works in a earlier website we did to a customer. 
      When I check the source code for current website it does initiate front end edit: 
      <span id=pw-edit-1 class='pw-edit pw-edit-InputfieldPageTitle' data-name=title data-page=1021 data-lang='1017' style='position:relative'><span class=pw-edit-orig>Tekijät</span><span class=pw-edit-copy id=pw-editor-title-1021 style='display:none;-webkit-user-select:text;user- select:text;' contenteditable>Tekijät</span></span>  But when I double click nothing happens (yes I'm 100% sure I'm superuser and logged in)
      I also tried to apply the front end with other methods than:
      $page->edit('title'); But didn't work either.
      We are using jquery 2.2.4, so it should not be a problem.
      Is this a bug related to current master or something else?
      Someone else having this problem as well?
    • By jom
      Hi everyone
      It seems that I don't fully understand the wireTempPath() function and I need some help.
      I use wireTempPath() to create a new location in assets/cache/WireTempDir and than copy a pdf from the assets/files/page folder to the new folder. I want the file to be accessible only for a limited time, that's why I use wireTempPath.
      The file seems to be copied to the right location, but gets deleted right afterwards, according to 
      As mentioned in the topic above, 
      $wireTempDir->setRemove(false); prevents the file to be deleted. But I like the file to be automatically deleted after a few days. So, how can I do that?
      My code so far (everything works, but the automatic removal of the tempDir folder):
      //generate and show download link $folder = time(); // timestamp as temporary folder $maxAge = (int) $settings->options_downloadlink_valid_hours * 3600; //tempDir wants maxAge as seconds $options = array( 'maxAge' => $maxAge ); $wireTempDir = wireTempDir($folder, $options); $wireTempDir->setRemove(false); $src_file = $page->ebook_download->filename; // Create a new directory in ProcessWire's cache dir if(wire('files')->mkdir($wireTempDir, $recursive = true)) { if(wire('files')->copy($src_file, $wireTempDir)){ //get subdirs from tempDir: $pos = strpos($wireTempDir, "WireTempDir"); $subdir = substr($wireTempDir, $pos, 100); $out .= "<p><a href='" . wire('pages')->get('template=passthrough')->httpUrl . "?file=" . $subdir . $page->ebook_download->basename . "' target='_blank'>$page->title</a></p>"; } } I appreciate any ideas - thanks!
      Oliver
    • 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!
×
×
  • Create New...