agentejo / cockpit

Add content management functionality to any site - plug & play / headless / api-first CMS
http://getcockpit.com
MIT License
5.4k stars 524 forks source link

Delegate forms $data validation to Forms::submit #1411

Open Raruto opened 3 years ago

Raruto commented 3 years ago

Preamble

This pull request doesn't alter current forms submission behavior (in essence it ensures that the "forms.submit.before" event is always triggered for each form submission).

A borderline example is shown below.


Issue description:

When using a form call like the following:

<form action="/api/forms/submit/contact" method="post" enctype="multipart/form-data">
  <input name="files[]" type="file">
  <input name="submit" type="submit" value="Submit">
</form>

empty $_POST data is passed to Forms\Controller\RestApi::submit causing an early exit from that function and without being so able to parse $_FILES data inside the "forms.submit.before" hook

https://github.com/agentejo/cockpit/blob/efb8944051d7f347a4114cd21d8f01ecab373a00/modules/Forms/Controller/RestApi.php#L23-L34

Proposed solution:

Set fallback param $data to empty array and delegate empty check validation to Forms::submit function.

// Forms\Controller\RestApi::submit

$data    = $this->param('form', []);
$options = $this->param('form_options', []);

return $this->module('forms')->submit($form, $data, $options);
// Forms::submit

$this->app->trigger('forms.submit.before', [$form, &$data, $frm, &$options]); // <-- parse here form $data request

if (empty($data)) {
  return false;
}

Example of usage:

Dynamically check and populate forms $data['files'] entry:

// config/bootstrap.php

$app->on('forms.submit.before', function($form, &$data, $frm, &$options) use ($app) {

  // see "Helper Functions" for more info about it
  $files = get_uploaded_files()

  if (!empty($files)) {
    $files = $app->module('cockpit')->uploadAssets('files', ['folder' => get_forms_uploads_folder()]);
    $data['files'] = $files['uploaded']; // <-- save entries as filename
  }

});
Contact Form
Simple contact form template with **single input `files[]`**: ![contact](https://user-images.githubusercontent.com/9614886/106008118-494d0c80-60b7-11eb-91ae-adb0af7f77e0.png) **Lexy template:** ```blade @form( 'contact', [ 'id' => 'contact-form', 'class'=>'contact-form' ] )
@lang('Contact us'):

@endform ```
Upload Form
Simple contact form template with **multiple input `files[]`** : ![upload](https://user-images.githubusercontent.com/9614886/106009161-56b6c680-60b8-11eb-8a4e-4436864a7014.png) **Lexy template:** ```blade @form( 'upload', [ 'id' => 'upload-form', 'class'=>'upload-form' ] )
@lang('Upload some files'):
@endform ```

Helper Functions

config/bootstrap.php
```php /** * Check and retrieve forms uploaded files * * @return array $data */ function get_uploaded_files() { $app = cockpit(); $files = $app->param('files', [], $_FILES); $data = []; if (isset($files['name']) && is_array($files['name'])) { for ($i = 0; $i < count($files['name']); $i++) { if (is_uploaded_file($files['tmp_name'][$i]) && !$files['error'][$i]) { foreach($files as $k => $v) { $data['files'][$k] = $data['files'][$k] ?? []; $data['files'][$k][] = $files[$k][$i]; } } } } return $data; } /** * Check and retrieve forms upload folder * * @return array $folder */ function get_forms_uploads_folder() { $app = cockpit(); $name = 'forms_uploads'; $parent = ''; $folder = $app->storage->findOne('cockpit/assets_folders', ['name'=>$name, '_p'=>$parent]); if (empty($folder)) { $user = $app->storage->findOne('cockpit/accounts', ['group'=>'admin'], ['_id' => 1]); $meta = [ 'name' => $name, '_p' => $parent, '_by' => $user['_id'] ?? '', ]; $folder = $app->storage->save('cockpit/assets_folders', $meta); } return $folder; } /** * Check and populate forms $data['files'] entry */ $app->on('forms.submit.before', function($form, &$data, $frm, &$options) use ($app) { $files = get_uploaded_files(); if (!empty($files)) { $files = $app->module('cockpit')->uploadAssets('files', ['folder' => get_forms_uploads_folder()]); $data['files'] = []; $ASSETS_URL = rtrim($app->filestorage->getUrl('assets://'), '/'); // save entries as filename // $data['files'] = $files['uploaded']; // save entries as absolute urls foreach($files['assets'] as $file) { $data['files'][] = $ASSETS_URL.$file['path']; } } }); ```

End notes

The Contact Form example reported above does not suffer from this problem because the variable $_POST is still populated by other input fields.

The Upload Form (with just input $_FILES) is purely demonstrative. It can be solved somehow by hooking the assets api, however, it would be nice to be able to do it with the same hook (regardless of the number of parameters and without having to define custom rest api endpoints...)

Hoping that's clear enough, Raruto