vendor/mikehaertl/php-pdftk/src/XfdfFile.php line 209

Open in your IDE?
  1. <?php
  2. namespace mikehaertl\pdftk;
  3. use mikehaertl\tmp\File;
  4. /**
  5.  * XfdfFile
  6.  *
  7.  * This class represents a temporary XFDF file that can be used to fill a PDF
  8.  * form with valid unicode characters.
  9.  *
  10.  * Form data must be passed to the constructor as an array in this form:
  11.  *
  12.  * ```
  13.  * [
  14.  *     // Field name => field value
  15.  *     'Firstname' => 'John',
  16.  *
  17.  *     // Hierarchical/nested fields in dot notation
  18.  *     'Address.Street' => 'Some Street',
  19.  *     'Address.City' => 'Any City',
  20.  *
  21.  *     // Multi value fields
  22.  *     'Pets' => ['Cat', 'Mouse'],
  23.  * ]
  24.  * ```
  25.  *
  26.  * This will result in the following XML structure (header/footer omitted):
  27.  *
  28.  * ```
  29.  * <field name="Firstname">
  30.  *   <Value>John</Value>
  31.  * </field>
  32.  * <field name="Address">
  33.  *   <field name="Street">
  34.  *     <Value>Some Street</Value>
  35.  *   </field>
  36.  *   <field name="City">
  37.  *     <Value>Any City</Value>
  38.  *   </field>
  39.  * </field>
  40.  * <field name="Pets">
  41.  *   <Value>Cat</Value>
  42.  *   <Value>Mouse</Value>
  43.  * </field>
  44.  * ```
  45.  *
  46.  * @author Tomas Holy <holy@interconnect.cz>
  47.  * @author Michael Härtl <haertl.mike@gmail.com>
  48.  * @license http://www.opensource.org/licenses/MIT
  49.  */
  50. class XfdfFile extends File
  51. {
  52.     // XFDF file header
  53.     const XFDF_HEADER = <<<FDF
  54. <?xml version="1.0" encoding="UTF-8"?>
  55. <xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
  56. <fields>
  57. FDF;
  58.     // XFDF file footer
  59.     const XFDF_FOOTER = <<<FDF
  60. </fields>
  61. </xfdf>
  62. FDF;
  63.     /**
  64.      * Constructor
  65.      *
  66.      *
  67.      * @param array $data the form data as name => value
  68.      * @param string|null $suffix the optional suffix for the tmp file
  69.      * @param string|null $prefix the optional prefix for the tmp file. If null
  70.      * 'php_tmpfile_' is used.
  71.      * @param string|null $directory directory where the file should be
  72.      * created. Autodetected if not provided.
  73.      * @param string|null $encoding of the data. Default is 'UTF-8'.
  74.      */
  75.     public function __construct($data$suffix null$prefix null$directory null$encoding 'UTF-8')
  76.     {
  77.         if ($directory === null) {
  78.             $directory self::getTempDir();
  79.         }
  80.         if ($suffix === null) {
  81.             $suffix '.xfdf';
  82.         }
  83.         if ($prefix === null) {
  84.             $prefix 'php_pdftk_xfdf_';
  85.         }
  86.         $tempfile tempnam($directory$prefix);
  87.         $this->_fileName $tempfile $suffix;
  88.         rename($tempfile$this->_fileName);
  89.         $fields $this->parseData($data$encoding);
  90.         $this->writeXml($fields);
  91.     }
  92.     /**
  93.      * Parses an array of key/value data into a nested array structure.
  94.      *
  95.      * The data may use keys in dot notation (#55). Values can also be arrays in
  96.      * case of multi value fields (#148). To make both distinguishable in the
  97.      * result array keys that represent field names are prefixed with `_`. This
  98.      * also allows for numeric field names (#260).
  99.      *
  100.      * For example an array like this:
  101.      *
  102.      * ```
  103.      * [
  104.      *     'a' => 'value a',
  105.      *     'b.x' => 'value b.x',
  106.      *     'b.y' => 'value b.y',
  107.      *
  108.      *     'c.0' => 'val c.0',
  109.      *     'c.1' => 'val c.1',
  110.      *
  111.      *     'd' => ['m1', 'm2'],
  112.      * ]
  113.      * ```
  114.      *
  115.      * Will become:
  116.      *
  117.      * ```
  118.      * [
  119.      *     '_a' => 'value a',
  120.      *     '_b' => [
  121.      *         '_x' => 'value b.x',
  122.      *         '_y' => 'value b.y',
  123.      *     ],
  124.      *     '_c' => [
  125.      *         '_0' => 'value c.0',
  126.      *         '_1' => 'value c.1',
  127.      *     ],
  128.      *     '_d' => [
  129.      *         // notice the missing underscore in the keys
  130.      *         0 => 'm1',
  131.      *         1 => 'm2',
  132.      *     ],
  133.      * ]
  134.      *
  135.      *
  136.      * @param mixed $data the data to parse
  137.      * @param string the encoding of the data
  138.      * @return array the result array in UTF-8 encoding with dot keys converted
  139.      * to nested arrays
  140.      */
  141.     protected function parseData($data$encoding)
  142.     {
  143.         $result = array();
  144.         foreach ($data as $key => $value) {
  145.             if ($encoding !== 'UTF-8' && function_exists('mb_convert_encoding')) {
  146.                 $key mb_convert_encoding($key'UTF-8'$encoding);
  147.                 $value mb_convert_encoding($value'UTF-8'$encoding);
  148.             }
  149.             if (strpos($key'.') === false) {
  150.                 $result['_' $key] = $value;
  151.             } else {
  152.                 $target = &$result;
  153.                 $keyParts explode('.'$key);
  154.                 $lastPart array_pop($keyParts);
  155.                 foreach ($keyParts as $part) {
  156.                     if (!isset($target['_' $part])) {
  157.                         $target['_' $part] = array();
  158.                     }
  159.                     $target = &$target['_' $part];
  160.                 }
  161.                 $target['_' $lastPart] = $value;
  162.             }
  163.         }
  164.         return $result;
  165.     }
  166.     /**
  167.      * Write the given fields to an XML file
  168.      *
  169.      * @param array $fields the fields in a nested array structure
  170.      */
  171.     protected function writeXml($fields)
  172.     {
  173.         // Use fwrite, since file_put_contents() messes around with character encoding
  174.         $fp fopen($this->_fileName'w');
  175.         fwrite($fpself::XFDF_HEADER);
  176.         $this->writeFields($fp$fields);
  177.         fwrite($fpself::XFDF_FOOTER);
  178.         fclose($fp);
  179.     }
  180.     /**
  181.      * Write the fields to the given filepointer
  182.      *
  183.      * @param int $fp
  184.      * @param mixed[] $fields an array of field values as returned by
  185.      * `parseData()`.
  186.      */
  187.     protected function writeFields($fp$fields)
  188.     {
  189.         foreach ($fields as $key => $value) {
  190.             $key $this->xmlEncode(substr($key,1));
  191.             fwrite($fp"<field name=\"$key\">\n");
  192.             if (!is_array($value)) {
  193.                 $value = array($value);
  194.             }
  195.             if (array_key_exists(0$value)) {
  196.                 // Numeric keys: single or multi-value field
  197.                 foreach($value as $val) {
  198.                     $val $this->xmlEncode($val);
  199.                     fwrite($fp"<value>$val</value>\n");
  200.                 }
  201.             } else {
  202.                 // String keys: nested/hierarchical fields
  203.                 $this->writeFields($fp$value);
  204.             }
  205.             fwrite($fp"</field>\n");
  206.         }
  207.     }
  208.     /**
  209.      * @param string $value the value to encode
  210.      * @return string the value correctly encoded for use in a XML document
  211.      */
  212.     protected function xmlEncode($value)
  213.     {
  214.         return defined('ENT_XML1') ?
  215.             htmlspecialchars($valueENT_XML1'UTF-8') :
  216.             htmlspecialchars($value);
  217.     }
  218. }