Skip to content
Open
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
Binary file added .DS_Store
Binary file not shown.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
composer.phar
composer.lock
/vendor/
/migration_todo.txt

# Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
Expand Down
170 changes: 79 additions & 91 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,138 +1,126 @@
# PDF Form Filling with FPDM
# FPDM - PDF Form Data Merge

## Package
A PHP library for filling PDF forms by merging data into form fields.

The FPDM class allows to fill out PDF forms, i.e. populate fields of a PDF file. It is **developed by Olivier Plathey**, author of the [FDPF Library](http://www.fpdf.org/), and has been released as [Skript 93](http://www.fpdf.org/en/script/script93.php).
> **Fork Notice**: This is a **PHP 8.x compatible fork** of [codeshell/fpdm](https://github.com/codeshell/fpdm), originally based on [Olivier Plathey's FPDM script](http://www.fpdf.org/en/script/script93.php).

I created this repository for the following reasons:
## Features

- make the current FPDM source available via [composer](https://packagist.org/packages/tmw/fpdm), autoload via classmaps
- bugfixing
- FIX: compatibility issues with PHP 7.x [e376dc1](https://github.com/codeshell/fpdm/commit/e376dc157655ded24c61e098199586f3325d63c1) v2.9.1
- FIX: filling forms in multiple files (wrong buffer usage, invalid offsets) [e376dc1](https://github.com/codeshell/fpdm/commit/e376dc157655ded24c61e098199586f3325d63c1) v2.9.1
- FIX: convert ASCII object names to utf-8 [1eddba7](https://github.com/codeshell/fpdm/commit/1eddba76f610690821e8c0b3753df337a6cf65f7) v2.9.2
- improvements (changes to the original codebase are prefixed with `//FIX: change description` and ended with `//ENDFIX`)
- ADD: support for checkboxes (disabled by default, activate with `$pdf->useCheckboxParser = true;`) [0375dd9](https://github.com/codeshell/fpdm/commit/0375dd95f05fd2d8d32d9ae1ab882fa0895b07b3) v2.9.2
- Fill PDF text fields from PHP arrays or FDF files
- Checkbox support with automatic state detection
- Native form flattening (experimental) - make fields read-only without external tools
- PHP 8.0+ compatible
- No external dependencies (pure PHP)

## Version

Based on version 2.9 (2017-05-11) available from [fpdf.org/en/script/script93.php](http://www.fpdf.org/en/script/script93.php).

_Note: If you find that a new version has been hosted on fpdf.org, please do not hesitate to drop me [a short note](https://github.com/codeshell/fpdm/issues) to make sure I do not miss it out._

This repository only contains the separate php class written for form filling (FPD**M**). If you are looking for a repository containing the main FPD**F** Library, please head over to [github.com/Setasign/FPDF](https://github.com/Setasign/FPDF).

Once again, all credits to Olivier Plathey for providing an easy to use script for form filling in addition to his FPDF library!

## Installation
## Installation

### Composer

The preferred way of making FPDM available in your app is to install it via composer with

`composer require tmw/fpdm`

## Usage

### Composer (autoload)

[autoload](https://getcomposer.org/doc/01-basic-usage.md#autoloading) FPDM class files by adding this to your code:

`require 'vendor/autoload.php';`

### Standalone Script (legacy)

Load the top level entry point by calling

`require_once '/abolute/path/to/fpdm.php';`

or

`require_once './relative/path/to/fpdm.php';`
```bash
composer require tmw/fpdm
```

## Customization to original code
### Manual

### classmaps vs. psr-4 (or: legacy code vs modern frameworks á la Laravel)
```php
require_once 'path/to/fpdm.php';
```

Autoloading classes with [namespaces](https://www.php.net/manual/en/language.namespaces.basics.php) and following [PSR-4: Autoloader](https://www.php-fig.org/psr/psr-4/) would be desireable. Especially reducing the risk of naming conflicts by using vendor namespaces.
## Usage

However, FPDM has been around for a long time and as such is used in many projects that use non-namespaced code (I refer to them as legacy projects). Legacy projects instantiate FPDM by calling `$mypdf = new FPDM()` which is unqualified but defaults to the global namespace with non-namespaced code.
### Basic Text Fields

Using psr-4 would autoload the class to a subnamespace (e.g. \codeshell\fpdm\FPDM) instead of the global namespace (e.g. \FPDM) thus breaking any legacy code no matter if it used `new FPDM()` or `new \FPDM()`.
```php
<?php
require 'vendor/autoload.php';

__Classmaps are a compromise.__ They allow taking advantage of composers autoloading and dependency management. Yet classes are added to the global namespace. Legacy projects can switch to composer without having to refactor their code. __Newer projects (e.g. utilizing frameworks like laravel, that heavily rely on namespaces) can still use legacy classes__ by using the fully qualified name (in this case the class name prefixed with global prefix operator as in `new \FPDM()`).
$fields = array(
'name' => 'John Doe',
'address' => '123 Main St',
'city' => 'New York',
);

That's my reasoning for using classmaps over psr-4 for FPDM. Please let me know if there are use cases where classmaps won't work with modern frameworks.
$pdf = new FPDM('template.pdf');
$pdf->Load($fields, true); // true = UTF-8, false = ISO-8859-1
$pdf->Merge();
$pdf->Output('F', 'filled.pdf'); // Save to file
```

### Checkboxes

I added support for checkboxes. The feature is not heavily tested but works for me. Can be enabled with `useCheckboxParser = true` like so:
Enable checkbox support to toggle checkboxes in your PDF forms:

```php
<?php
$fields = array(
'my_checkbox' => 'anything that evaluates to true.', // checkbox will be checked; Careful, that includes ANY non-empty string (even "no" or "unchecked")
'another_checkbox' => false, // checkbox will be UNchecked; empty string or 0 work as well
'agree_terms' => true, // Checked
'newsletter' => false, // Unchecked
);

$pdf = new FPDM('template.pdf');
$pdf->useCheckboxParser = true; // Checkbox parsing is ignored (default FPDM behaviour) unless enabled with this setting
$pdf = new FPDM('form.pdf');
$pdf->useCheckboxParser = true; // Enable checkbox support
$pdf->Load($fields, true);
$pdf->Merge();
$pdf->Output();
```

You don't have to figure out the technical names of checkbox states. They are retrieved during the parsing process.
Checkbox state names (e.g., `Yes`/`Off`, `Oui`/`Off`) are automatically detected during parsing.

## Original Info Page
_Everything below is mirrored from http://www.fpdf.org/en/script/script93.php ._
### Flatten (Experimental)

### Information
Make form fields read-only after filling to prevent users from editing:

Author: Olivier
```php
<?php
$pdf = new FPDM('form.pdf');
$pdf->Load(['name' => 'John Doe']);
$pdf->Flatten(); // Enable flatten mode
$pdf->Merge();
$pdf->Output('F', 'flattened.pdf');

License: FPDF
// Or use shorthand:
$pdf->Merge(true); // true = flatten
```

### Description
> **⚠️ Experimental**: This feature sets the ReadOnly flag on all form fields. The form structure is preserved but fields cannot be edited. This is a native PHP implementation that doesn't require external tools like pdftk.

This script allows to merge data into a PDF form. Given a template PDF with text fields, it's
possible to inject values in two different ways:
## Output Options

- from a PHP array
- from an <abbr title="Forms Data Format">FDF</abbr> file
```php
$pdf->Output(); // Send to browser (inline)
$pdf->Output('D', 'f.pdf'); // Force download
$pdf->Output('F', 'f.pdf'); // Save to file
$pdf->Output('S'); // Return as string
```

The resulting document is produced by the Output() method, which works the same as for FPDF.
## Debugging

Note: if your template PDF is not compatible with this script, you can process it with
[PDFtk](https://www.pdflabs.com/tools/pdftk-server/) this way:
Enable verbose mode to see parsing details:

`pdftk modele.pdf output modele2.pdf`
```php
$pdf->set_modes('verbose', true);
$pdf->set_modes('verbose_level', 3); // 1-4, higher = more detail
```

Then try again with modele2.pdf.
> **Note**: Disable verbose mode before generating PDF output.

### Example
## Fork Changes

This example shows how to merge data from an array:
This fork includes:

```php
<?php
- Full PHP 8.0 - 8.5 compatibility
- Enhanced checkbox parser with support for indirect `/AP` references
- Native form flattening (experimental) - sets ReadOnly flags without requiring pdftk
- Replaced deprecated `create_function()` with closures
- Fixed curly brace string access syntax
- Fixed `implode()` parameter order
- Added missing class property declarations

/***************************
Sample using a PHP array
****************************/
## Credits

$fields = array(
'name' => 'My name',
'address' => 'My address',
'city' => 'My city',
'phone' => 'My phone number'
);
- **Original Author**: [Olivier Plathey](http://www.fpdf.org/) (FPDM v2.9)
- **Upstream Fork**: [codeshell/fpdm](https://github.com/codeshell/fpdm)

$pdf = new FPDM('template.pdf');
$pdf->Load($fields, false); // second parameter: false if field values are in ISO-8859-1, true if UTF-8
$pdf->Merge();
$pdf->Output();
?>
```
## License

View the result [here](http://www.fpdf.org/en/script/ex93.pdf).
FPDF License
6 changes: 5 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
}
],
"require": {
"php": ">=5.3.0"
"php": ">=8.0"
},
"keywords": [
"FPDM",
Expand All @@ -45,5 +45,9 @@
"support": {
"issues": "https://github.com/codeshell/fpdm/issues",
"source": "https://github.com/codeshell/fpdm/tree/master"
},
"require-dev": {
"squizlabs/php_codesniffer": "^3.13",
"phpcompatibility/php-compatibility": "^9.3"
}
}
Binary file added src/.DS_Store
Binary file not shown.
3 changes: 2 additions & 1 deletion src/ex-array.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

$pdf = new FPDM('template.pdf');
$pdf->Load($fields, false); // second parameter: false if field values are in ISO-8859-1, true if UTF-8
$pdf->Flatten();
$pdf->Merge();
$pdf->Output();
$pdf->Output('F', 'output.pdf');
?>
Binary file removed src/ex.pdf
Binary file not shown.
22 changes: 11 additions & 11 deletions src/export/fdf/forge_fdf.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,17 @@ function escape_pdf_string( $ss )
$ss_esc= '';
$ss_len= strlen( $ss );
for( $ii= 0; $ii< $ss_len; ++$ii ) {
if( ord($ss{$ii})== 0x28 || // open paren
ord($ss{$ii})== 0x29 || // close paren
ord($ss{$ii})== 0x5c ) // backslash
if( ord($ss[$ii])== 0x28 || // open paren
ord($ss[$ii])== 0x29 || // close paren
ord($ss[$ii])== 0x5c ) // backslash
{
$ss_esc.= chr(0x5c).$ss{$ii}; // escape the character w/ backslash
$ss_esc.= chr(0x5c).$ss[$ii]; // escape the character w/ backslash
}
else if( ord($ss{$ii}) < 32 || 126 < ord($ss{$ii}) ) {
$ss_esc.= sprintf( "\\%03o", ord($ss{$ii}) ); // use an octal code
else if( ord($ss[$ii]) < 32 || 126 < ord($ss[$ii]) ) {
$ss_esc.= sprintf( "\\%03o", ord($ss[$ii]) ); // use an octal code
}
else {
$ss_esc.= $ss{$ii};
$ss_esc.= $ss[$ii];
}
}
return $ss_esc;
Expand All @@ -55,13 +55,13 @@ function escape_pdf_name( $ss )
$ss_esc= '';
$ss_len= strlen( $ss );
for( $ii= 0; $ii< $ss_len; ++$ii ) {
if( ord($ss{$ii}) < 33 || 126 < ord($ss{$ii}) ||
ord($ss{$ii})== 0x23 ) // hash mark
if( ord($ss[$ii]) < 33 || 126 < ord($ss[$ii]) ||
ord($ss[$ii])== 0x23 ) // hash mark
{
$ss_esc.= sprintf( "#%02x", ord($ss{$ii}) ); // use a hex code
$ss_esc.= sprintf( "#%02x", ord($ss[$ii]) ); // use a hex code
}
else {
$ss_esc.= $ss{$ii};
$ss_esc.= $ss[$ii];
}
}
return $ss_esc;
Expand Down
19 changes: 4 additions & 15 deletions src/export/pdf/pdftk.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
******************************************************************/

if (!defined('URL_TOOLBOX')) die("Requires the URL_TOOLBOX package!");

define("PHP5_ENGINE",version_compare(phpversion(), "5"));


//!NOTE try to detect your OS

Expand Down Expand Up @@ -104,22 +103,12 @@ function pdftk($pdf_file,$fdf_file,$settings) {

//echo htmlentities("$cmdline , $descriptorspec, $cwd, $env");

if(PHP5_ENGINE) { // Php5
$process = proc_open($cmdline, $descriptorspec, $pipes, $cwd, $env);
}else { //Php4
$process = proc_open($cmdline, $descriptorspec, $pipes);
}
$process = proc_open($cmdline, $descriptorspec, $pipes, $cwd, $env);

if (is_resource($process)) {

if(PHP5_ENGINE) {
$err=stream_get_contents($pipes[2]);
}else { //Php4
$err= "";
while (($str = fgets($pipes[2], 4096))) {
$err.= "$str\n";
}
}
$err=stream_get_contents($pipes[2]);


fclose($pipes[2]);

Expand Down
7 changes: 2 additions & 5 deletions src/filters/FilterASCII85.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
if (!defined('ORD_tilde'))
define('ORD_tilde', ord('~'));

$__tmp = version_compare(phpversion(), "5") == -1 ? array('FilterASCII85') : array('FilterASCII85', false);
if (!call_user_func_array('class_exists', $__tmp)) {
if (!class_exists('FilterASCII85', false)) {


if(isset($FPDM_FILTERS)) array_push($FPDM_FILTERS,"ASCII85Decode");
Expand Down Expand Up @@ -103,6 +102,4 @@ function encode($in) {
return $this->error("ASCII85 encoding not implemented.");
}
}
}

unset($__tmp);
}
6 changes: 3 additions & 3 deletions src/filters/FilterASCIIHex.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class FilterASCIIHex {
*@internal same as _hex2bin ($hexString)
*@access public
*@note Function was written because PHP has a bin2hex, but not a hex2bin!
*@internal note pack(�C�,hexdec(substr($data,$i,2))) DOES NOT WORK
*@internal note pack(“C”,hexdec(substr($data,$i,2))) DOES NOT WORK
*
**/
function decode($data) {
Expand Down Expand Up @@ -44,7 +44,7 @@ function decode($data) {
*
*@internal same as bin2hex
*@access public
*@internal dechex(ord($str{$i})); is buggy because for hex value of 0-15 heading 0 is missing! Using sprintf() to get it right.
*@internal dechex(ord($str[$i])); is buggy because for hex value of 0-15 heading 0 is missing! Using sprintf() to get it right.
*@param string $str a binary string
*@return string hex the hexified string
**/
Expand All @@ -54,7 +54,7 @@ function encode($data) {
$hex = "";
$i = 0;
do {
$hex .= sprintf("%02x", ord($str{$i}));
$hex .= sprintf("%02x", ord($str[$i]));
$i++;
} while ($i < strlen($str));
return $hex;
Expand Down
3 changes: 1 addition & 2 deletions src/filters/FilterFlate.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
// NOTE: requires ZLIB >= 1.0.9!
//

$__tmp = version_compare(phpversion(), "5") == -1 ? array('FilterFlateDecode') : array('FilterFlateDecode', false);
if (!call_user_func_array('class_exists', $__tmp)) {
if (!class_exists('FilterFlate', false)) {


if(isset($FPDM_FILTERS)) array_push($FPDM_FILTERS,"FlateDecode");
Expand Down
Loading