diff --git a/Documentation-develop/CODING.md b/Documentation-develop/CODING.md index b763de81b21d2997e2168676fa4360ec7f00aed4..690582a0710975b8eac57be3c695ff6a2ba5a2da 100644 --- a/Documentation-develop/CODING.md +++ b/Documentation-develop/CODING.md @@ -162,7 +162,7 @@ Upload to server, before 'save' ............................... * If a user open's a file for upload via the browse button, that file is immediately transmitted to the server. The user will see a turning wheel until the upload finished. -* After successfull upload the 'Browse' button disappears and the filename, plus the delete button, will be displayed (client logic). +* After successfully upload the 'Browse' button disappears and the filename, plus the delete button, will be displayed (client logic). * The uploaded file will be checked: maxsize, mime type, check script. * The uploaded file is still temporary. It has been renamed from '[STORE_EXTRA][<uploadSip>][FILES_TMP_NAME]' to '[STORE_EXTRA][<uploadSip>][FILES_TMP_NAME].cached'. diff --git a/Documentation/Form.rst b/Documentation/Form.rst index b9a47f44c72cf51f6b576497574adbe4de9a1d38..1c44bdfa14024ab658c8aabbeea596fd8539a736 100644 --- a/Documentation/Form.rst +++ b/Documentation/Form.rst @@ -586,7 +586,7 @@ The `mode` is given via (in this priority): Mode ;;;; -* *standard*: +* **standard**: * The form will behave like defined in the form editor. * Missing required values will a) be indicated and b) block saving the record. @@ -2027,7 +2027,24 @@ FormElement.parameter * The following attributes are hard coded (can't be changed): `s|M:file|d|F` -* fileSplit, fileDestinationSplit, tableNameSplit: see :ref:`split-pdf-upload` +* *fileUnzip* - If the file is a ZIP file (only then) it will be unzipped. If no directory is given via ``fileUnzip``, the + basedir of ``fileDestination`` is taken, appended by ``unpack``. + + If an unzip will be done, for each file of the archive STORE_VAR will be filled (name, path of the extracted file, + mime type, size) and the following will be triggered: *sqlValidate, slaveId, sqlBefore, sqlAfter, sqlInsert, sqlUpdate*. + + Example:: + + fileDestination = fileadmin/file_{{id:R}}.zip + fileUnzip + sqlValidate ={{! SELECT '' FROM (SELECT '') AS fake WHERE '{{mimeType:V}}' LIKE 'application/pdf%' }} + expectRecords=1 + messageFail=Unexpected filetype + + # Set new + sqlAfter={{INSERT INTO Upload (pathFileName) VALUES '{{filename:V}}' }} + +* `fileSplit`, `fileDestinationSplit`, `tableNameSplit`: see :ref:`split-pdf-upload` * Excel Import: QFQ offers functionality to directly import excel data into the database. This functionality can optionally be combined with saving the file by using the above parameters like `fileDestination`. @@ -2159,8 +2176,8 @@ file type. * [jpeg] - default: `-density 150 -quality 90` * *fileDestinationSplit* = `<pathFileName (pattern)>` - Target directory and filename pattern for the created & - split'ed files. Default <fileDestination>.split/split.<nr>.<fileSplit>. - If explicit given, respect that SVG needs a printf style for <nr>, whereas JPEG is numbered automatically. E.g. :: + split'ed files. Default <fileDestination>.split/split.<nr>.<fileSplit>. + If explicit given, respect that SVG needs a printf style for <nr>, whereas JPEG is numbered automatically. E.g. :: [svg] fileDestinationSplit = fileadmin/protected/{{id:R}}.{{filenameBase:V}}.%02d.svg [jpeg] fileDestinationSplit = fileadmin/protected/{{id:R}}.{{filenameBase:V}}.jpg diff --git a/Documentation/Links.rst b/Documentation/Links.rst deleted file mode 100644 index defbc6e87623d241641b6a1f7b8e6c5835bf4b6c..0000000000000000000000000000000000000000 --- a/Documentation/Links.rst +++ /dev/null @@ -1,58 +0,0 @@ -.. ================================================== -.. ================================================== -.. ================================================== -.. Header hierarchy -.. == -.. -- -.. ^^ -.. "" -.. ;; -.. ,, -.. -.. --------------------------------------------used to the update the records specified ------ -.. Best Practice T3 reST: https://docs.typo3.org/m/typo3/docs-how-to-document/master/en-us/WritingReST/CheatSheet.html -.. Reference: https://docs.typo3.org/m/typo3/docs-how-to-document/master/en-us/WritingReST/Index.html -.. Italic *italic* -.. Bold **bold** -.. Code ``text`` -.. External Links: `Bootstrap <http://getbootstrap.com/>`_ -.. Add Images: .. image:: ../Images/a4.jpg -.. -.. -.. Admonitions -.. .. note:: .. important:: .. tip:: .. warning:: -.. Color: (blue) (orange) (green) (red) -.. -.. Definition: -.. some text becomes strong (only one line) -.. description has to indented - -.. -*- coding: utf-8 -*- with BOM. - -.. include:: Includes.txt - - -.. _links: - -Links ------ - -The links to issue and the GitHub repository are maintained in the Settings.cfg. - -You may want to remove this file if all important links are already handled in -Settings.cfg. - -:Packagist: - https://packagist.org/packages/<username>/<extension key> - -:TER: - https://typo3.org/extensions/repository/view/<extension key> - -:Issues: - https://github.com/<username>/<extension key>/issues - -:GitHub Repository: - https://github.com/<username>/<extension key> - -:Contact: - `@<username> <https://twitter.com/your-username>`__ diff --git a/Documentation/index.rst b/Documentation/index.rst index be57bdf8b7d3a14ce6fb050fd836b6e63a7207aa..643d134ea60556cf128da19cefd93d05fa672348 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -87,7 +87,6 @@ This documentation is for the TYPO3 extension **qfq**. ApplicationTest GeneralTips Release - Links License Sitemap SearchDocs diff --git a/extension/Classes/Core/Constants.php b/extension/Classes/Core/Constants.php index 3e03467c418cc6af3bd5f5178e8c56b0cce78cec..2f39901ea860341a78ac596416996e96d1f8efc0 100644 --- a/extension/Classes/Core/Constants.php +++ b/extension/Classes/Core/Constants.php @@ -242,7 +242,7 @@ const ERROR_STORE_KEY_EXIST = 1201; // I/O Error const ERROR_IO_COPY = 1300; - +const ERROR_IO_ZIP_OPEN = 1301; const ERROR_IO_RMDIR = 1302; const ERROR_IO_WRITE = 1303; const ERROR_IO_OPEN = 1304; @@ -1129,6 +1129,8 @@ const FE_FILE_REPLACE_MODE = 'fileReplace'; // Flag if a) QFQ throw an error if const FE_FILE_REPLACE_MODE_ALWAYS = 'always'; // Value for flag FE_FILE_REPLACE_MODE const FE_FILE_MIME_TYPE_ACCEPT = 'accept'; // Comma separated list of mime types const FE_FILE_MAX_FILE_SIZE = SYSTEM_FILE_MAX_FILE_SIZE; // Max upload file size +const FE_FILE_UNZIP = 'fileUnzip'; // 0|1|dir|{{SELECT ...}} +const FE_FILE_UNPACK_DIR = 'unpack'; // default dir if not specified const FE_FILE_CAPTURE = 'capture'; // On a smartphone opens the camera const FE_FILE_SPLIT = 'fileSplit'; diff --git a/extension/Classes/Core/Form/FormAction.php b/extension/Classes/Core/Form/FormAction.php index c452db09b1dd668f508d9893a9117e2650bfbd01..aed3b24c53451c714668b113f9f7ef3116139232 100644 --- a/extension/Classes/Core/Form/FormAction.php +++ b/extension/Classes/Core/Form/FormAction.php @@ -200,7 +200,7 @@ class FormAction { $this->store->setStore($arr, STORE_LDAP, true); } - $this->sqlValidate($fe); + HelperFormElement::sqlValidate($this->evaluate, $fe); if ($fe[FE_TYPE] === FE_TYPE_SENDMAIL) { $this->doSendMail($fe); @@ -291,57 +291,6 @@ class FormAction { $sendMail->process($mailConfig); } - /** - * If there is a query defined in fe.parameter.FE_SQL_VALIDATE: fire them. - * Count the selected records and compare them with fe.parameter.FE_EXPECT_RECORDS. - * If match: everything is fine, do nothing. - * Else throw \UserFormException with error message of fe.parameter.FE_MESSAGE_FAIL - * - * @param array $fe - * - * @throws \CodeException - * @throws \DbException - * @throws \UserFormException - * @throws \UserReportException - */ - private function sqlValidate(array $fe) { - - // Is there something to check? - if ($fe[FE_SQL_VALIDATE] === '') { - return; - } - - if ($fe[FE_EXPECT_RECORDS] === '') { - throw new \UserFormException("Missing parameter '" . FE_EXPECT_RECORDS . "'", ERROR_MISSING_EXPECT_RECORDS); - } - $expect = $this->evaluate->parse($fe[FE_EXPECT_RECORDS]); - - if ($fe[FE_MESSAGE_FAIL] === '') { - throw new \UserFormException("Missing parameter '" . FE_MESSAGE_FAIL . "'", ERROR_MISSING_MESSAGE_FAIL); - } - - // Do the check - $result = $this->evaluate->parse($fe[FE_SQL_VALIDATE], ROW_REGULAR); - if (!is_array($result)) { - throw new \UserFormException("Expected an array for '" . FE_SQL_VALIDATE . "', got a scalar. Please check for {{!...", ERROR_EXPECTED_ARRAY); - } - - // If there is at least one record count given, who matches: return 'check succeeded' - $countRecordsArr = explode(',', $expect); - foreach ($countRecordsArr as $count) { - if (count($result) == $count) { - return; // check successfully passed - } - } - - $msg = $this->evaluate->parse($fe[FE_MESSAGE_FAIL]); // Replace possible dynamic parts - - // Throw user error message - throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => $msg - , ERROR_MESSAGE_TO_DEVELOPER => 'validate() failed']), ERROR_REPORT_FAILED_ACTION); - - } - /** * Process slaveId, sqlBefore, sqlInsert|sqlUpdate|sqlDelete, sqlAfter. * flagFeAction=false: for Native Elements diff --git a/extension/Classes/Core/Helper/HelperFile.php b/extension/Classes/Core/Helper/HelperFile.php index 5619f9c0c6cf2510d4c215ad5adcbd673690e00d..a9b1935276d6c9d7bcfc8286bd722dc84045514a 100644 --- a/extension/Classes/Core/Helper/HelperFile.php +++ b/extension/Classes/Core/Helper/HelperFile.php @@ -101,6 +101,8 @@ class HelperFile { /** * Returns an array with filestat information to $pathFileName + * - mimeType + * - fileSize * * @param $pathFileName * @return array @@ -540,5 +542,43 @@ class HelperFile { return $pre . $separator . $post; } + + /** + * Translates ZIP error codes to text. + * + * @param $errno + * @return string + */ + public static function zipFileErrMsg($errno) { + + // using constant name as a string to make this function PHP4 compatible + $zipFileFunctionsErrors = array( + 'ZIPARCHIVE::ER_MULTIDISK' => 'Multi-disk zip archives not supported.', + 'ZIPARCHIVE::ER_RENAME' => 'Renaming temporary file failed.', + 'ZIPARCHIVE::ER_CLOSE' => 'Closing zip archive failed', + 'ZIPARCHIVE::ER_SEEK' => 'Seek error', + 'ZIPARCHIVE::ER_READ' => 'Read error', + 'ZIPARCHIVE::ER_WRITE' => 'Write error', + 'ZIPARCHIVE::ER_CRC' => 'CRC error', + 'ZIPARCHIVE::ER_ZIPCLOSED' => 'Containing zip archive was closed', + 'ZIPARCHIVE::ER_NOENT' => 'No such file.', + 'ZIPARCHIVE::ER_EXISTS' => 'File already exists', + 'ZIPARCHIVE::ER_OPEN' => 'Can\'t open file', + 'ZIPARCHIVE::ER_TMPOPEN' => 'Failure to create temporary file.', + 'ZIPARCHIVE::ER_ZLIB' => 'Zlib error', + 'ZIPARCHIVE::ER_MEMORY' => 'Memory allocation failure', + 'ZIPARCHIVE::ER_CHANGED' => 'Entry has been changed', + 'ZIPARCHIVE::ER_COMPNOTSUPP' => 'Compression method not supported.', + 'ZIPARCHIVE::ER_EOF' => 'Premature EOF', + 'ZIPARCHIVE::ER_INVAL' => 'Invalid argument', + 'ZIPARCHIVE::ER_NOZIP' => 'Not a zip archive', + 'ZIPARCHIVE::ER_INTERNAL' => 'Internal error', + 'ZIPARCHIVE::ER_INCONS' => 'Zip archive inconsistent', + 'ZIPARCHIVE::ER_REMOVE' => 'Can\'t remove file', + 'ZIPARCHIVE::ER_DELETED' => 'Entry has been deleted', + ); + + return $zipFileFunctionsErrors[$errno] ?? 'unknown'; + } } diff --git a/extension/Classes/Core/Helper/HelperFormElement.php b/extension/Classes/Core/Helper/HelperFormElement.php index 33c8dcd1575de89af0d55dc7d3ee38d24737dd3d..b969ac401c692e350372f1b2328d0784d1a78a5b 100644 --- a/extension/Classes/Core/Helper/HelperFormElement.php +++ b/extension/Classes/Core/Helper/HelperFormElement.php @@ -8,6 +8,7 @@ namespace IMATHUZH\Qfq\Core\Helper; +use IMATHUZH\Qfq\Core\Evaluate; use IMATHUZH\Qfq\Core\Store\Store; @@ -37,7 +38,7 @@ class HelperFormElement { */ public static function explodeParameterInArrayElements(array &$elements, $keyName) { - foreach ($elements AS $key => $element) { + foreach ($elements as $key => $element) { self::explodeParameter($element, $keyName); $elements[$key] = $element; } @@ -58,7 +59,7 @@ class HelperFormElement { // Do not add FE_SLAVE_ID - it's necessary to detect if a value is given or not. $default = [FE_SQL_BEFORE => '', FE_SQL_INSERT => '', FE_SQL_UPDATE => '', FE_SQL_DELETE => '', FE_SQL_AFTER => '']; - foreach ($elements AS $key => $element) { + foreach ($elements as $key => $element) { $elements[$key][FE_TG_INDEX] = 0; unset($elements[$key][FE_ADMIN_NOTE]); // $elements[$key][FE_DATA_REFERENCE] = ''; @@ -91,7 +92,7 @@ class HelperFormElement { if (!$flagAllowOverwrite) { // Check if some of the exploded keys conflict with existing keys $checkKeys = array_keys($arr); - foreach ($checkKeys AS $checkKey) { + foreach ($checkKeys as $checkKey) { if (!empty($element[$checkKey])) { self::$store = Store::getInstance(); self::$store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($element), STORE_SYSTEM); @@ -861,5 +862,55 @@ EOF; return '<div class="help-block with-errors hidden"></div>'; } + /** + * If there is a query defined in fe.parameter.FE_SQL_VALIDATE: fire them. + * Count the selected records and compare them with fe.parameter.FE_EXPECT_RECORDS. + * If match: everything is fine, do nothing. + * Else throw \UserFormException with error message of fe.parameter.FE_MESSAGE_FAIL + * + * @param array $fe + * @param Evaluate $evaluate + * @throws \CodeException + * @throws \DbException + * @throws \UserFormException + * @throws \UserReportException + */ + public static function sqlValidate(Evaluate $evaluate, array $fe) { + + // Is there something to check? + if ($fe[FE_SQL_VALIDATE] === '') { + return; + } + + if ($fe[FE_EXPECT_RECORDS] === '') { + throw new \UserFormException("Missing parameter '" . FE_EXPECT_RECORDS . "'", ERROR_MISSING_EXPECT_RECORDS); + } + $expect = $evaluate->parse($fe[FE_EXPECT_RECORDS]); + if ($fe[FE_MESSAGE_FAIL] === '') { + throw new \UserFormException("Missing parameter '" . FE_MESSAGE_FAIL . "'", ERROR_MISSING_MESSAGE_FAIL); + } + + // Do the check + $result = $evaluate->parse($fe[FE_SQL_VALIDATE], ROW_REGULAR); + if (!is_array($result)) { + throw new \UserFormException("Expected an array for '" . FE_SQL_VALIDATE . "', got a scalar. Please check for {{!...", ERROR_EXPECTED_ARRAY); + } + + // If there is at least one record count given, who matches: return 'check succeeded' + $countRecordsArr = explode(',', $expect); + foreach ($countRecordsArr as $count) { + if (count($result) == $count) { + return; // check successfully passed + } + } + + $msg = $evaluate->parse($fe[FE_MESSAGE_FAIL]); // Replace possible dynamic parts + + // Throw user error message + throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => $msg + , ERROR_MESSAGE_TO_DEVELOPER => "validate() failed.\nSQL Raw: " . $fe[FE_SQL_VALIDATE]]) + , ERROR_REPORT_FAILED_ACTION); + + } } \ No newline at end of file diff --git a/extension/Classes/Core/Save.php b/extension/Classes/Core/Save.php index 886444cae4b6274f6ee793ced7195c907f2799be..5e9cca3c8139ef44797e325ffb5213fd8bc40255 100644 --- a/extension/Classes/Core/Save.php +++ b/extension/Classes/Core/Save.php @@ -20,6 +20,7 @@ use IMATHUZH\Qfq\Core\Helper\Support; use IMATHUZH\Qfq\Core\Store\FillStoreForm; use IMATHUZH\Qfq\Core\Store\Sip; use IMATHUZH\Qfq\Core\Store\Store; +use ZipArchive; /** * Class Save @@ -254,7 +255,7 @@ class Save { $formValues = $this->createEmptyTemplateGroupElements($formValues); // Iterate over all table.columns. Built an assoc array $newValues. - foreach ($tableColumns AS $column) { + foreach ($tableColumns as $column) { // Never save a predefined 'id': autoincrement values will be given by database.. if ($column === COLUMN_ID) { @@ -408,7 +409,7 @@ class Save { */ private function isColumnUploadField($feName) { - foreach ($this->feSpecNative AS $formElement) { + foreach ($this->feSpecNative as $formElement) { if ($formElement[FE_NAME] === $feName && $formElement[FE_TYPE] == FE_TYPE_UPLOAD) return true; } @@ -501,12 +502,22 @@ class Save { $sip = new Sip(false); $newValues = array(); - $vars = array(); + + $flagDoUnzip = false; $formValues = $this->store->getStore(STORE_FORM); $primaryRecord = $this->store->getStore(STORE_RECORD); // necessary to check if the current formElement exist as a column of the primary table. - foreach ($this->feSpecNative AS $formElement) { + // Upload - Take care the necessary target directories exist. + $cwd = getcwd(); + $sitePath = $this->store->getVar(SYSTEM_SITE_PATH, STORE_SYSTEM); + if ($cwd === false || $sitePath === false || !HelperFile::chdir($sitePath)) { + throw new \UserFormException( + json_encode([ERROR_MESSAGE_TO_USER => 'getcwd() failed or SITE_PATH undefined or chdir() failed', ERROR_MESSAGE_TO_DEVELOPER => "getcwd() failed or SITE_PATH undefined or chdir('$sitePath') failed."]), + ERROR_IO_CHDIR); + } + + foreach ($this->feSpecNative as $formElement) { // skip non upload formElements if ($formElement[FE_TYPE] != FE_TYPE_UPLOAD) { continue; @@ -523,7 +534,34 @@ class Save { } $column = $formElement[FE_NAME]; + + $statusUpload = $this->store->getVar($formValues[$column] ?? '', STORE_EXTRA); + // Get file stats + $vars = array(); + $vars[VAR_FILE_SIZE] = $statusUpload[FILES_SIZE] ?? ''; + $vars[VAR_FILE_MIME_TYPE] = $statusUpload[FILES_TYPE] ?? ''; + + // Check for 'unzip'. + if (isset($formElement[FE_FILE_UNZIP]) + && $formElement[FE_FILE_UNZIP] != '0' + && $vars[VAR_FILE_MIME_TYPE] == 'application/zip') { + $flagDoUnzip = true; + } + + // Do upload $pathFileName = $this->doUpload($formElement, ($formValues[$column] ?? ''), $sip, $modeUpload); + if ($flagDoUnzip && $pathFileName != '') { + if ($formElement[FE_FILE_UNZIP] == '' || $formElement[FE_FILE_UNZIP] == '1') { + // Set default dir. + $formElement[FE_FILE_UNZIP] = HelperFile::joinPathFilename(dirname($pathFileName), FE_FILE_UNPACK_DIR); + } + + // Backup STORE_VAR - will be changed in doUnzip() + $tmpStoreVar = $this->store->getStore(STORE_VAR); + $this->doUnzip($formElement, $pathFileName); + // Restore STORE_VAR + $this->store->setStore($tmpStoreVar, STORE_VAR, true); + } if ($modeUpload == UPLOAD_MODE_DELETEOLD && $pathFileName == '') { $pathFileNameTmp = ''; // see '4' @@ -540,15 +578,15 @@ class Save { // No new upload and no existing: take care to remove previous upload file statistics. $this->store->unsetVar(VAR_FILE_MIME_TYPE, STORE_VAR); $this->store->unsetVar(VAR_FILE_SIZE, STORE_VAR); - $vars[VAR_FILE_SIZE] = 0; - $vars[VAR_FILE_MIME_TYPE] = ''; } else { - $vars = HelperFile::getFileStat($pathFileNameTmp); + $this->store->appendToStore($vars, STORE_VAR); } // If given: fire a sqlBefore query - $this->evaluate->parse($formElement[FE_SQL_BEFORE]); + if (!$flagDoUnzip) { + $this->evaluate->parse($formElement[FE_SQL_BEFORE]); + } // Upload Type: Simple or Advanced // If (isset($primaryRecord[$column])) { - see #5048 - isset does not deal correctly with NULL! @@ -567,22 +605,101 @@ class Save { } } elseif (isset($formElement[FE_IMPORT_TO_TABLE]) && !isset($formElement[FE_SLAVE_ID])) { // Excel import on nonexisting column -> no upload + } elseif ($flagDoUnzip) { + // If ZIP and advanced upload: process it not here but via doUnzip. } else { // 'Advanced Upload' $this->doUploadSlave($formElement, $modeUpload); } // If given: fire a sqlAfter query - $this->evaluate->parse($formElement[FE_SQL_AFTER]); - + if (!$flagDoUnzip) { + $this->evaluate->parse($formElement[FE_SQL_AFTER]); + } } + // Clean up + HelperFile::chdir($cwd); + // Only used in 'Simple Upload' if (count($newValues) > 0) { $this->updateRecord($this->formSpec[F_TABLE_NAME], $newValues, $recordId, $this->formSpec[F_PRIMARY_KEY]); } } + /** + * Unzip $pathFileName to $formElement[FE_FILE_UNZIP]. Before final extract, fire FE_SQL_VALIDATE. + * For each file in ZIP: + * - Fill STORE_VAR with VAR_FILENAME, VAR_FILENAME_ONLY, VAR_FILENAME_BASE, VAR_FILENAME_EXT, VAR_FILE_MIME_TYPE, VAR_FILE_SIZE. + * - Fire $formElement[FE_SQL_VALIDATE] + * - Fire FE_SLAVE_ID, FE_SQL_BEFORE, FE_SQL_INSERT, FE_SQL_UPDATE, FE_SQL_DELETE, FE_SQL_AFTER + * + * @param array $formElement + * @param string $pathFileName + * @throws \CodeException + * @throws \DbException + * @throws \UserFormException + * @throws \UserReportException + */ + private function doUnzip(array $formElement, $pathFileName) { + + if (!is_readable($pathFileName)) { + throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Open ZIP file failed", + ERROR_MESSAGE_TO_DEVELOPER => "File: " . $pathFileName]), + ERROR_IO_ZIP_OPEN); + } + + $zip = new ZipArchive(); + $res = $zip->open($pathFileName); + if ($res !== true) { + throw new \UserFormException(json_encode([ERROR_MESSAGE_TO_USER => "Open ZIP file failed" . HelperFile::zipFileErrMsg($res), + ERROR_MESSAGE_TO_DEVELOPER => "File: " . $pathFileName]), ERROR_IO_ZIP_OPEN); + } + + // Extract + if (false === $zip->extractTo($formElement[FE_FILE_UNZIP])) { + throw new \UserFormException("Failed to extract ZIP.", ERROR_IO_ZIP_OPEN); + } + + // Do sqlValidate() - to get mime type of zipped items, the archive has already been extracted. + if (!empty($formElement[FE_SQL_VALIDATE])) { + for ($i = 0; $i < $zip->numFiles; $i++) { + $stat = $zip->statIndex($i); + + $itemPathFileName = HelperFile::joinPathFilename($formElement[FE_FILE_UNZIP], $stat['name']); + $this->store->appendToStore(HelperFile::getFileStat($itemPathFileName), STORE_VAR); + $this->store->appendToStore(HelperFile::pathinfo($itemPathFileName), STORE_VAR); + + HelperFormElement::sqlValidate($this->evaluate, $formElement); + } + } + + // Process + if (!isset($formElement[FE_SLAVE_ID])) { + $formElement[FE_SLAVE_ID] = ''; + } + + if (!empty($formElement[FE_SLAVE_ID] . $formElement[FE_SQL_BEFORE] . $formElement[FE_SQL_INSERT] . + $formElement[FE_SQL_UPDATE] . $formElement[FE_SQL_DELETE] . $formElement[FE_SQL_AFTER])) { + for ($i = 0; $i < $zip->numFiles; $i++) { + $stat = $zip->statIndex($i); + + $itemPathFileName = HelperFile::joinPathFilename($formElement[FE_FILE_UNZIP], $stat['name']); + $this->store->appendToStore(HelperFile::getFileStat($itemPathFileName), STORE_VAR); + $this->store->appendToStore(HelperFile::pathinfo($itemPathFileName), STORE_VAR); + + $this->evaluate->parse($formElement[FE_SQL_BEFORE]); + $this->doUploadSlave($formElement, UPLOAD_MODE_NEW); + $this->evaluate->parse($formElement[FE_SQL_AFTER]); + } + } + + // Close Zip + if (false === $zip->close()) { + throw new \UserFormException("Failed to close ZIP.", ERROR_IO_ZIP_OPEN); + } + } + /** * Process all Upload FormElements for the given $recordId. * After processing, &$formValues will be updated with the final filename. @@ -592,7 +709,7 @@ class Save { */ public function processAllImageCutFE() { - foreach ($this->feSpecNative AS $formElement) { + foreach ($this->feSpecNative as $formElement) { // skip non upload formElements if ($formElement[FE_TYPE] != FE_TYPE_IMAGE_CUT) { continue; @@ -631,7 +748,7 @@ class Save { $flagAllRequiredGiven = 1; - foreach ($this->feSpecNative AS $key => $formElement) { + foreach ($this->feSpecNative as $key => $formElement) { // Do not check retype slave FE. if (isset($formElement[FE_RETYPE_SOURCE_NAME])) { @@ -748,26 +865,26 @@ class Save { * Process upload for the given Formelement. If necessary, delete a previous uploaded file. * Calculate the final path/filename and move the file to the new location. * - * Check also: doc/CODING.md + * Check also: Documentation-develop/CODING.md * * @param array $formElement FormElement 'upload' * @param string $sipUpload SIP * @param Sip $sip * @param string $modeUpload UPLOAD_MODE_UNCHANGED | UPLOAD_MODE_NEW | UPLOAD_MODE_DELETEOLD | * UPLOAD_MODE_DELETEOLD_NEW - * * @return false|string New pathFilename or false on error * @throws \CodeException * @throws \DbException - * @throws \UserFormException - * @throws \UserReportException * @throws \PhpOffice\PhpSpreadsheet\Exception * @throws \PhpOffice\PhpSpreadsheet\Reader\Exception + * @throws \UserFormException + * @throws \UserReportException * @internal param $recordId */ private function doUpload($formElement, $sipUpload, Sip $sip, &$modeUpload) { $flagDelete = false; $modeUpload = UPLOAD_MODE_UNCHANGED; + $pathFileName = ''; // Status information about upload file $statusUpload = $this->store->getVar($sipUpload, STORE_EXTRA); @@ -781,15 +898,6 @@ class Save { $this->doImport($formElement, $tmpFile); } - // Upload - Take care the necessary target directories exist. - $cwd = getcwd(); - $sitePath = $this->store->getVar(SYSTEM_SITE_PATH, STORE_SYSTEM); - if ($cwd === false || $sitePath === false || !HelperFile::chdir($sitePath)) { - throw new \UserFormException( - json_encode([ERROR_MESSAGE_TO_USER => 'getcwd() failed or SITE_PATH undefined or chdir() failed', ERROR_MESSAGE_TO_DEVELOPER => "getcwd() failed or SITE_PATH undefined or chdir('$sitePath') failed."]), - ERROR_IO_CHDIR); - } - // Delete existing old file. if (isset($statusUpload[FILES_FLAG_DELETE]) && $statusUpload[FILES_FLAG_DELETE] == '1') { $arr = $sip->getVarsFromSip($sipUpload); @@ -819,8 +927,6 @@ class Save { Logger::logMessageWithPrefix($msg, $this->qfqLogFilename); } - HelperFile::chdir($cwd); - // Delete current used uniq SIP $this->store->setVar($sipUpload, array(), STORE_EXTRA); @@ -828,6 +934,8 @@ class Save { } /** + * Excel Import + * * @param $formElement * @param $fileName * @throws \CodeException @@ -977,7 +1085,7 @@ class Save { } // Import the data - foreach ($worksheetData AS $rowIndex => $row) { + foreach ($worksheetData as $rowIndex => $row) { $columnList = '`' . implode('`,`', $columnListArr) . '`'; $paramPlaceholders = str_repeat('?,', count($worksheetData[0]) - 1) . '?'; $insertSql = "INSERT INTO `$tableName` ($columnList) VALUES ($paramPlaceholders)"; @@ -989,7 +1097,7 @@ class Save { /** * Copy uploaded file from temporary location to final location. * - * Check also: doc/CODING.md + * Check also: Documentation-develop/CODING.md * * @param array $formElement * @param array $statusUpload @@ -1087,9 +1195,9 @@ class Save { } /** - * Check's if the file $pathFileName should be splitted in one file per page. If no: do nothing and return. + * Check's if the file $pathFileName should be split'ed in one file per PDF page. If no: do nothing and return. * The only possible split target file format is 'svg': fileSplit=svg. - * The splitted files will be saved under fileDestinationSplit=some/path/to/file.%02d.svg. A printf style token, + * The split'ed files will be saved under fileDestinationSplit=some/path/to/file.%02d.svg. A printf style token, * like '%02d', is needed to create distinguished filename's. See 'man pdf2svg' for further details. * For every created file, a record in table 'Split' is created (see splitSvg() ), storing the pathFileName of the * current page/file. @@ -1221,7 +1329,7 @@ class Save { * Create/update or delete the slave record. * * @param array $fe - * @param $modeUpload + * @param string $modeUpload UPLOAD_MODE_NEW|UPLOAD_MODE_DELETEOLD_NEW|UPLOAD_MODE_DELETEOLD * @return int * @throws \CodeException * @throws \DbException