Commit eece7ccf authored by Carsten  Rose's avatar Carsten Rose
Browse files

Feature 4901 / PDF Split: First implemention - SVG will be created per page.

parent 10e4a0e4
...@@ -61,14 +61,14 @@ For the `download`_ function, the programs `pdftk` and `file` are necessary to c ...@@ -61,14 +61,14 @@ For the `download`_ function, the programs `pdftk` and `file` are necessary to c
Preparation for Ubuntu 14.04:: Preparation for Ubuntu 14.04::
sudo apt-get install php5-mysqlnd php5-intl sudo apt-get install php5-mysqlnd php5-intl
sudo apt-get install pdftk file # for file upload and PDF sudo apt-get install pdftk file pdf2svg # for file upload and PDF, PDF split
sudo php5enmod mysqlnd sudo php5enmod mysqlnd
sudo service apache2 restart sudo service apache2 restart
Preparation for Ubuntu 16.04:: Preparation for Ubuntu 16.04::
sudo apt install php7.0-intl sudo apt install php7.0-intl
sudo apt install pdftk libxrender1 file # for file upload, PDF and 'HTML to PDF' (wkhtmltopdf) sudo apt install pdftk libxrender1 file pdf2svg # for file upload, PDF, PDF split and 'HTML to PDF' (wkhtmltopdf)
.. _wkhtml: .. _wkhtml:
...@@ -1305,10 +1305,10 @@ Store: *VARS* - V ...@@ -1305,10 +1305,10 @@ Store: *VARS* - V
+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+ +-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| slaveId | see *FormElement* `action` | | slaveId | see *FormElement* `action` |
+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+ +-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| filename | Original filename of an uploaded file via an 'upload'-FormElement. Valid only during processing of the current 'upload'-formElement. |
+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| fileDestination | Destination (path & filename) for an uploaded file. Defined in an 'upload'-FormElement.parameter. Valid: same as 'filename'. | | fileDestination | Destination (path & filename) for an uploaded file. Defined in an 'upload'-FormElement.parameter. Valid: same as 'filename'. |
+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+ +-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| filename | Original filename of an uploaded file via an 'upload'-FormElement. Valid only during processing of the current 'upload'-formElement. |
+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| filenameBase | Like 'filename', but without the extension (if there is one) | | filenameBase | Like 'filename', but without the extension (if there is one) |
+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+ +-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| filenameExt | Only the extension of 'filename' (if there is one) | | filenameExt | Only the extension of 'filename' (if there is one) |
...@@ -3065,6 +3065,9 @@ and will be processed after saving the primary record and before any action Form ...@@ -3065,6 +3065,9 @@ and will be processed after saving the primary record and before any action Form
* *fileReplace=always*: If `fileDestination` exist - replace it by the new one. * *fileReplace=always*: If `fileDestination` exist - replace it by the new one.
* *fileSplit=svg*: Only valid for filetype=PDF. Will split the PDF in SVG files, one SVG per page.
* *fileDestinationSplit*: Like `fileDestination`, but for the SVG. See `upload-split-pdf`_
Immediately after the upload finished (before the user press save), the file will be checked on the server for it's Immediately after the upload finished (before the user press save), the file will be checked on the server for it's
content or file extension (see 'accept'). content or file extension (see 'accept').
...@@ -3072,6 +3075,23 @@ The maximum size is defined by the minimum of `upload_max_filesize`, `post_max_s ...@@ -3072,6 +3075,23 @@ The maximum size is defined by the minimum of `upload_max_filesize`, `post_max_s
In case of broken uploads, please also check `max_input_time` in php.ini. In case of broken uploads, please also check `max_input_time` in php.ini.
.. _`upload-split-pdf`:
Upload a PDF and split to single pages
''''''''''''''''''''''''''''''''''''''
Only implemented for PDF to SVG.
An uploaded PDF file will be split into single SVG pages. Additionally for every page SVG file, a reference record is created
in table `Split`. The table `Split` contains the following columns: `id`, `tableName`, `xId`, `pathFileName`. The `tableName`
is the table of the current form or, if explicit defined, the parameter `splitTableName`. The columns `tableName` and
`xId` together defines the reference to the source PDF file/ record. Example: ::
fileSplitTableName=Note
fileSplit=svg
fileDestinationSplit=fileadmin/split/{{id:R}}/{{filenameBase:V}}.%02d.svg
The conversion tool is `pdf2svg`, which accepts a 'printf'-style notation for the numbering like '%02d'.
Deleting a record and the referenced file Deleting a record and the referenced file
''''''''''''''''''''''''''''''''''''''''' '''''''''''''''''''''''''''''''''''''''''
......
...@@ -13,6 +13,8 @@ const GFX_INFO = 'typo3conf/ext/qfq/Resources/Public/icons/note.gif'; ...@@ -13,6 +13,8 @@ const GFX_INFO = 'typo3conf/ext/qfq/Resources/Public/icons/note.gif';
const API_DIR = 'typo3conf/ext/qfq/qfq/api'; const API_DIR = 'typo3conf/ext/qfq/qfq/api';
const QFQ_LOG = 'qfq.log'; const QFQ_LOG = 'qfq.log';
const QFQ_TEMP_FILE_PATTERN = 'qfq.split.XXXXX';
const QFQ_TEMP_SOURCE = '.temp.source';
const MAX_LENGTH_IPV6 = 45; const MAX_LENGTH_IPV6 = 45;
...@@ -24,6 +26,7 @@ const SESSION_FE_USER_GROUP = 'feUserGroup'; ...@@ -24,6 +26,7 @@ const SESSION_FE_USER_GROUP = 'feUserGroup';
const TABLE_NAME_FORM = 'Form'; const TABLE_NAME_FORM = 'Form';
const TABLE_NAME_FORM_ELEMENT = 'FormElement'; const TABLE_NAME_FORM_ELEMENT = 'FormElement';
const TABLE_NAME_SPLIT = 'Split';
const FORM_LOAD = 'form_load'; const FORM_LOAD = 'form_load';
const FORM_SAVE = 'form_save'; const FORM_SAVE = 'form_save';
...@@ -175,6 +178,7 @@ const ERROR_MISSING_PRINTF_ARGUMENTS = 1077; ...@@ -175,6 +178,7 @@ const ERROR_MISSING_PRINTF_ARGUMENTS = 1077;
const ERROR_MISSING_DEFINITON = 1078; const ERROR_MISSING_DEFINITON = 1078;
const ERROR_QFQ_VERSION = 1079; const ERROR_QFQ_VERSION = 1079;
const ERROR_PLAY_SQL_FILE = 1080; const ERROR_PLAY_SQL_FILE = 1080;
const ERROR_MISSING_FILE_NAME = 1081;
// Subrecord // Subrecord
const ERROR_SUBRECORD_MISSING_COLUMN_ID = 1100; const ERROR_SUBRECORD_MISSING_COLUMN_ID = 1100;
...@@ -848,11 +852,17 @@ const FE_SHOW_SECONDS = 'showSeconds'; // value: 0|1 ...@@ -848,11 +852,17 @@ const FE_SHOW_SECONDS = 'showSeconds'; // value: 0|1
const FE_SHOW_ZERO = 'showZero'; // 0|1 - Used for 'date/datime/time': in case of fe.value='0' shows corresponding '00-00-0000'|'00:00:00' const FE_SHOW_ZERO = 'showZero'; // 0|1 - Used for 'date/datime/time': in case of fe.value='0' shows corresponding '00-00-0000'|'00:00:00'
const FE_HIDE_ZERO = 'hideZero'; // 0|1 - In case of fe.value=0|'0', an empty string is shown. const FE_HIDE_ZERO = 'hideZero'; // 0|1 - In case of fe.value=0|'0', an empty string is shown.
const FE_FILE_DESTINATION = 'fileDestination'; // Target pathFilename for an uploaded file. const FE_FILE_DESTINATION = 'fileDestination'; // Target pathFilename for an uploaded file.
const FE_FILE_DESTINATION_SPLIT = 'fileDestinationSplit'; // Target pathFilename for an uploaded file.
const FE_FILE_REPLACE_MODE = 'fileReplace'; // Target pathFilename for an uploaded file. const FE_FILE_REPLACE_MODE = 'fileReplace'; // Target pathFilename for an uploaded file.
const FE_FILE_REPLACE_MODE_ALWAYS = 'always'; // Target pathFilename for an uploaded file. const FE_FILE_REPLACE_MODE_ALWAYS = 'always'; // Target pathFilename for an uploaded file.
const FE_FILE_MIME_TYPE_ACCEPT = 'accept'; // Target pathFilename for an uploaded file. const FE_FILE_MIME_TYPE_ACCEPT = 'accept'; // Target pathFilename for an uploaded file.
const FE_FILE_MAX_FILE_SIZE = 'maxFileSize'; // Target pathFilename for an uploaded file. const FE_FILE_MAX_FILE_SIZE = 'maxFileSize'; // Target pathFilename for an uploaded file.
const FE_FILE_CAPTURE = 'capture'; // On a smartphone opens the camera const FE_FILE_CAPTURE = 'capture'; // On a smartphone opens the camera
const FE_FILE_SPLIT = 'fileSplit';
const FE_FILE_SPLIT_SVG = 'svg';
const FE_FILE_SPLIT_TABLE_NAME = 'tableNameSplit';
const FE_IMAGE_SOURCE = 'imageSource'; // On a smartphone opens the camera const FE_IMAGE_SOURCE = 'imageSource'; // On a smartphone opens the camera
const FE_SQL_VALIDATE = 'sqlValidate'; // Action: Query to validate form load const FE_SQL_VALIDATE = 'sqlValidate'; // Action: Query to validate form load
const FE_EXPECT_RECORDS = 'expectRecords'; // Action: expected number of rows of FE_SQL_VALIDATE const FE_EXPECT_RECORDS = 'expectRecords'; // Action: expected number of rows of FE_SQL_VALIDATE
...@@ -1208,4 +1218,5 @@ const AUTOCRON_UNIT = 'unit'; ...@@ -1208,4 +1218,5 @@ const AUTOCRON_UNIT = 'unit';
const AUTOCRON_COUNT = 'count'; const AUTOCRON_COUNT = 'count';
// Annotate // Annotate
const FABRIC_CSS_CLASS = 'fabric'; const FABRIC_CSS_CLASS = 'fabric';
\ No newline at end of file
...@@ -224,7 +224,7 @@ class Save { ...@@ -224,7 +224,7 @@ class Save {
$paramList = substr($paramList, 0, strlen($paramList) - 2); $paramList = substr($paramList, 0, strlen($paramList) - 2);
$columnList = '`' . implode('`, `', array_keys($values)) . '`'; $columnList = '`' . implode('`, `', array_keys($values)) . '`';
$sql = 'INSERT INTO ' . $tableName . ' ( ' . $columnList . ' ) VALUES ( ' . $paramList . ' )'; $sql = "INSERT INTO $tableName ( " . $columnList . " ) VALUES ( " . $paramList . ' )';
$rc = $this->db->sql($sql, ROW_REGULAR, array_values($values)); $rc = $this->db->sql($sql, ROW_REGULAR, array_values($values));
...@@ -438,15 +438,112 @@ class Save { ...@@ -438,15 +438,112 @@ class Save {
throw new UserFormException("Rename file: '$srcFile' > '$pathFileName'", ERROR_IO_RENAME); throw new UserFormException("Rename file: '$srcFile' > '$pathFileName'", ERROR_IO_RENAME);
} }
$this->splitUpload($formElement, $pathFileName);
return $pathFileName; return $pathFileName;
} }
/**
* @param array $formElement
* @param $pathFileName
* @throws UserFormException
*/
private function splitUpload(array $formElement, $pathFileName) {
if (empty($formElement[FE_FILE_SPLIT]) || empty($formElement[FE_FILE_DESTINATION_SPLIT])) {
return;
}
$fileDestinationSplit = $this->evaluate->parse($formElement[FE_FILE_DESTINATION_SPLIT]);
$fileSplit = $this->evaluate->parse($formElement[FE_FILE_SPLIT]);
$fileSplitTableName = $this->evaluate->parse($formElement[FE_FILE_SPLIT_TABLE_NAME]);
if (empty($fileSplitTableName)) {
$fileSplitTableName = $this->formSpec[F_TABLE_NAME];
}
// Filetype testen: nur Dateien splitten die man auch wirklich entpacken kann
switch ($fileSplit) {
case FE_FILE_SPLIT_SVG:
$this->splitSvg($pathFileName, $fileDestinationSplit, $fileSplitTableName);
break;
default:
throw new UserFormException("Unknown 'fileSplit' type: " . $formElement[FE_FILE_SPLIT], ERROR_UNKNOWN_TOKEN);
}
}
/**
* @param $pathFileNameSrc
* @param $fileDestinationSplit
* @throws CodeException
* @throws DbException
* @throws UserFormException
*/
private function splitSvg($pathFileNameSrc, $fileDestinationSplit, $fileSplitTableName) {
Support::mkDirParent($fileDestinationSplit);
// Save CWD
$cwd = getcwd();
// Create temporary directory
$tempDir = Support::createTempDir();
$newSrc = $tempDir . DIRECTORY_SEPARATOR . QFQ_TEMP_SOURCE;
$rc = copy($pathFileNameSrc, $newSrc);
$rc = chdir($tempDir);
// Split destination.
$pathParts = pathinfo($fileDestinationSplit);
if (empty($pathParts['filename']) || empty($pathParts['basename'])) {
throw new UserFormException('Missing filename in ' . FE_FILE_DESTINATION_SPLIT, ERROR_MISSING_FILE_NAME);
}
// Extract filename from destination directory.
$fileNameDest = $pathParts['basename'];
// Split PDF
$rc = exec('pdf2svg "' . $newSrc . '" "' . $fileNameDest . '" all');
// Array of created filenames.
$files = scandir('.');
// Create DB records according to the extracted filenames.
$tableName = TABLE_NAME_SPLIT;
$sql = "INSERT INTO $tableName (`tableName`, `xId`, `pathFilename`, `created`) VALUES (?,?,?, NOW())";
foreach ($files as $file) {
if ($file == '.' || $file == '..' || $file == QFQ_TEMP_SOURCE) {
continue;
}
if (!empty($pathParts['dirname'])) {
$fileDestination = $pathParts['dirname'] . '/' . $file;
} else {
$fileDestination = $file;
}
$rc = rename($file, Support::joinPath($cwd, $fileDestination));
// Insert records.
$this->db->sql($sql, ROW_REGULAR, [$fileSplitTableName, $this->store->getVar(COLUMN_ID, STORE_RECORD), $fileDestination]);
}
// Pop directory
$rc = chdir($cwd);
// Remove duplicated source
$rc = unlink($newSrc);
// Remove empty directory
$rc = rmdir($tempDir);
}
/** /**
* Create/update or delete the slave record. * Create/update or delete the slave record.
* *
* @param array $fe * @param array $fe
* @param bool $flagNewUpload * @param $modeUpload
*
* @return int * @return int
* @throws CodeException * @throws CodeException
* @throws UserFormException * @throws UserFormException
......
...@@ -86,7 +86,9 @@ $UPDATE_ARRAY = array( ...@@ -86,7 +86,9 @@ $UPDATE_ARRAY = array(
"ALTER TABLE `FormElement` CHANGE `type` `type` ENUM( 'checkbox', 'date', 'datetime', 'dateJQW', 'datetimeJQW', 'extra', 'gridJQW', 'text', 'editor', 'annotate', 'time', 'note', 'password', 'radio', 'select', 'subrecord', 'upload', 'annotate', 'fieldset', 'pill', 'templateGroup', 'beforeLoad', 'beforeSave', 'beforeInsert', 'beforeUpdate', 'beforeDelete', 'afterLoad', 'afterSave', 'afterInsert', 'afterUpdate', 'afterDelete', 'sendMail', 'paste' ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'text';", "ALTER TABLE `FormElement` CHANGE `type` `type` ENUM( 'checkbox', 'date', 'datetime', 'dateJQW', 'datetimeJQW', 'extra', 'gridJQW', 'text', 'editor', 'annotate', 'time', 'note', 'password', 'radio', 'select', 'subrecord', 'upload', 'annotate', 'fieldset', 'pill', 'templateGroup', 'beforeLoad', 'beforeSave', 'beforeInsert', 'beforeUpdate', 'beforeDelete', 'afterLoad', 'afterSave', 'afterInsert', 'afterUpdate', 'afterDelete', 'sendMail', 'paste' ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'text';",
], ],
'0.25.3' => [
"CREATE TABLE `Split` (`id` INT(11) NOT NULL AUTO_INCREMENT, `tableName` VARCHAR(255) NOT NULL, `xId` INT(11) NOT NULL, `pathFileName` VARCHAR(255) NOT NULL, `modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `created` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00', PRIMARY KEY (`id`)) ENGINE = InnoDB AUTO_INCREMENT = 0 DEFAULT CHARSET = utf8;"
],
); );
...@@ -832,6 +832,25 @@ class Support { ...@@ -832,6 +832,25 @@ class Support {
return $filename . $extend; return $filename . $extend;
} }
/**
*
* @param $path
* @param $file
* @return string
*/
public static function joinPath($path, $file) {
if ($file[0] == DIRECTORY_SEPARATOR) {
return $file; // absolute
}
if (substr($path, -1) == DIRECTORY_SEPARATOR) {
return $path . $file; // SEPARATOR already inside
}
return $path . DIRECTORY_SEPARATOR . $file;
}
/** /**
* Creates all necessary directories in $pathFileName, but not the last part, the filename. A filename has to be * Creates all necessary directories in $pathFileName, but not the last part, the filename. A filename has to be
* specified. * specified.
...@@ -872,6 +891,15 @@ class Support { ...@@ -872,6 +891,15 @@ class Support {
} }
} }
/**
*
*/
public static function createTempDir() {
return exec("mktemp -d --tmpdir " . QFQ_TEMP_FILE_PATTERN);
}
/** /**
* Convert 'false' and 'empty' to '0'. * Convert 'false' and 'empty' to '0'.
* *
......
...@@ -478,4 +478,15 @@ VALUES ...@@ -478,4 +478,15 @@ VALUES
'<p>Starttime of running job. When job is finished, will be set back to 0. A new job will only be started, if this is 0.</p>\r\n<p>&nbsp;</p>'); '<p>Starttime of running job. When job is finished, will be set back to 0. A new job will only be started, if this is 0.</p>\r\n<p>&nbsp;</p>');
CREATE TABLE `Split` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`tableName` VARCHAR(255) NOT NULL,
`xId` INT(11) NOT NULL,
`pathFileName` VARCHAR(255) NOT NULL,
`modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`created` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`)
)
ENGINE = InnoDB
AUTO_INCREMENT = 0
DEFAULT CHARSET = utf8;
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment