Commit 11fa9e42 authored by Marc Egger's avatar Marc Egger

Refs #9600 report export and file read works.

parent 39fa2e75
Pipeline #3673 failed with stages
in 2 minutes and 15 seconds
......@@ -1459,8 +1459,10 @@ const INDEX_PHP = 'index.php';
const T3DATA_BODYTEXT = 'bodytext';
const T3DATA_BODYTEXT_RAW = 'bodytext-raw';
const T3DATA_UID = 'uid';
const T3DATA_PID = 'pid';
const T3DATA_HEADER = 'header';
const REPORT_INLINE_BODYTEXT = 'bodytext';
const REPORT_FILE_EXTENSION = '.qfqr';
// Special Column to check for uploads
const COLUMN_PATH_FILE_NAME = 'pathFileName';
......
......@@ -550,5 +550,40 @@ class HelperFile {
{
return preg_match('/^([-\.\w]+)$/', $fileName) > 0;
}
/**
* Remove non-alphanumeric characters and replace them with -
*
* @param $string
* @return string
*/
public static function sanitizeFileName($string): ?string
{
$res = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $string), '-'));
$res = preg_replace('~-+~', '-', $res);
if (empty($res)) {
return null;
}
return $res;
}
/**
* Creates the given path if it does not exist.
*
* @param $path
* @throws \UserFormException
*/
public static function createPathRecursive($path): void
{
if (!is_dir($path)) {
$success = mkdir($path, 0777, true);
if ($success === false) {
throw new \UserFormException(json_encode([
ERROR_MESSAGE_TO_USER => "Can't create file path.",
ERROR_MESSAGE_TO_DEVELOPER => "Can't create path: " . $path]),
ERROR_IO_WRITE_FILE);
}
}
}
}
......@@ -146,22 +146,18 @@ class QuickFormQuery {
$t3data[T3DATA_UID] = 0;
}
/////// DEBUG /////////
preg_match('/^\s*file\s*=\s*(.+)\/([^\/\s]+)/m', $t3data[T3DATA_BODYTEXT], $matches);
throw new \UserFormException(json_encode([
ERROR_MESSAGE_TO_USER => json_encode(ReportAsFile::parseFileKeyword($t3data[T3DATA_BODYTEXT]), JSON_PRETTY_PRINT),
ERROR_MESSAGE_TO_DEVELOPER => json_encode($matches, JSON_PRETTY_PRINT)
]));
///////////////////////
// TODO: find keyword file=<path> (regex ^file=[path]$)
// TODO: if keyword 'file' does not exist:
// read page path of tt-content element
// create path in filesystem if not exists
// create file with content bodytext. Filename: header slugg if exists, uid if not.
// replace tt-content bodytext with file=<path>
// TODO: read file. If not exists, throw exception.
// TODO: set bodytext to file content.
// Read report file if file keyword exists
$reportPathFileNameFull = ReportAsFile::parseFileKeyword($t3data[T3DATA_BODYTEXT]);
if ($reportPathFileNameFull !== null) {
$fileContents = file_get_contents($reportPathFileNameFull);
if ($fileContents === false) {
throw new \UserReportException(json_encode([
ERROR_MESSAGE_TO_USER => "File read error.",
ERROR_MESSAGE_TO_DEVELOPER => "Report file not found or no permission to read file: '$reportPathFileNameFull'"]),
ERROR_FORM_NOT_FOUND);
}
$t3data[T3DATA_BODYTEXT] = $fileContents;
}
$btp = new BodytextParser();
$t3data[T3DATA_BODYTEXT_RAW] = $t3data[T3DATA_BODYTEXT];
......@@ -205,6 +201,69 @@ class QuickFormQuery {
$dbIndex = $this->evaluate->parse($dbIndex);
$dbIndex = ($dbIndex == '') ? DB_INDEX_DEFAULT : $dbIndex;
$this->store->setVar(TOKEN_DB_INDEX, $dbIndex, STORE_TYPO3);
// Create report file if file keyword not found
// TODO: ?CR: $t3data might not contain the following values. Or uid could be 0.
if ($reportPathFileNameFull === null && $t3data[T3DATA_UID] !== 0) {
// read page path of tt-content element (use page alias if exists)
$dbT3 = $this->store->getVar(SYSTEM_DB_NAME_T3, STORE_SYSTEM);
$reportPath = '';
$pid = $t3data[T3DATA_PID];
while (intval($pid) !== 0) {
$sql = "SELECT `pid`, `alias`, `title` FROM `$dbT3`.`pages` WHERE `uid` = ?";
$page = $this->dbArray[$this->dbIndexData]->sql($sql, ROW_EXPECT_1,
[$pid], "Typo3 page not found. Uid: $pid");
$supDir = $page['alias'] !== '' ? $page['alias'] : $page['title'];
$reportPath = HelperFile::joinPathFilename(HelperFile::sanitizeFileName($supDir), $reportPath);
$pid = $page['pid'];
if (strlen($reportPath) > 4096) {
// Throw exception in case of infinite loop.
throw new \UserReportException(json_encode([
ERROR_MESSAGE_TO_USER => "Path too long.",
ERROR_MESSAGE_TO_DEVELOPER => "Report path too long: '$reportPath'"]),
ERROR_FORM_NOT_FOUND);
}
}
$reportPathFull = HelperFile::joinPathFilename(ReportAsFile::reportPath(), $reportPath);
// create path in filesystem if not exists
HelperFile::createPathRecursive($reportPathFull);
// add filename
$fileName = HelperFile::sanitizeFileName($t3data[T3DATA_HEADER]);
if ($fileName === null) {
$fileName = strval($t3data[T3DATA_UID]);
}
$reportPathFileNameFull = HelperFile::joinPathFilename($reportPathFull, $fileName) . REPORT_FILE_EXTENSION;
// if file exists, throw exception
if (file_exists($reportPathFileNameFull)) {
throw new \UserReportException(json_encode([
ERROR_MESSAGE_TO_USER => "Writing file failed.",
ERROR_MESSAGE_TO_DEVELOPER => "Can't export report to file. File already exists: '$reportPathFileNameFull'"]),
ERROR_FORM_NOT_FOUND);
}
// create file with content bodytext
$success = file_put_contents($reportPathFileNameFull, $t3data[T3DATA_BODYTEXT_RAW]);
if ($success === false) {
throw new \UserFormException(json_encode([
ERROR_MESSAGE_TO_USER => "Writing file failed.",
ERROR_MESSAGE_TO_DEVELOPER => "Can't write to file '$reportPathFileNameFull'"]),
ERROR_IO_WRITE_FILE);
}
// replace tt-content bodytext with file=<path>
$newBodytext = "file=" . HelperFile::joinPathFilename($reportPath, $fileName) . REPORT_FILE_EXTENSION;
$sql = "UPDATE `$dbT3`.`tt_content` SET `bodytext` = ?, `tstamp` = UNIX_TIMESTAMP(NOW()) WHERE `uid` = ?";
$this->dbArray[$this->dbIndexData]->sql($sql, ROW_REGULAR, [$newBodytext, $t3data[T3DATA_UID]]);
// Clear cache
// Need to truncate cf_cache_pages because it is used to restore page-specific cache
$sql = "DELETE FROM `$dbT3`.`cf_cache_pages`";
$this->dbArray[$this->dbIndexData]->sql($sql);
}
}
/**
......
......@@ -3,26 +3,52 @@
// TODO: QuickFormQuery.php: edit saveReport() such that it saves to file instead of DB. Search T3DATA_UID
// pass the file path instead of uid in sip. Escape could be a problem. Example: Search KeyValueStringParser::unparse($t3varsArray
// TODO: Add filename to Report Editor. Search T3DATA_HEADER
// TODO: abstract all these IO functions which throw exceptions
// TODO: replace keyword 'file' with constant
// TODO: ?CR: QuickFormQuery.php L138: Why fallback $t3data[T3DATA_UID] = 0; and $t3data[T3DATA_BODYTEXT] = ''; ? When does this happen?
namespace IMATHUZH\Qfq\Core\Report;
use IMATHUZH\Qfq\Core\Helper\HelperFile;
use IMATHUZH\Qfq\Core\Store\Store;
class ReportAsFile
{
/**
* Finds the keyword file=<path> and returns path and filename.
* Finds the keyword file=<pathFileName> and returns pathFileName of report.
*
* @param string $bodyText
* @return array|null [path, filename]
* @return string|null ReportPathFileName
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/
public static function parseFileKeyword(string $bodyText): ?array
public static function parseFileKeyword(string $bodyText): ?string
{
if (preg_match('/^\s*file\s*=\s*(.+)\/([^\/\s]+)/m', $bodyText, $matches)) {
return [$matches[1], $matches[2]];
if (preg_match('/^\s*file\s*=\s*([^\s]*)/m', $bodyText, $matches)) {
return HelperFile::joinPathFilename(self::reportPath(), $matches[1]);
} else {
return null;
}
}
/**
* Return the path of the report directory relative to CWD.
* Create path if it doesn't exist.
*
* @return string
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/
public static function reportPath(): string
{
// TODO: make private
$systemQfqProjectDir = Store::getInstance()->getVar(SYSTEM_QFQ_PROJECT_DIR_SECURE, STORE_SYSTEM);
$reportPath = HelperFile::correctRelativePathFileName(HelperFile::joinPathFilename($systemQfqProjectDir, 'report'));
HelperFile::createPathRecursive($reportPath);
return $reportPath;
}
}
\ No newline at end of file
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