Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
typo3
qfq
Commits
00d02c9d
Commit
00d02c9d
authored
Jul 29, 2020
by
Marc Egger
Browse files
Refs #9600 Edit report file using frontend editor.
parent
29ac8bc8
Pipeline
#3681
failed with stages
in 2 minutes and 22 seconds
Changes
4
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
extension/Classes/Core/Constants.php
View file @
00d02c9d
...
...
@@ -1461,6 +1461,7 @@ const T3DATA_BODYTEXT_RAW = 'bodytext-raw';
const
T3DATA_UID
=
'uid'
;
const
T3DATA_PID
=
'pid'
;
const
T3DATA_HEADER
=
'header'
;
const
T3DATA_REPORT_PATH_FILENAME
=
'reportPathFileName'
;
const
REPORT_INLINE_BODYTEXT
=
'bodytext'
;
const
REPORT_FILE_EXTENSION
=
'.qfqr'
;
...
...
@@ -1545,6 +1546,7 @@ const TOKEN_RECORD_ID = CLIENT_RECORD_ID;
const
TOKEN_DEBUG_BODYTEXT
=
TYPO3_DEBUG_SHOW_BODY_TEXT
;
const
TOKEN_DB_INDEX
=
F_DB_INDEX
;
const
TOKEN_CONTENT
=
'content'
;
const
TOKEN_REPORT_FILE
=
'file'
;
const
TOKEN_VALID_LIST
=
'sql|twig|head|althead|altsql|tail|shead|stail|rbeg|rend|renr|rsep|fbeg|fend|fsep|fskipwrap|rbgd|debug|form|r|debugShowBodyText|dbIndex|sqlLog|sqlLogMode|content|render'
;
...
...
extension/Classes/Core/Helper/HelperFile.php
View file @
00d02c9d
...
...
@@ -557,7 +557,7 @@ class HelperFile {
* @param $string
* @return string
*/
public
static
function
sanitizeFileName
(
$string
):
?string
public
static
function
sanitizeFileName
(
$string
)
//
: ?string
{
$res
=
strtolower
(
trim
(
preg_replace
(
'/[^A-Za-z0-9-]+/'
,
'-'
,
$string
),
'-'
));
$res
=
preg_replace
(
'~-+~'
,
'-'
,
$res
);
...
...
@@ -573,7 +573,7 @@ class HelperFile {
* @param $path
* @throws \UserFormException
*/
public
static
function
createPathRecursive
(
$path
):
void
public
static
function
createPathRecursive
(
$path
)
//
: void
{
if
(
!
is_dir
(
$path
))
{
$success
=
mkdir
(
$path
,
0777
,
true
);
...
...
@@ -585,5 +585,23 @@ class HelperFile {
}
}
}
/**
* Wrapper for file_put_contents which throws exception on failure.
*
* @param $pathFileName
* @param $content
* @throws \UserFormException
*/
public
static
function
file_put_contents
(
$pathFileName
,
$content
)
// : void
{
$success
=
file_put_contents
(
$pathFileName
,
$content
);
if
(
$success
===
false
)
{
throw
new
\
UserFormException
(
json_encode
([
ERROR_MESSAGE_TO_USER
=>
"Writing file failed."
,
ERROR_MESSAGE_TO_DEVELOPER
=>
"Can't write to file '
$pathFileName
'"
]),
ERROR_IO_WRITE_FILE
);
}
}
}
extension/Classes/Core/QuickFormQuery.php
View file @
00d02c9d
...
...
@@ -146,17 +146,10 @@ class QuickFormQuery {
$t3data
[
T3DATA_UID
]
=
0
;
}
// Read report file if file keyword exists
// Read report file
,
if file keyword exists
in bodytext
$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
;
$t3data
[
T3DATA_BODYTEXT
]
=
ReportAsFile
::
read_report_file
(
$reportPathFileNameFull
);
}
$btp
=
new
BodytextParser
();
...
...
@@ -203,67 +196,13 @@ class QuickFormQuery {
$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.
// TODO: ?CR:
uid could be 0. When does this happen?
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
);
$reportPathFileNameFull
=
ReportAsFile
::
create_file_from_ttContent
(
$t3data
[
T3DATA_UID
],
$this
->
dbArray
[
$this
->
dbIndexData
]);
}
// Save pathFileName for use in inline editor
$this
->
t3data
[
T3DATA_REPORT_PATH_FILENAME
]
=
$reportPathFileNameFull
;
}
/**
...
...
@@ -1792,15 +1731,16 @@ class QuickFormQuery {
return
$html
;
}
/** Constructs a form to directly edit qfq content elements inline.
/**
* Constructs a form to directly edit qfq content elements inline.
*
* @return string - the html code
* @throws \CodeException
* @throws \UserFormException
*/
private
function
buildInlineReport
()
{
// TODO: replace $uid with file path
$uid
=
$this
->
t3data
[
T3DATA_UID
];
$reportPathFileNameFull
=
$this
->
t3data
[
T3DATA_REPORT_PATH_FILENAME
];
$bodytext
=
$this
->
t3data
[
T3DATA_BODYTEXT_RAW
];
$header
=
$this
->
t3data
[
T3DATA_HEADER
];
...
...
@@ -1815,7 +1755,7 @@ class QuickFormQuery {
Support
::
doAttribute
(
'title'
,
'Save & Reload'
);
$saveBtnIcon
=
Support
::
renderGlyphIcon
(
GLYPH_ICON_CHECK
);
$saveBtn
=
Support
::
wrapTag
(
"<button
$saveBtnAttributes
>"
,
$saveBtnIcon
);
$header
=
"QFQ Page Content '
$header
'"
;
$header
=
"QFQ Page Content '
$header
'
.<br><small>File: '
$reportPathFileNameFull
'</small>
"
;
$headerBar
=
Support
::
wrapTag
(
"<div class='col-md-12 qfq-form-title'>"
,
$header
.
$saveBtn
);
$ttContentCode
=
Support
::
htmlEntityEncodeDecode
(
MODE_ENCODE
,
$bodytext
);
...
...
@@ -1841,28 +1781,17 @@ class QuickFormQuery {
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
* @throws \UserReportException
*/
public
function
saveReport
()
{
// TODO: replace $uid with file path
$uid
=
$this
->
store
->
getVar
(
T3DATA_UID
,
STORE_SIP
.
STORE_ZERO
,
SANITIZE_ALLOW_DIGIT
);
if
(
$uid
==
0
)
{
// Check if it was called with a SIP (containing a uid)
// If not, this might be an attack => cancel.
return
;
}
$bodytext
=
Support
::
htmlEntityEncodeDecode
(
MODE_DECODE
,
$_POST
[
REPORT_INLINE_BODYTEXT
]);
$dbT3
=
$this
->
store
->
getVar
(
SYSTEM_DB_NAME_T3
,
STORE_SYSTEM
);
// Update bodytext
$sql
=
"UPDATE `
$dbT3
`.`tt_content` SET `bodytext` = ?, `tstamp` = UNIX_TIMESTAMP(NOW()) WHERE `uid` = ?"
;
$this
->
dbArray
[
$this
->
dbIndexData
]
->
sql
(
$sql
,
ROW_REGULAR
,
[
$bodytext
,
$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
);
$bodytextNew
=
Support
::
htmlEntityEncodeDecode
(
MODE_DECODE
,
$_POST
[
REPORT_INLINE_BODYTEXT
]);
ReportAsFile
::
write_file_uid
(
$uid
,
$bodytextNew
,
$this
->
dbArray
[
$this
->
dbIndexData
]);
$this
->
formSpec
[
F_FORWARD_MODE
]
=
'auto'
;
}
...
...
extension/Classes/Core/Report/ReportAsFile.php
View file @
00d02c9d
<?php
// 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: show frontend editor even when an exception occured during report rendering
// Solution: in rendering of exception, add the editor to the html.
// TODO: frontend edit komplett auschalten koennen (CR)
// TODO: volle kontrolle ueber code, dass der nicht pauschal ausgeliefert wird. (CR)
// TODO: zentrale konfig, ob es angezeigt wird. (sekreteaerin soll nicht sehen obwohl BE login) (CR)
// TODO: pro user option, ob gezeigt wird. (CR)
// TODO: fix unittests run by gitlab runner.
// TODO: ?CR: QuickFormQuery.php L138: Why fallback $t3data[T3DATA_UID] = 0; and $t3data[T3DATA_BODYTEXT] = ''; ? When does this happen?
// TODO: ?CR: alle ?CR suchen und durchgehen.
namespace
IMATHUZH\Qfq\Core\Report
;
use
IMATHUZH\Qfq\Core\Database\Database
;
use
IMATHUZH\Qfq\Core\Helper\HelperFile
;
use
IMATHUZH\Qfq\Core\Store\Store
;
class
ReportAsFile
{
/**
* Finds the keyword file=<pathFileName> and returns pathFileName of report.
* Finds the keyword file=<pathFileName> and returns pathFileName of report
relative to CWD
.
*
* @param string $bodyText
* @return string|null
ReportP
athFileName
* @return string|null
p
athFileName
of report relative to CWD.
* @throws \CodeException
* @throws \UserFormException
* @throws \UserReportException
*/
public
static
function
parseFileKeyword
(
string
$bodyText
):
?string
public
static
function
parseFileKeyword
(
string
$bodyText
)
//
: ?string
{
if
(
preg_match
(
'/^\s*
file
\s*=\s*([^\s]*)/m'
,
$bodyText
,
$matches
))
{
if
(
preg_match
(
'/^\s*
'
.
TOKEN_REPORT_FILE
.
'
\s*=\s*([^\s]*)/m'
,
$bodyText
,
$matches
))
{
return
HelperFile
::
joinPathFilename
(
self
::
reportPath
(),
$matches
[
1
]);
}
else
{
return
null
;
}
}
/**
* Read report file from given pathFileName. Throw exception on failure.
*
* @param string $reportPathFileNameFull
* @return string|null
* @throws \UserReportException
*/
public
static
function
read_report_file
(
string
$reportPathFileNameFull
):
string
{
$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
);
}
return
$fileContents
;
}
/**
* Create new file named after the tt_content header or the UID if not defined.
* - Invalid characters are stripped from filename.
* - If file exists, throw exception.
* - The path of the file is derived the typo3 page structure (use page alias if exists, else page name).
* - The path is created if not exists.
*
* @param int $uid
* @param Database $databaseT3
* @return string pathFileName of report relative to CWD
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
* @throws \UserReportException
*/
public
static
function
create_file_from_ttContent
(
int
$uid
,
Database
$databaseT3
):
string
{
// Select tt_content
$dbT3
=
Store
::
getInstance
()
->
getVar
(
SYSTEM_DB_NAME_T3
,
STORE_SYSTEM
);
$sql
=
"SELECT `bodytext`, `header`, `pid` FROM `
$dbT3
`.`tt_content` WHERE `uid` = ?"
;
$ttContent
=
$databaseT3
->
sql
(
$sql
,
ROW_EXPECT_1
,
[
$uid
],
"Typo3 content element not found. Uid:
$uid
"
);
// read page path of tt-content element (use page alias if exists)
$reportPath
=
''
;
$pid
=
$ttContent
[
'pid'
];
while
(
intval
(
$pid
)
!==
0
)
{
$sql
=
"SELECT `pid`, `alias`, `title` FROM `
$dbT3
`.`pages` WHERE `uid` = ?"
;
$page
=
$databaseT3
->
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
(
$ttContent
[
'header'
]);
if
(
$fileName
===
null
)
{
$fileName
=
strval
(
$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
HelperFile
::
file_put_contents
(
$reportPathFileNameFull
,
$ttContent
[
'bodytext'
]);
// replace tt-content bodytext with file=<path>
$newBodytext
=
TOKEN_REPORT_FILE
.
"="
.
HelperFile
::
joinPathFilename
(
$reportPath
,
$fileName
)
.
REPORT_FILE_EXTENSION
;
$sql
=
"UPDATE `
$dbT3
`.`tt_content` SET `bodytext` = ?, `tstamp` = UNIX_TIMESTAMP(NOW()) WHERE `uid` = ?"
;
$databaseT3
->
sql
(
$sql
,
ROW_REGULAR
,
[
$newBodytext
,
$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`"
;
$databaseT3
->
sql
(
$sql
);
return
$reportPathFileNameFull
;
}
/**
* Overwrites report file given by the 'file=<path>' keyword of the tt_content element with the given uid.
*
* @param int $uid
* @param string $newContent
* @param Database $databaseT3
* @throws \CodeException
* @throws \DbException
* @throws \UserFormException
* @throws \UserReportException
*/
public
static
function
write_file_uid
(
int
$uid
,
string
$newContent
,
Database
$databaseT3
)
// : void
{
// Get the report file path from tt_content
$dbT3
=
Store
::
getInstance
()
->
getVar
(
SYSTEM_DB_NAME_T3
,
STORE_SYSTEM
);
$sql
=
"SELECT `bodytext` FROM `
$dbT3
`.`tt_content` WHERE `uid` = ?"
;
$ttContent
=
$databaseT3
->
sql
(
$sql
,
ROW_EXPECT_1
,
[
$uid
],
"Typo3 content element not found. Uid:
$uid
"
);
$reportPathFileNameFull
=
ReportAsFile
::
parseFileKeyword
(
$ttContent
[
'bodytext'
]);
if
(
$reportPathFileNameFull
===
null
)
{
throw
new
\
UserReportException
(
json_encode
([
ERROR_MESSAGE_TO_USER
=>
"No report file defined."
,
ERROR_MESSAGE_TO_DEVELOPER
=>
"The keyword '"
.
TOKEN_REPORT_FILE
.
"' is not present in the typo3 content element with id
$uid
"
]),
ERROR_FORM_NOT_FOUND
);
}
// Update report file
HelperFile
::
file_put_contents
(
$reportPathFileNameFull
,
$newContent
);
}
/**
* Return the path of the report directory relative to CWD.
* Create path if it doesn't exist.
...
...
@@ -46,10 +173,8 @@ class ReportAsFile
* @throws \UserFormException
* @throws \UserReportException
*/
p
ublic
static
function
reportPath
():
string
p
rivate
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
);
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment