Commit 208fd77f authored by Carsten  Rose's avatar Carsten Rose
Browse files

Feature 5333 / Thumbnail: first implementation.

parent 62a95292
Setup
==
=====
Include the main CodeMirror JavaScript in the HTML source:
......@@ -9,7 +9,7 @@ Add the class `qfq-codemirror` to a `<textarea>` element. This class marks the e
.Helper.codemirror()`.
Configuration
==
=============
Set the `data-config` attribute on the `<textarea>` element. The value has to be valid JSON. It will then be
deserialized and passed to CodeMirror during initialization. E.g.
......@@ -21,7 +21,7 @@ Refer to the [CodeMirror manual](https://codemirror.net/doc/manual.html) under s
configuration options.
Mode
==
====
The configuration option `mode` sets the mode to be used by CodeMirror. To work properly, the corresponding
JavaScript file has to be loaded. For instance, `mode: "text/x-sql"` requires to include `code-mirror-mode/sql/sql.js`
......
Thumbnail
=========
General
=======
* Thumbnails are created by special column name '_thumbnail'.
* The given parameter controls size and secure/public access mode.
* The thumbnails are stored in a public available directory or in a access protected directory. Both directories are
central configured in config.qfq.ini.
* The column '_thumbnail' checks if the thumbnail already exist or if it has to be created first.
* Cleaning the thumbnail directory is fine at any time. The next page reload will recreate any needed thumbs.
* The thumbnail filename is a MD5 hash of the original pathFilename. To detect any modified source, the timestamp of source
has to be older than of the thumbnail.
* SVG files will be rendered by 'inkscape'.
* All other file formats are rendered by GraphicsMagick 'convert'.
* The rendering process starts the rendering in the background. Therefore QFQ returns quickly, even for several new thumbnails.
* Secured thumbnails will detect if there is a rendering process pending: before the rendering starts, the thumbnail will
be created as an empty file. Public thumbnails are not protected. The client has to reload the page.
Secured by SIP
--------------
Thumbnails might contain sensitive data and needs to be protected by SIP.
QFQ / report generates a `<img src="api/download.php?s=...">` HTML tag.
If a thumbnail file is empty, `api/download.php` will wait up to ten seconds and than returning a 404.
Public
------
Saved in a public readable directory, referenced like `<img src="<public dir>/<hash>.png">`
How to use thumbnails
=====================
Report column '_thumbnail'
--------------------------
Secure: `SELECT 'T:<pathFilename>|[W:[<width>][x<Height>]]' AS _thumbnail` >> <img src="api/thumbnail.php?s=badcaffee1234">
Public: `SELECT 's:0|T:<pathFilename>|[W:[<width>][x<Height>]]' AS _thumbnail` >> <img src="<public dir>/<hash>.png">
Report column '_link'
---------------------
Part of a '_link' definition. Secure or Public access is equal to the link itself.
Form
----
Inside of a form with the new feature #5422 {{COLUMN '...' AS _thumbnail}}
This diff is collapsed.
......@@ -125,4 +125,11 @@ WKHTMLTOPDF = /opt/wkhtmltox/bin/wkhtmltopdf
; FORM_LANGUAGE_D_LABEL = E.g. FORM_LANGUAGE_D_ID = Chinese
; Pressing the 'enter' key is equal to save and close
; enterAsSubmit = 1
\ No newline at end of file
; enterAsSubmit = 1
; Attention: be sure that 'fileadmin/protected' is really locked down by a webserver directive.
; See https://docs.typo3.org/typo3cms/drafts/github/T3DocumentationStarter/Public-Info-053/Manual.html#secure-direct-fileaccess
; thumbnailDirSecure = fileadmin/protected/qfqThumbnail
; thumbnailDirPublic = typo3temp/qfqThumbnail
; cmdInkscape = inkscape
; cmdConvert = convert
\ No newline at end of file
......@@ -106,6 +106,8 @@ const ROW_KEYS = "keys";
const KVP_IF_VALUE_EMPTY_COPY_KEY = 'if_value_empty_copy_key';
const KVP_VALUE_GIVEN = 'value_given';
// https://lib2.colostate.edu/wildlife/atoz.php?letter=ALL
// QFQ Error Codes
const ERROR_UNKNOW_SANITIZE_CLASS = 1001;
......@@ -288,6 +290,8 @@ const ERROR_SESSION_BROKEN_SCRIPT_PATH = 2402;
const ERROR_HTML2PDF_MISSING_CONFIG = 2500;
const ERROR_HTML2PDF_WKHTML_NOT_EXECUTABLE = 2501;
const ERROR_HTML2PDF_WKHTML_FAILED = 2502;
const ERROR_THUMBNAIL_RENDER = 2600;
//
// Store Names: Identifier
//
......@@ -501,6 +505,14 @@ const SYSTEM_FORM_LANGUAGE_D_LABEL = 'FORM_LANGUAGE_D_LABEL';
const SYSTEM_ENTER_AS_SUBMIT = 'enterAsSubmit';
// Thumbnail
const SYSTEM_CMD_INKSCAPE = 'cmdInkscape';
const SYSTEM_CMD_CONVERT = 'cmdConvert';
const SYSTEM_THUMBNAIL_DIR_SECURE = 'thumbnailDirSecure';
const SYSTEM_THUMBNAIL_DIR_SECURE_DEFAULT = 'fileadmin/protected/qfqThumbnail';
const SYSTEM_THUMBNAIL_DIR_PUBLIC = 'thumbnailDirPublic';
const SYSTEM_THUMBNAIL_DIR_PUBLIC_DEFAULT = 'typo3temp/qfqThumbnail';
const DOCUMENTATION_QFQ = 'DOCUMENTATION_QFQ';
const DOCUMENTATION_QFQ_URL = 'https://docs.typo3.org/typo3cms/drafts/github/T3DocumentationStarter/Public-Info-053/Manual.html';
......@@ -520,6 +532,7 @@ const SYSTEM_REPORT_COLUMN_INDEX = 'reportColumnIndex'; // Keyname of SQL-column
const SYSTEM_REPORT_COLUMN_NAME = 'reportColumnName'; // Keyname of SQL-column processed at the moment.
const SYSTEM_REPORT_COLUMN_VALUE = 'reportColumnValue'; // Keyname of SQL-column processed at the moment.
const SYSTEM_REPORT_FULL_LEVEL = 'reportFullLevel'; // Keyname of SQL-column processed at the moment.
const SYSTEM_MESSAGE_DEBUG = 'messageDebug';
const SYSTEM_DOWNLOAD_POPUP = 'hasDownloadPopup'; // Marker which is set to 'true' if there is at least one Download Link rendered
const DOWNLOAD_POPUP_REQUEST = 'true';
const DOWNLOAD_POPUP_REPLACE_TEXT = '#downloadPopupReplaceText#';
......@@ -1112,6 +1125,9 @@ const COLUMN_PATH_FILE_NAME = 'pathFileName';
// Used to in SIP Store to handle 'delete' after upload
const EXISTING_PATH_FILE_NAME = '_existingPathFileName';
const THUMBNAIL_WIDTH_DEFAULT = '150x';
const THUMBNAIL_UNKNOWN_TYPE = 'typo3/sysext/frontend/Resources/Public/Icons/FileIcons/';
//SENDMAIL
const SENDMAIL_TOKEN_RECEIVER = 't';
const SENDMAIL_TOKEN_RECEIVER_LONG = 'to';
......@@ -1180,33 +1196,37 @@ const TOKEN_VALID_LIST = 'sql|head|althead|altsql|tail|shead|stail|rbeg|rend|ren
const TOKEN_COLUMN_CTRL = '_';
//Report: Column Token
const COLUMN_PPAGE = "Page";
const COLUMN_PPAGEC = "Pagec";
const COLUMN_PPAGED = "Paged";
const COLUMN_PPAGEE = "Pagee";
const COLUMN_PPAGEH = "Pageh";
const COLUMN_PPAGEI = "Pagei";
const COLUMN_PPAGEN = "Pagen";
const COLUMN_PPAGES = "Pages";
const COLUMN_PPDF = "Pdf";
const COLUMN_ZZIP = "Zip";
const COLUMN_FFILE = "File";
const COLUMN_PAGE = "page";
const COLUMN_PAGEC = "pagec";
const COLUMN_PAGED = "paged";
const COLUMN_PAGEE = "pagee";
const COLUMN_PAGEH = "pageh";
const COLUMN_PAGEI = "pagei";
const COLUMN_PAGEN = "pagen";
const COLUMN_PAGES = "pages";
const COLUMN_PDF = "pdf";
const COLUMN_FILE = "file";
const COLUMN_ZIP = "zip";
const COLUMN_NL2BR = "nl2br";
const COLUMN_HTMLENTITIES = "htmlentities";
const COLUMN_STRIPTAGS = "striptags";
const COLUMN_LINK = 'link';
const COLUMN_EXEC = 'exec';
const COLUMN_THUMBNAIL = 'thumbnail';
const COLUMN_PPAGE = 'Page';
const COLUMN_PPAGEC = 'Pagec';
const COLUMN_PPAGED = 'Paged';
const COLUMN_PPAGEE = 'Pagee';
const COLUMN_PPAGEH = 'Pageh';
const COLUMN_PPAGEI = 'Pagei';
const COLUMN_PPAGEN = 'Pagen';
const COLUMN_PPAGES = 'Pages';
const COLUMN_PPDF = 'Pdf';
const COLUMN_ZZIP = 'Zip';
const COLUMN_FFILE = 'File';
const COLUMN_PAGE = 'page';
const COLUMN_PAGEC = 'pagec';
const COLUMN_PAGED = 'paged';
const COLUMN_PAGEE = 'pagee';
const COLUMN_PAGEH = 'pageh';
const COLUMN_PAGEI = 'pagei';
const COLUMN_PAGEN = 'pagen';
const COLUMN_PAGES = 'pages';
const COLUMN_PDF = 'pdf';
const COLUMN_FILE = 'file';
const COLUMN_ZIP = 'zip';
const COLUMN_NL2BR = 'nl2br';
const COLUMN_HTMLENTITIES = 'htmlentities';
const COLUMN_STRIPTAGS = 'striptags';
const COLUMN_WRAP_TOKEN = '+';
......@@ -1269,6 +1289,9 @@ const TOKEN_FILE = 'F';
const TOKEN_FILE_DEPRECATED = 'f'; // since 5.12.17
const TOKEN_DOWNLOAD_MODE = 'M';
const TOKEN_THUMBNAIL = 'T';
const TOKEN_THUMBNAIL_WIDTH = 'W';
const TOKEN_ACTION_DELETE = 'x';
const TOKEN_ACTION_DELETE_AJAX = 'a';
const TOKEN_ACTION_DELETE_REPORT = 'r';
......@@ -1346,10 +1369,12 @@ const EXCEPTION_EDIT_FORM = 'Edit';
const EXCEPTION_TIMESTAMP = 'Timestamp';
const EXCEPTION_CODE = 'Code';
const EXCEPTION_MESSAGE = 'Message';
const EXCEPTION_MESSAGE = 'Message'; // Will be shown on every exception. NO sensitive data here!
const EXCEPTION_MESSAGE_DEBUG = SYSTEM_MESSAGE_DEBUG; // Will only be shown as debugging (Typically BE user is logged in)
const EXCEPTION_FILE = 'File';
const EXCEPTION_LINE = 'Line';
const EXCEPTION_STACKTRACE = 'Stacktrace';
const EXCEPTION_TABLE_CLASS = 'table table-hover qfq-table-80';
\ No newline at end of file
const EXCEPTION_TABLE_CLASS = 'table table-hover qfq-table-80';
......@@ -115,7 +115,7 @@ class Evaluate {
$posFirstClose = strpos($result, $this->endDelimiter);
$this->store->setVar(SYSTEM_SQL_RAW, $line, STORE_SYSTEM);
// $this->store->setVar(SYSTEM_SQL_RAW, $line, STORE_SYSTEM);
while ($posFirstClose !== false) {
......
......@@ -144,7 +144,7 @@ class File {
$listElementMimeType = trim($listElementMimeType);
if ($listElementMimeType == '') {
continue; // will be skipped
} elseif ($listElementMimeType[0] == '.') { // Check for defintion 'filename extension'
} elseif ($listElementMimeType[0] == '.') { // Check for definition 'filename extension'
if ('.' . $path_parts['extension'] == $listElementMimeType) {
return true;
}
......
......@@ -21,7 +21,8 @@ require_once(__DIR__ . '/../helper/OnArray.php');
class AbstractException extends \Exception {
public $messageArray = array();
public $messageArrayDebug = array();
public $messageArrayDebug = [EXCEPTION_MESSAGE_DEBUG => ''];
public $store = null;
protected $file = '';
......@@ -67,6 +68,8 @@ class AbstractException extends \Exception {
// Debug Information
if ($store !== null && Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_YES, $store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM))) {
$this->messageArrayDebug[EXCEPTION_MESSAGE_DEBUG] = Store::getVar(EXCEPTION_MESSAGE_DEBUG, STORE_SYSTEM);
$arrDebugShow = array_merge([EXCEPTION_REPORT_FULL_LEVEL => Store::getVar(SYSTEM_REPORT_FULL_LEVEL, STORE_SYSTEM)], $this->messageArrayDebug);
$arrDebugShow[EXCEPTION_SIP] = $store->getStore(STORE_SIP);
......
<?php
/**
* Created by PhpStorm.
* User: crose
* Date: 2/10/18
* Time: 3:14 PM
*/
namespace qfq;
class OnString {
/**
* Returns part of haystack string starting from and including the last occurrence of needle to the end of haystack.
*
* strrstr('hello/world/to/the/limit', '/') = 'limit'
*
* @param $haystack
* @param $needle
* @return string
*/
public static function strrstr($haystack, $needle) {
return substr($haystack, strpos($haystack, $needle) + 1);
}
}
......@@ -1396,4 +1396,24 @@ class Support {
}
}
/**
* Executes the Command in $cmd
* RC: if RC==0 Returns Output, else 'RC - Output'
*
* @param string $cmd : command to start
*
* @return string The content that is displayed on the website
*/
public function myExec($cmd) {
exec($cmd, $arr, $rc);
$output = implode('<br>', $arr);
if ($rc != 0) {
$output = $rc . " - " . $output;
}
return ($output);
}
}
\ No newline at end of file
<?php
/**
* Created by PhpStorm.
* User: crose
* Date: 2/10/18
* Time: 3:14 PM
*/
namespace qfq;
class Token {
/**
* Verify Empty values. If appropriate, set defaults, if not throw an exception.
*
* @param string $key
* @param string $value
*
* @return string
* @throws UserReportException
*/
public static function checkForEmptyValue($key, $value) {
if ($value !== '') {
return $value;
}
$value = '';
switch ($key) {
case TOKEN_URL:
case TOKEN_MAIL:
case TOKEN_GLYPH:
throw new UserReportException ("Missing value for token '$key'", ERROR_MISSING_VALUE);
break;
case TOKEN_RIGHT:
$value = 'r';
break;
case TOKEN_ENCRYPTION:
$value = '1';
break;
case TOKEN_SIP:
$value = '1';
break;
case TOKEN_BOOTSTRAP_BUTTON:
$value = '1';
break;
case TOKEN_QUESTION:
$value = DEFAULT_QUESTION_TEXT;
break;
case TOKEN_RENDER:
$value = DEFAULT_RENDER_MODE;
break;
case TOKEN_ACTION_DELETE:
$value = DEFAULT_ACTION_DELETE;
break;
default:
}
return $value;
}
/**
* Explode a string of the form 'T:<arg1>|W:<arg2>|s' into an array [ 'T' => '<arg1>', 'W' => '<arg2>', 's' => '' ].
* Checks if a parameter is double defined.
*
* @param string $str
* @return array
* @throws UserReportException
*/
public static function explodeTokenString($str) {
$tokenGiven = array();
$control = array();
$param = explode(PARAM_DELIMITER, $str);
foreach ($param as $item) {
// Skip empty entries
if ($item === '') {
continue;
}
// u:www.example.com
$arr = explode(":", $item, 2);
$key = isset($arr[0]) ? $arr[0] : '';
$value = isset($arr[1]) ? $arr[1] : '';
// Bookkeeping defined parameter.
if (isset($tokenGiven[$key])) {
throw new UserReportException ("Multiple definitions for key '$key'", ERROR_MULTIPLE_DEFINITION);
}
$tokenGiven[$key] = true;
$control[$key] = self::checkForEmptyValue($key, $value);
}
return $control;
}
/**
* Converts a string of the form '[width]x[height]' to '[-w <width>][ -h <height]'
*
* @param string $dimension
* @return string
*/
public static function explodeDimension($dimension) {
$width = '';
$height = '';
if ($dimension[0] == 'x') {
$height = '-h ' . substr($dimension, 1);
} else {
$arr = explode('x', $dimension, 2);
if (count($arr) > 1) {
$width = '-w ' . $arr[0];
$height = ' -h ' . $arr[1];
} else {
$width = '-w ' . $dimension;
}
}
return $width . $height;
}
/**
* Executes the Command in $cmd
* RC: if RC==0 Returns Output, else 'RC - Output'
*
* @param string $cmd : command to start
*
* @param int $rc
* @return string The content that is displayed on the website
*/
public static function qfqExec($cmd, &$rc = 0) {
exec($cmd, $arr, $rc);
$output = implode('<br>', $arr);
if ($rc != 0) {
$output = $rc . " - " . $output;
}
return ($output);
}
}
\ No newline at end of file
......@@ -10,7 +10,7 @@
namespace qfq;
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
//use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
require_once(__DIR__ . '/../Constants.php');
require_once(__DIR__ . '/../store/Session.php');
......
......@@ -31,6 +31,7 @@ require_once(__DIR__ . '/../store/Store.php');
require_once(__DIR__ . '/../store/Sip.php');
require_once(__DIR__ . '/../exceptions/UserReportException.php');
require_once(__DIR__ . '/../helper/KeyValueStringParser.php');
require_once(__DIR__ . '/../helper/Token.php');
/*
* u:url
......@@ -502,7 +503,11 @@ class Link {
$keyName = $this->tableVarName[$key]; // convert token to name
$value = $this->checkForEmptyValue($key, $value);
if ($key == TOKEN_PAGE && $value == '') {
$value = $this->store->getVar(TYPO3_PAGE_ID, STORE_TYPO3); // If no pageid|pagealias is defined, take current page
}
$value = Token::checkForEmptyValue($key, $value);
$value = $this->checkValue($key, $value);
// Store value
......@@ -546,55 +551,6 @@ class Link {
return $vars;
}
/**
* Cleans and make existing the standard vars used every time to render a link.
*
* @return array
*/
private function initVars() {
return [
NAME_MAIL => '',
NAME_URL => '',
NAME_PAGE => '',
NAME_TEXT => '',
NAME_ALT_TEXT => '',
NAME_BOOTSTRAP_BUTTON => '',
NAME_IMAGE => '',
NAME_IMAGE_TITLE => '',
NAME_GLYPH => '',
NAME_GLYPH_TITLE => '',
NAME_QUESTION => '',
NAME_TARGET => '',
NAME_TOOL_TIP => '',
NAME_TOOL_TIP_JS => '',
NAME_URL_PARAM => '',
NAME_EXTRA_CONTENT_WRAP => '',
NAME_DOWNLOAD_MODE => '',
NAME_DOWNLOAD_ELEMENTS => array(),
NAME_RENDER => '0',
NAME_RIGHT => 'l',
NAME_SIP => '0',
NAME_ENCRYPTION => '0',
NAME_DELETE => '',
NAME_LINK_CLASS => '', // class name
NAME_LINK_CLASS_DEFAULT => '', // Depending of 'as page' or 'as url'. Only used if class is not explizit set.
NAME_ACTION_DELETE => '',
FINAL_HREF => '',
FINAL_CONTENT => '',
FINAL_SYMBOL => '',
FINAL_TOOL_TIP => '',
FINAL_CLASS => '',
FINAL_QUESTION => '',
];
}
/**
* Verify Empty values. If appropriate, set defaults, if not throw an exception.
*
......@@ -646,6 +602,54 @@ class Link {
return $value;
}
/**
* Cleans and make existing the standard vars used every time to render a link.
*
* @return array
*/
private function initVars() {
return [
NAME_MAIL => '',
NAME_URL => '',
NAME_PAGE => '',
NAME_TEXT => '',
NAME_ALT_TEXT => '',
NAME_BOOTSTRAP_BUTTON => '',
NAME_IMAGE => '',
NAME_IMAGE_TITLE => '',
NAME_GLYPH => '',
NAME_GLYPH_TITLE => '',
NAME_QUESTION => '',
NAME_TARGET => '',
NAME_TOOL_TIP => '',
NAME_TOOL_TIP_JS => '',
NAME_URL_PARAM => '',
NAME_EXTRA_CONTENT_WRAP => '',
NAME_DOWNLOAD_MODE => '',
NAME_DOWNLOAD_ELEMENTS => array(),
NAME_RENDER => '0',
NAME_RIGHT => 'l',
NAME_SIP => '0',
NAME_ENCRYPTION => '0',
NAME_DELETE => '',
NAME_LINK_CLASS => '', // class name
NAME_LINK_CLASS_DEFAULT => '', // Depending of 'as page' or 'as url'. Only used if class is not explizit set.
NAME_ACTION_DELETE => '',
FINAL_HREF => '',
FINAL_CONTENT => '',
FINAL_SYMBOL => '',
FINAL_TOOL_TIP => '',
FINAL_CLASS => '',
FINAL_QUESTION => '',
];
}
/**
* Validate value for token
......@@ -850,6 +854,7 @@ class Link {
return $urlNParam;
}
/**