From 0683ca830133af598dbc8886a52f58f8786db0aa Mon Sep 17 00:00:00 2001 From: dorian Date: Sun, 14 Apr 2013 16:54:56 +0200 Subject: [PATCH] Add a way to fill pdf forms with key/value pairs --- src/Shuble/Slurpy/Factory.php | 6 + src/Shuble/Slurpy/Operation/BaseOperation.php | 9 + .../Slurpy/Operation/FillFormOperation.php | 31 ++- .../Operation/OperationArgument/FormData.php | 185 ++++++++++++++++++ .../Slurpy/Operation/OperationInterface.php | 7 + src/Shuble/Slurpy/Slurpy.php | 7 + .../Tests/Operation/FillFormOperationTest.php | 31 +++ .../OperationArgument/FormDataTest.php | 116 +++++++++++ tests/Shuble/Slurpy/Tests/SlurpyTest.php | 14 +- 9 files changed, 395 insertions(+), 11 deletions(-) create mode 100644 src/Shuble/Slurpy/Operation/OperationArgument/FormData.php create mode 100644 tests/Shuble/Slurpy/Tests/Operation/OperationArgument/FormDataTest.php diff --git a/src/Shuble/Slurpy/Factory.php b/src/Shuble/Slurpy/Factory.php index ef283a5..8b1319e 100644 --- a/src/Shuble/Slurpy/Factory.php +++ b/src/Shuble/Slurpy/Factory.php @@ -10,6 +10,8 @@ namespace Shuble\Slurpy; +use Shuble\Slurpy\Operation\OperationArgument\FormData; + use Shuble\Slurpy\Operation\AttachFilesOperation; use Shuble\Slurpy\Operation\UpdateInfoOperation; use Shuble\Slurpy\Operation\UnpackFilesOperation; @@ -186,6 +188,10 @@ public function fillForm($input, $dataFile, $output, $flatten = false) $slurpy->addInput($inputFile); $operation = new FillFormOperation(); + $dataFile = is_array($dataFile) + ? new FormData($dataFile) + : $dataFile + ; $operation->setDataFile($dataFile); $slurpy->setOperation($operation); diff --git a/src/Shuble/Slurpy/Operation/BaseOperation.php b/src/Shuble/Slurpy/Operation/BaseOperation.php index b2e2590..e999246 100644 --- a/src/Shuble/Slurpy/Operation/BaseOperation.php +++ b/src/Shuble/Slurpy/Operation/BaseOperation.php @@ -14,4 +14,13 @@ public function getArguments() { return array(); } + + /** + * (non-PHPdoc) + * @see \Shuble\Slurpy\Operation\OperationInterface::getStdin() + */ + public function getStdin() + { + return null; + } } diff --git a/src/Shuble/Slurpy/Operation/FillFormOperation.php b/src/Shuble/Slurpy/Operation/FillFormOperation.php index 8f6ae1e..cdab090 100644 --- a/src/Shuble/Slurpy/Operation/FillFormOperation.php +++ b/src/Shuble/Slurpy/Operation/FillFormOperation.php @@ -10,21 +10,22 @@ namespace Shuble\Slurpy\Operation; -use Shuble\Slurpy\Operation\OperationInterface; +use Shuble\Slurpy\Operation\BaseOperation; +use Shuble\Slurpy\Operation\OperationArgument\FormData; -class FillFormOperation implements OperationInterface +class FillFormOperation extends BaseOperation { /** - * Path to datafile used to fill the form + * Path to datafile used to fill the form or a FormData instance * - * @var string + * @var FormData|string */ protected $dataFile; /** * Set data filepath used to fill the form * - * @param string $dataFile + * @param FormData|string $dataFile * * @return FillFormOperation */ @@ -37,7 +38,8 @@ public function setDataFile($dataFile) /** * Get data filepath used to fill the form - * @return string + * + * @return FormData|string */ public function getDataFile() { @@ -59,6 +61,21 @@ public function getName() */ public function getArguments() { - return array($this->dataFile); + return $this->dataFile instanceof FormData + ? array('-') + : array($this->dataFile) + ; + } + + /** + * (non-PHPdoc) + * @see \Shuble\Slurpy\Operation\OperationInterface::getStdin() + */ + public function getStdin() + { + return $this->dataFile instanceof FormData + ? $this->dataFile->getXfdf('bla') + : null + ; } } diff --git a/src/Shuble/Slurpy/Operation/OperationArgument/FormData.php b/src/Shuble/Slurpy/Operation/OperationArgument/FormData.php new file mode 100644 index 0000000..3aad3dd --- /dev/null +++ b/src/Shuble/Slurpy/Operation/OperationArgument/FormData.php @@ -0,0 +1,185 @@ + + */ +class FormData +{ + /** + * An array of fields indexed by field names + * + * @var array + */ + protected $fields = array(); + + /** + * Constructor + * + * @param array $fields An array of fields indexed by field names + */ + public function __construct($fields = array()) + { + $this->addFields($fields); + } + + /** + * Add fields + * + * @param array $fields An array of fields indexed by field names + * + * @return FormData + */ + public function addFields($fields) + { + foreach ($fields as $fieldName => $value) { + $this->addField($fieldName, $value); + } + + return $this; + } + + /** + * Set fields + * + * @param array $fields An array of fields indexed by field names + * + * @return FormData + */ + public function setFields($fields) + { + foreach ($fields as $fieldName => $value) { + $this->setField($fieldName, $value); + } + + return $this; + } + + /** + * Get fields + * + * @return array An array of fields indexed by field names + */ + public function getFields() + { + return $this->fields; + } + + /** + * Check whether a field is defined or not + * + * @param string $fieldName + */ + public function hasField($fieldName) + { + return isset($this->fields[$fieldName]); + } + + /** + * Adds a field + * + * @param string $fieldName Name of the field + * @param string $value Value of the field + * + * @throws \InvalidArgumentException If the field is already defined + * + * @return FormData + */ + public function addField($fieldName, $value) + { + if ($this->hasField($fieldName)) { + throw new \InvalidArgumentException(sprintf('Field "%s" is already defined', $fieldName)); + } + + $this->fields[$fieldName] = $value; + + return $this; + } + + /** + * Sets a field name and value + * + * @param string $fieldName Name of the field + * @param string $value Value of the field + * + * @throws \InvalidArgumentException If the field is not defined + * + * @return FormData + */ + public function setField($fieldName, $value) + { + if (!$this->hasField($fieldName)) { + throw new \InvalidArgumentException(sprintf('Field "%s" is not defined', $fieldName)); + } + + $this->fields[$fieldName] = $value; + + return $this; + } + + /** + * Gets a field value from it's name + * + * @param string fieldName Name of the field + * + * @throws \InvalidArgumentException If the field is not defined + * + * @return string Value of the field + */ + public function getField($fieldName) + { + if (!$this->hasField($fieldName)) { + throw new \InvalidArgumentException(sprintf('Field "%s" is not defined', $fieldName)); + } + + return $this->fields[$fieldName]; + } + + /** + * Get the FormData under XFDF format + * + * @param string $file filepath or url of the pdf + * @param string $encoding Encoding (Default: UTF-8) + * + * @return string XFDF resulting file + */ + public function getXfdf($file, $modified = null, $encoding = 'UTF-8') + { + $fields = array(); + foreach ($this->fields as $fieldName => $value) { + $fields[] = sprintf('%s', + htmlspecialchars($fieldName), + htmlspecialchars($value) + ); + } + $fields = implode("", $fields); + $original = md5($file); + $modified = null === $modified ? time() : $modified; + $href = $file; + + $xfdf = +<< + + + {$fields} + + + + +XML; + + return $xfdf; + } +} diff --git a/src/Shuble/Slurpy/Operation/OperationInterface.php b/src/Shuble/Slurpy/Operation/OperationInterface.php index f22b634..e542a90 100644 --- a/src/Shuble/Slurpy/Operation/OperationInterface.php +++ b/src/Shuble/Slurpy/Operation/OperationInterface.php @@ -17,4 +17,11 @@ public function getName(); * @return array */ public function getArguments(); + + /** + * Get the operation's stdin + * + * @return null|string + */ + public function getStdin(); } diff --git a/src/Shuble/Slurpy/Slurpy.php b/src/Shuble/Slurpy/Slurpy.php index 64c960a..4955ea8 100644 --- a/src/Shuble/Slurpy/Slurpy.php +++ b/src/Shuble/Slurpy/Slurpy.php @@ -366,6 +366,13 @@ protected function buildCommand(array $options = array()) $args = implode(' ', array_map('escapeshellarg', $args)); $command .= ' '. $args; } + + if (null !== $this->operation->getStdin()) { + $command = sprintf("echo %s | %s", + escapeshellarg($this->operation->getStdin()), + $command + ); + } } // Output diff --git a/tests/Shuble/Slurpy/Tests/Operation/FillFormOperationTest.php b/tests/Shuble/Slurpy/Tests/Operation/FillFormOperationTest.php index f6a1d27..d3fb19b 100644 --- a/tests/Shuble/Slurpy/Tests/Operation/FillFormOperationTest.php +++ b/tests/Shuble/Slurpy/Tests/Operation/FillFormOperationTest.php @@ -14,6 +14,11 @@ public function testGetSetDataFile() $operation->setDataFile($file); $this->assertEquals($file, $operation->getDataFile()); + + $dataFile = $this->getMock('Shuble\Slurpy\Operation\OperationArgument\FormData'); + $operation->setDataFile($dataFile); + + $this->assertEquals($dataFile, $operation->getDataFile()); } public function testGetName() @@ -31,5 +36,31 @@ public function testGetArguments() $operation->setDataFile($file); $this->assertEquals(array($file), $operation->getArguments()); + + $dataFile = $this->getMock('Shuble\Slurpy\Operation\OperationArgument\FormData'); + $operation->setDataFile($dataFile); + + $this->assertEquals(array('-'), $operation->getArguments()); + } + + public function testGetStdin() + { + $operation = new FillFormOperation(); + + $file = 'path/to/file'; + $operation->setDataFile($file); + + $this->assertEquals(null, $operation->getStdin()); + + $dataFile = $this->getMock('Shuble\Slurpy\Operation\OperationArgument\FormData'); + $dataFile + ->expects($this->once()) + ->method('getXfdf') + ->will($this->returnValue('foobar')) + ; + + $operation->setDataFile($dataFile); + + $this->assertEquals('foobar', $operation->getStdin()); } } diff --git a/tests/Shuble/Slurpy/Tests/Operation/OperationArgument/FormDataTest.php b/tests/Shuble/Slurpy/Tests/Operation/OperationArgument/FormDataTest.php new file mode 100644 index 0000000..8d11515 --- /dev/null +++ b/tests/Shuble/Slurpy/Tests/Operation/OperationArgument/FormDataTest.php @@ -0,0 +1,116 @@ +formData = new FormData(); + } + + public function testCreation() + { + $this->assertEquals(array(), $this->formData->getFields()); + } + + public function testAddGetSetField() + { + $fieldName = 'field'; + $fieldValue = 'value'; + + $message = '->getField throw exception if the field does not exist'; + try { + $this->formData->getField($fieldName); + $this->fail($message); + } catch (\InvalidArgumentException $e) { + $this->anything($message); + } + + $message = '->setField throw exception if the field does not exist'; + try { + $this->formData->setField($fieldName, $fieldValue); + $this->fail($message); + } catch (\InvalidArgumentException $e) { + $this->anything($message); + } + + $this->formData->addField($fieldName, $fieldValue); + $this->assertEquals($fieldValue, $this->formData->getField($fieldName)); + + $message = '->addField throw exception if the field already exist'; + try { + $this->formData->addField($fieldName, $fieldValue); + $this->fail($message); + } catch (\InvalidArgumentException $e) { + $this->anything($message); + } + + $this->formData->setField($fieldName, 'value2'); + $this->assertEquals('value2', $this->formData->getField($fieldName)); + } + + public function testAddGetSetFields() + { + $fields = array( + 'field1' => 'value1', + 'field2' => 'value2', + ); + + $message = '->setFields throw exception if any of the fields does not exist'; + try { + $this->formData->setFields($fields); + $this->fail($message); + } catch (\InvalidArgumentException $e) { + $this->anything($message); + } + + $this->formData->addFields($fields); + $this->assertEquals($fields, $this->formData->getFields($fields)); + + $message = '->addFields throw exception if any of the fields already exist'; + try { + $this->formData->addFields($fields); + $this->fail($message); + } catch (\InvalidArgumentException $e) { + $this->anything($message); + } + + $this->formData->setFields($fields); + $this->assertEquals($fields, $this->formData->getFields($fields)); + } + + public function testGetXfdf() + { + $fields = array( + 'field1' => 'value1', + 'field2' => 'value2', + ); + $this->formData->addFields($fields); + + $modified = time(); + $file = '/path/to/file.pdf'; + $original = md5($file); + $encoding = 'UTF-8'; + $expected = +<< + + + value1value2 + + + + +XML; + + $actual = $this->formData->getXfdf($file, $modified, $encoding); + + $this->assertXmlStringEqualsXmlString($expected, $actual); + } + +} diff --git a/tests/Shuble/Slurpy/Tests/SlurpyTest.php b/tests/Shuble/Slurpy/Tests/SlurpyTest.php index 9f59ca2..6477d5d 100644 --- a/tests/Shuble/Slurpy/Tests/SlurpyTest.php +++ b/tests/Shuble/Slurpy/Tests/SlurpyTest.php @@ -250,6 +250,12 @@ public function dataForBuildCommand() ->will($this->returnValue(array('arg1', 'arg2'))) ; + $op + ->expects($this->any()) + ->method('getStdin') + ->will($this->returnValue('foobar')) + ; + return array( array( 'thebinary', @@ -281,7 +287,7 @@ public function dataForBuildCommand() '/the/output/path', $op, array('flatten' => null), - 'thebinary HANDLE=/path input_pw HANDLE=pa$$w0rd operation \'arg1\' \'arg2\' output /the/output/path' + 'echo \'foobar\' | thebinary HANDLE=/path input_pw HANDLE=pa$$w0rd operation \'arg1\' \'arg2\' output /the/output/path' ), array( 'thebinary', @@ -289,7 +295,7 @@ public function dataForBuildCommand() '/the/output/path', $op, array('flatten' => false), - 'thebinary HANDLE=/path input_pw HANDLE=pa$$w0rd operation \'arg1\' \'arg2\' output /the/output/path' + 'echo \'foobar\' | thebinary HANDLE=/path input_pw HANDLE=pa$$w0rd operation \'arg1\' \'arg2\' output /the/output/path' ), array( 'thebinary', @@ -297,7 +303,7 @@ public function dataForBuildCommand() '/the/output/path', $op, array('user_pw' => 'foo'), - 'thebinary HANDLE=/path input_pw HANDLE=pa$$w0rd operation \'arg1\' \'arg2\' output /the/output/path user_pw \'foo\'' + 'echo \'foobar\' | thebinary HANDLE=/path input_pw HANDLE=pa$$w0rd operation \'arg1\' \'arg2\' output /the/output/path user_pw \'foo\'' ), array( 'thebinary', @@ -305,7 +311,7 @@ public function dataForBuildCommand() '/the/output/path', $op, array('allow' => array('foo', 'bar')), - 'thebinary HANDLE=/path input_pw HANDLE=pa$$w0rd operation \'arg1\' \'arg2\' output /the/output/path allow \'foo\' \'bar\'' + 'echo \'foobar\' | thebinary HANDLE=/path input_pw HANDLE=pa$$w0rd operation \'arg1\' \'arg2\' output /the/output/path allow \'foo\' \'bar\'' ), ); }