Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,62 @@ All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and the project follows [Semantic Versioning](https://semver.org/).

## [4.8.0] - unreleased

### Added

- `{id}` in the `name` of the upload config of `uploadField()` is replaced by the id of the
record, e.g. `'name' => 'spieler_{id}'`. An edit form uses the id from the URL. A new
record uploads the file after the INSERT with the id from `getInsertId()` and then saves
the filename in the column of the field (an UPDATE); when the INSERT fails, no file is
moved. `onCorrect` gets an empty filename for a new record, `onSaved` the real one. It
needs a `dbFormHandler` with a table and a single primary key; otherwise it throws a
`FormHandlerException`, as well as for an id with other characters than letters, digits,
`_` and `-`, and for a file posted on an earlier page of a form with more pages.
- `{id}` in the filename of `resizeImage()` (`$saveAs`) is replaced by the same id, e.g.
`resizeImage('bild', $dir . 'spieler_{id}.jpg', 200)`. On a new record the upload of the
field waits for the INSERT then, also when its own filename has no `{id}`. `FormHandler`
throws a `FormHandlerException` for it.
- `dbFormHandler::getEditId()` returns the id of the edited record (`null` for a new one).
- `dbFormHandler::getNewId()` returns the id the INSERT gave a new record (an array with
more primary keys; `null` before the INSERT, on an edit form and when the INSERT failed).
It is set right after the INSERT, so `onSaved` can use it already. `getRecordId()`
returns the id of the record the form works on: `getNewId() ?? getEditId()`. Projects
no longer have to override `_saveDbData()` to remember the id.
- `dbFormHandler::getDatabaseAdapter()` returns the database adapter of the form.
- `dbFormHandler::setCopyOf($id)` copies a record: the form shows the record with this id
and saves it as a new record (an INSERT). Call it before `dbInfo()`,
`setConnectionResource()` and `setDatabaseAdapter()`, on every request of the form.
The primary keys and the files of the uploadfields are not copied (both records would
use the same file), the values of the junction tables are. `isCopy()` and
`getCopyOfId()` tell if and which record is copied; `getEditId()` is `null` for a copy,
`getNewId()` and `getRecordId()` return the id of the new record after saving. Unique
fields are checked like on a new record. A record which does not exist throws a
`FormHandlerException`. Projects no longer have to change `$insert`, `$edit` and `$_id`
themselves.
- `FormHandler::joinNextFields($count, $separator = ' ')` shows the next fields in one row
of the mask, e.g. a select field and a text field. The title, help and value of the
first field fill the mask, the fields are put together with the separator (HTML), and
the error messages of all of them are shown. Hidden fields are not counted; HTML, a
line, a fieldset, a new mask or page ends the row earlier. The row uses the mask once,
so a mask set with `setMask()` applies to the fields after it as before. Projects no
longer have to change `%field%` in the mask themselves.

### Changed

- `dbFormHandler` keeps its database adapter after saving the record (it was unset), so
`onSaved` and the code after `flush()` can still query the database, e.g. to compare
the old and the new values. Projects no longer have to remember the connection.

### Security

- `cancelButton()` escapes its URL: it is put into the javascript as a JSON string and the
`onclick` attribute is HTML escaped, so a URL from the request with `'` or `"` can not
break out any more. `&` (from a HTML link) is decoded first. A URL with another
scheme than `http`/`https` (e.g. `javascript:`, `data:`) goes back in the history, like a
button without a URL. Only a history call such as `history.back(-1)` or `history.go(-2)`
is used as javascript; before, every URL containing "history" was (e.g. `/history/x.php`).

## [4.7.0] - 2026-10-30

### Added
Expand Down
75 changes: 75 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,81 @@ this page".
with `fh_conf('FH_CSRF_PROTECTION', false)` before the first form is created (the
`fh_conf()` values become constants then).

**Copy a record** (4.8). `setCopyOf()` shows a record and saves it as a new one:

```php
$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:

```php
$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.

### Uploads with the id of the record in the filename

`{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`.

```php
$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()` and
`mergeImage()` 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 of `resizeImage()`, 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}`.
- `onCorrect` runs before the INSERT: for a new record the filename is still empty there,
`onSaved` gets it. `isUploaded()` and `getFileInfo()` 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 `dbFormHandler` with a table and a single primary key. `FormHandler` throws a
`FormHandlerException`, 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.

### Images and files in the editor

The bundled file manager is removed (4.7): it had no login. Uploading or choosing files
Expand Down
Loading