FormHandler is a PHP written "module" which allows you to create dynamic forms in an easy way. So easy that you can build a fully working form, including field validations, within 10 lines!
Install the latest version with
$ composer require formhandler/formhandlerWhen the vendor directory is outside the root directory of your web application use a symlink on the FHTML directory to make it publicly available.
For example: map vendor/formhandler/formhandler/src/FHTML to /FHTML/ of your website root. Then add to your application
<?php
fh_conf('FH_FHTML_DIR', '/FHTML/');<?php
//FormHandler and all its classes are loaded by Composer's autoloader
require __DIR__ . '/vendor/autoload.php';
//create a new FormHandler object
$form = new FormHandler();
//some fields.. (see manual for examples)
$form->textField("Name", "name", FH_STRING, 20, 40);
$form->textField("Age", "age", FH_INTEGER, 4, 2);
//button for submitting
$form->submitButton();
//set the 'commit-after-form' function
$form->onCorrect('doRun');
//display the form
$form->flush();
//the 'commit-after-form' function
function doRun($data)
{
echo "Hello ". $data['name'].", you are ".$data['age'] ." years old!";
}For an extended version of the documentation please check our manual located at http://www.fh3.nl/manual/manual.html
The manual describes version 3. The changes of every version are in CHANGELOG.md,
and examples/ has a page for every element (see SETUP.md).
Database connection with Doctrine DBAL. dbInfo() and dbConnect() work as before.
An open connection has to be a DBAL connection:
$conn = \Doctrine\DBAL\DriverManager::getConnection([
'driver' => 'pdo_mysql', 'host' => 'localhost',
'user' => 'user', 'password' => 'secret', 'dbname' => 'mydb',
]);
$form = new dbFormHandler();
$form->setConnectionResource($conn, 'users');HTML5 date field (4.2). The browser shows its own date picker, the value is always
yyyy-mm-dd. The mask is only used in view mode; true sets today's date in a new form.
// html5DateField($title, $name, $validator, $mask, $setCurDate, $extra)
$form->html5DateField('Birthday', 'birthday', FH_NOT_EMPTY, 'd.m.Y');The editor HTML is sanitized on the server (4.4). editor() only keeps safe HTML:
scripts, event handlers, javascript: links, iframes and forms are removed, also when the
request is sent by hand. The style attribute is removed as well; to keep colors and
font sizes, extend Editor:
class StyledEditor extends Editor
{
protected function isStyleAllowed()
{
return true;
}
}examples/editor.php shows how to add such an editor to a form; override
getSanitizerConfig() for another allow list.
Errors are exceptions (4.1). A wrong use of FormHandler (unknown language, missing
callback, invalid date mask, editing a record that does not exist, ...) throws a
FormHandlerException instead of trigger_error(..., E_USER_ERROR).
Captcha with gregwar/captcha (4.7). captchaField() needs the package gregwar/captcha
(composer require gregwar/captcha), FormHandler does not install it for every project.
The image is part of the form and its code is kept in the PHP session.
No bundled JavaScript libraries (4.7). jQuery, overLIB, jscolor, CalendarPopup and the
file manager are gone; small scripts without a library and the controls of the browser
replace them (date picker, color input, suggestion list for setAutoComplete() and
textSelectField()). The PHP API is unchanged. A project with its own FH_HELP_MASK that
calls overlib() has to change it, and browserField() is a plain text field now (see
"Images and files in the editor" below).
Getters for the fields (4.7). $form->getField('name') returns the field object, and
the fields have getters for what was only in public properties (getErrorMessage(),
getOptions(), getHourField(), ...). The public properties are deprecated and become
protected in 5.0.
Composer only (4.3). All classes are loaded by Composer's autoloader, so you can extend
e.g. TextArea in your own fields. Including class.FormHandler.php without Composer is
no longer supported.
CSRF protection (4.6). Every form has a hidden field <form name>_csrf with a token of
the PHP session. A form posted without the right token (e.g. from another website) is not
processed; it is shown again with the message "The form has expired or was not sent from
this page".
-
It needs a PHP session. FormHandler starts it when no output was sent yet; otherwise call
session_start()before any output. Without a session the form warns and is not protected. -
Own AJAX requests that post a form have to send the token. Requests that post all fields of the form, including the hidden ones, already do; otherwise add it yourself:
// in the page: pass the token to your JavaScript echo '<script>var formToken = ' . json_encode($form->getCsrfToken()) . ';</script>'; // in the request: POST FormHandler_csrf=<formToken> (the field is "<form name>_csrf")
-
Turn it off for a single form with
$form->setCsrfProtection(false), or for all forms withfh_conf('FH_CSRF_PROTECTION', false)before the first form is created (thefh_conf()values become constants then).
Copy a record (4.8). setCopyOf() shows a record and saves it as a new one:
$form = new dbFormHandler();
if (($_GET['funktion'] ?? '') == 'copy')
{
$form->setCopyOf($_GET['id']); // before dbInfo() / setConnectionResource()
}
$form->setConnectionResource($conn, 'news');Call it on every request of the form, also when it is posted. The primary key and the
files of the uploadfields are not copied, the values of the junction tables are.
isCopy() and getCopyOfId() tell if and which record is copied, getEditId() is
null, and after saving getRecordId() returns the id of the new record.
More fields in one row (4.8). joinNextFields() puts the next fields into one row of
the mask, with the title and help of the first one and the error messages of all:
$form->joinNextFields(2, ' / ');
$form->selectField('Opponent', 'gegner', $gegner);
$form->textField('Team', 'team');The database after saving (4.8). $form->getDatabaseAdapter() returns the
DatabaseAdapter of a dbFormHandler, also after flush() saved the record; before 4.8
it was unset after saving.
{id} in the name of the upload config is replaced by the id of the record (4.8). A new
record does not have an id while the form is checked, so FormHandler uploads the file after
the INSERT; there is no need to guess the next id with SHOW TABLE STATUS.
$form = new dbFormHandler();
$form->setConnectionResource($conn, 'spieler');
$form->textField('Name', 'name', FH_NOT_EMPTY);
$form->uploadField('Picture', 'bild', [
'path' => __DIR__ . '/images/spieler/',
'type' => 'jpg jpeg png',
'name' => 'spieler_{id}', // spieler_42.jpg
'exists' => 'overwrite', // a new picture replaces the old one
]);
$form->onSaved('spielerSaved');
$form->flush();
function spielerSaved($id, $data, $form)
{
// $data['bild'] is "spieler_42.jpg"
}- Edit form (
?id=42): the file is uploaded as before, with the id from the URL. - New record: the file is uploaded after the INSERT, then
imageResize()andmergeImage()of the field run. If the table has a column with the name of the field, FormHandler saves the filename there with an UPDATE. When the INSERT fails, no file is moved. {id}also works in the filename ofresizeImage(), e.g.$form->resizeImage('bild', __DIR__ . '/images/spieler/klein_{id}.jpg', 200). On a new record the field then waits for the INSERT, even when its own filename has no{id}.onCorrectruns before the INSERT: for a new record the filename is still empty there,onSavedgets it.isUploaded()andgetFileInfo()work before the INSERT too.- After saving,
$form->getRecordId()returns the id of the record, of a new one as well as of an edited one (getNewId()only the id of the INSERT). 'exists'is used for the filename with the id.'alert'(the default) can not warn on a new record, the filename is not known while the form is checked; use'overwrite'or'rename'.- It needs a
dbFormHandlerwith a table and a single primary key.FormHandlerthrows aFormHandlerException, as well as an id with other characters than letters, digits,_and-. - On a form with more pages the field has to be on the last page: the uploaded file of an earlier page is gone when the record is saved.
The bundled file manager is removed (4.7): it had no login. Uploading or choosing files
has to happen behind the login of your application, so link your own scripts with the
$config of editor(). Both scripts must check the login themselves.
Upload images (the simplest way). CKEditor uploads pasted or dropped images and the images of the "Upload" tab of the image dialog to your script:
$form->editor('Text', 'text', config: [
'uploadUrl' => '/admin/editor-upload.php',
'filebrowserUploadUrl' => '/admin/editor-upload.php?responseType=json',
]);<?php
// /admin/editor-upload.php: the file comes in the field "upload", the answer is JSON
session_start();
header('Content-Type: application/json');
if (empty($_SESSION['user'])) { // your own login check
exit(json_encode(['uploaded' => 0, 'error' => ['message' => 'Not logged in']]));
}
$file = $_FILES['upload'] ?? null;
$types = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', 'image/webp' => 'webp'];
$type = $file && $file['error'] === UPLOAD_ERR_OK ? mime_content_type($file['tmp_name']) : '';
if (!isset($types[$type])) {
exit(json_encode(['uploaded' => 0, 'error' => ['message' => 'Only images can be uploaded']]));
}
// an own file name: never use the name from the browser as path
$name = bin2hex(random_bytes(8)) . '.' . $types[$type];
move_uploaded_file($file['tmp_name'], __DIR__ . '/../images/editor/' . $name);
echo json_encode(['uploaded' => 1, 'fileName' => $name, 'url' => '/images/editor/' . $name]);Browse files on the server. A file manager such as
elFinder (composer require studio-42/elfinder,
BSD license) can be opened by the "Browse Server" buttons of the image and link dialogs:
$form->editor('Text', 'text', config: [
'filebrowserBrowseUrl' => '/admin/elfinder.html',
]);-
/admin/elfinder.htmlshows elFinder (it needs jQuery and jQuery UI, only on this page) and gives the chosen file back to the editor:// the CKEditor dialog which opened the window var funcNum = new URLSearchParams(location.search).get('CKEditorFuncNum'); $('#elfinder').elfinder({ url: '/admin/elfinder-connector.php', getFileCallback: function (file) { window.opener.CKEDITOR.tools.callFunction(funcNum, file.url); window.close(); } });
-
/admin/elfinder-connector.phpis the connector of elFinder (see itsconnector.minimal.php): check the login first, then allow only one folder, e.g. with the roots option'path' => __DIR__ . '/../files/', 'URL' => '/files/'and anuploadAllowlist of file types.
A text field for a file (the former browserField()) works the same way: a
textField() with your own button which opens the file manager and writes the chosen URL
into the field.
FormHandler is licensed under the GNU Lesser General Public License Version 2.1 - see the LICENSE file for details