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
Preparation for Ubuntu 14.04::
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 service apache2 restart
Preparation for Ubuntu 16.04::
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:
......@@ -1305,10 +1305,10 @@ Store: *VARS* - V
+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| 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'. |
+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| 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) |
+-------------------------+--------------------------------------------------------------------------------------------------------------------------------------------+
| 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
* *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
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
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
'''''''''''''''''''''''''''''''''''''''''
......
......@@ -13,6 +13,8 @@ const GFX_INFO = 'typo3conf/ext/qfq/Resources/Public/icons/note.gif';
const API_DIR = 'typo3conf/ext/qfq/qfq/api';
const QFQ_LOG = 'qfq.log';
const QFQ_TEMP_FILE_PATTERN = 'qfq.split.XXXXX';
const QFQ_TEMP_SOURCE = '.temp.source';
const MAX_LENGTH_IPV6 = 45;
......@@ -24,6 +26,7 @@ const SESSION_FE_USER_GROUP = 'feUserGroup';
const TABLE_NAME_FORM = 'Form';
const TABLE_NAME_FORM_ELEMENT = 'FormElement';
const TABLE_NAME_SPLIT = 'Split';
const FORM_LOAD = 'form_load';
const FORM_SAVE = 'form_save';
......@@ -175,6 +178,7 @@ const ERROR_MISSING_PRINTF_ARGUMENTS = 1077;
const ERROR_MISSING_DEFINITON = 1078;
const ERROR_QFQ_VERSION = 1079;
const ERROR_PLAY_SQL_FILE = 1080;
const ERROR_MISSING_FILE_NAME = 1081;
// Subrecord
const ERROR_SUBRECORD_MISSING_COLUMN_ID = 1100;
......@@ -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_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_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_ALWAYS = 'always'; // 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_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_SQL_VALIDATE = 'sqlValidate'; // Action: Query to validate form load
const FE_EXPECT_RECORDS = 'expectRecords'; // Action: expected number of rows of FE_SQL_VALIDATE
......@@ -1209,3 +1219,4 @@ const AUTOCRON_COUNT = 'count';
// Annotate
const FABRIC_CSS_CLASS = 'fabric';
......@@ -224,7 +224,7 @@ class Save {
$paramList = substr($paramList, 0, strlen($paramList) - 2);
$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));
......@@ -438,15 +438,112 @@ class Save {
throw new UserFormException("Rename file: '$srcFile' > '$pathFileName'", ERROR_IO_RENAME);
}
$this->splitUpload($formElement, $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.
*
* @param array $fe
* @param bool $flagNewUpload
*
* @param $modeUpload
* @return int
* @throws CodeException
* @throws UserFormException
......
......@@ -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';",
],
'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 {
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
* specified.
......@@ -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'.
*
......
......@@ -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>');
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