Allow guests to create pages from Panel or frontend

User Scenario:

Guest visits www.website.com
-> clicks link to “Submit” www.website.com/submit
-> a version of Panel opens up. Guest creates a page, fills the fields, uploads photos, hits Submit.
-> The created page is saved as invisible.
-> Website admin reviews the inivisible pages and turns to Visible the ones he approves.

Practically I would need a “Guest” access to the panel, not protected by user/password, with capability to create / sumbit invisible pages.

I would then turn on the pages that have been filled correctly / approved.

Is it possible?

1 Like

Currently not out of the box. User permission are in the pipeline for one of the next releases but not yet available.

You could however have those users create pages via a form on the frontend.

A form in the front end could work for me. How do I get the form to create a folder, file and put the content into the file?

Can you point me towards any useful resource?

Check out the docs about $pages->create() and for file uploading you can use the upload class the toolkit provides. Unfortunately, there is documentation on how to use that class, though. But the forum is here to help …

Here’s some example code you can use in the controller of the frontend form page:

<?php 

return function($site, $pages, $page) {
  $error = null;
  if(r::method() === 'POST') {
    // The form has been sent
    
    // Build an array of the data you want to allow as fields
    // get() fetches the form field value with that `name`
    $data = array(
      'title' => get('title'),
      'text' => get('text')
    );
    
    // Validate the data
    if($whatever) $error = 'Something is invalid.';
    
    // Create a new page as child of the current page
    // You can also use a different page by using `page('whatever')->children()->create()`
    $p = $page->children()->create(uniqid(), 'yourtemplate', $data);
    
    // Upload an image
    try {
      // This uses the form field with `name="file"`
      // See http://php.net/manual/en/features.file-upload.post-method.php#example-392 on how to structure your form
      $upload = new Upload($p->root() . DS . '{safeFilename}');
    } catch(Error $e) {
      switch($e->getCode()) {
        case Upload::ERROR_MISSING_FILE:
          // File does not exist
          $error = 'No file uploaded.';
          break;
        // See the Upload class for other error values
      }
    }
  }
  
  return compact('error');
};
1 Like

I have managed to implement your code and it works successfully :slightly_smiling:

What if I want the user to upload multiple files, rather then just one?

<label for="file">Pictures</label> <input type="file" name="file" /> <input type="file" name="file" /> <input type="file" name="file" /> <input type="file" name="file" /> <input type="file" name="file" /> <input type="file" name="file" />

How do I make the controller save all the files?

Thanks!

Each of the file inputs needs to have its own name property (‘file1’, ‘file2’ etc. or completely custom names).

You can then specify to the Upload class which file to save, one at a time:

new Upload($p->root() . DS . '{safeFilename}', array('input' => 'file1'));

Is it possible to submit more files at ones?

At the moment I have in my template:

<form action="" method="post" enctype="multipart/form-data">
    <input name="upload1" type="file" />
    <input name="upload2" type="file" />
    <input name="upload3" type="file" />
    <input name="upload4" type="file" />
    <input type="submit" name="submit" value="submit">
</form>

And then in the controller:

  $upload = new Upload($p->root() . DS . '{safeFilename}', array('input' => 'upload1'));
  $upload = new Upload($p->root() . DS . '{safeFilename}', array('input' => 'upload2'));
  $upload = new Upload($p->root() . DS . '{safeFilename}', array('input' => 'upload3'));
  $upload = new Upload($p->root() . DS . '{safeFilename}', array('input' => 'upload4'));

But the user has manually to select one file at the time.


I see that HTML5 supports the input multiple attribute:

<form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="file" multiple>
    <input type="submit" name="submit" value="submit">
</form>

Then what should my controller look like?

...

When uploading multiple files you need to use an array as the value of the name attribute, not a string:

<input type="file" name="file[]" multiple>

You should then be able to iterate the resulting files array using the code above posted by @lukasbestle for each element of the array.

<?php 
$files = $_FILES['file'];
foreach($files as $file)  {
  try
   ....
}
?>

I get this error back:

Warning
: pathinfo() expects parameter 1 to be string, array given in
/mamp/site/kirby/toolkit/lib/f.php
on line
438

That’s the bit of the source code your error refers to:

public static function name($name) {
    return pathinfo($name, PATHINFO_FILENAME);

Looks like you are using the name() method somewhere with an array instead of a string?

What does your full code look like now?

This is the form in my template:

<form action="" method="post" enctype="multipart/form-data">
    <ul>
	    <li>
		    <label for="file">Pictures</label>
		    <input name="file[]" type="file" multiple>
	    </li>
	    <input type="submit" name="submit" value="Send it in!" class="button">
    </ul>
</form>

This is my controller now:

<?php 

return function($site, $pages, $page) {
  $error = null;
  if(r::method() === 'POST') {
    // The form has been sent

    $title = get('brand') . "-" . get('tube-name');

    
    // Build an array of the data you want to allow as fields
    // get() fetches the form field value with that `name`
    $data = array(
      'tube-name' => get('tube-name'),
      'brand' => get('brand'),
      'different_manufacturer' => get('different_manufacturer'),
      'country' => get('country'),
      'code_on_tube' => get('code_on_tube'),
      'factory_code' => get('factory_code'),
      'year' => get('year'),
      'bottle_height' => get('bottle_height'),
      'Tip-Shape' => get('Tip-Shape'),
      'glass_color' => get('glass_color'),
      'Mica' => get('Mica'),
      'getter' => get('getter'),
      'getter_support' => get('getter_support'),
      'plate_look' => get('plate_look'),
      'plate_size' => get('plate_size'),
      'plate_color' => get('plate_color'),
      'pin_color' => get('pin_color'),
      'base' => get('base'),
      'special_use' => get('special_use'),
      'other_marks' => get('other_marks'),
      'notes' => get('notes'),
      'uploader_name' => get('uploader_name'),
      'uploader_email' => get('uploader_email')
      );
    
    // Create a new page as child of the current page
    // You can also use a different page by using `page('whatever')->children()->create()`
    $p = page('uploads')->children()->create($title, 'tube', $data);

    
    // Upload an image
    $files = $_FILES['file'];
    foreach($files as $file)  {
      try {
        $upload = new Upload($p->root() . DS . '{safeFilename}', array('input' => 'file'));
      } catch(Error $e) {
        switch($e->getCode()) {
          case Upload::ERROR_MISSING_FILE:
          // File does not exist
          $error = 'No file uploaded.';
          break;
          // See the Upload class for other error values
        }
      }
    }
  }


  return compact('error');
};
?>

Thanks for the code. I just tested it and found out that the way PHP structures the resulting $_FILES array for multiple files is currently not supported by the Upload class.

I will add this feature as a Pull Request later today and come back to you here.

1 Like

Here’s the new Pull Request I created.

If you want to try it, temporarily replace your kirby/toolkit/lib/upload.php with the file from the PR and use the following controller code (your template is fine):

<?php 

return function($site, $pages, $page) {
  $error = null;
  if(r::method() === 'POST') {
    // The form has been sent
    
    $title = get('brand') . "-" . get('tube-name');
    
    // Build an array of the data you want to allow as fields
    // get() fetches the form field value with that `name`
    $data = array(
      'tube-name' => get('tube-name'),
      'brand' => get('brand'),
      'different_manufacturer' => get('different_manufacturer'),
      'country' => get('country'),
      'code_on_tube' => get('code_on_tube'),
      'factory_code' => get('factory_code'),
      'year' => get('year'),
      'bottle_height' => get('bottle_height'),
      'Tip-Shape' => get('Tip-Shape'),
      'glass_color' => get('glass_color'),
      'Mica' => get('Mica'),
      'getter' => get('getter'),
      'getter_support' => get('getter_support'),
      'plate_look' => get('plate_look'),
      'plate_size' => get('plate_size'),
      'plate_color' => get('plate_color'),
      'pin_color' => get('pin_color'),
      'base' => get('base'),
      'special_use' => get('special_use'),
      'other_marks' => get('other_marks'),
      'notes' => get('notes'),
      'uploader_name' => get('uploader_name'),
      'uploader_email' => get('uploader_email')
    );
    
    // Create a new page as child of the current page
    // You can also use a different page by using `page('whatever')->children()->create()`
    $p = page('uploads')->children()->create($title, 'tube', $data);
    
    // Upload the images
    $missingFile = false;
    $index = 0;
    do {
      try {
        $upload = new Upload($p->root() . DS . '{safeFilename}', array('input' => 'file', 'index' => $index));
        $index++;
      } catch(Error $e) {
        switch($e->getCode()) {
          case Upload::ERROR_MISSING_FILE:
            // No more files have been uploaded
            $missingFile = true;
            break;
          // See the Upload class for other error values
        }
      }
    } while(!$missingFile);
    
    // Check if an image has been uploaded at all
    if($index === 0) {
      $error = 'No file uploaded.';
    }
  }
  
  return compact('error');
};

It works now! Thank you! :smiley_cat:

1 Like

I am getting an error when the page is live on the server.
When instead I run the website locally on mamp, everything works good.

The error says:

Fatal error: Uncaught Error: Call to a member function children() on boolean in /home/user/website/site/controllers/submit-page.php:40 Stack trace: #0 /home/user/website/kirby/kirby.php(525): Kirby->{closure}(Object(Site), Object(Children), Object(Page), Array) #1 /home/user/website/kirby/kirby.php(699): Kirby->controller(Object(Page), Array) #2 /home/user/website/kirby/kirby.php(680): Kirby->template(Object(Page), Array) #3 /home/user/website/kirby/kirby.hp(781): Kirby->render(Object(Page)) #4 /home/user/website/index.php(16): Kirby->launch() #5 {main} thrown in /home/user/website/site/controllers/submit-page.php on line 40

What do you think could be?

What is line 40 of your controller?

This is line 40

$p = page('uploads')->children()->create($title, 'tube', $data);**strong text**

The whole controller.php is the same as you posted 4 replies above.

Do you have a page named uploads on your production server? This error normally means that the page does not exist.