diff --git a/doc/CODING.md b/doc/CODING.md index ebba160ce9d07bbfb2c5d2add6d51eed373862ee..b4c0af7e1972b03f6a8c4724cc1a5fcdd4ca7e5b 100644 --- a/doc/CODING.md +++ b/doc/CODING.md @@ -191,6 +191,51 @@ Form save * delete [STORE_EXTRA][<uploadSip>] * Step 3: update record with final <fileDestination> +Download +-------- + +A download might be: + * a single file (any type, will be detected on the fly), + * an export of several files as a ZIP archive, + * an export of a T3-'XML'-Page converted to Excel, + * a converted HTML page to PDF, + * a PDF file, concatenated on single PDF files and/or converted HTML page to PDF. + +'api/download.php' will be called with a SIP (no other vars used). The SIP contains: + * DOWNLOAD_EXPORT_FILENAME - any target filename, if none given take DOWNLOAD_OUTPUT_PDF ('output.pdf'). + * DONWLOAD_MODE - file / pdf / excel / zip. If not specified: + a) 'file' is the default if only one is given and if this is a file. + b) 'pdf' is the default if there are multiple TOKEN_URL, TOKEN_URL_PARAM, TOKEN_FILE in SIP_DOWNLOAD_PARAMETER found. + * SIP_DOWNLOAD_PARAMETER (base64 encoded) - contains all parameter to source elements. + Format: <format 1>:<element 1>|<format 2>:<element 2>|...|<format n>:<element n>| + + <format>: TOKEN_URL, TOKEN_URL_PARAM, TOKEN_FILE + <element>: depending on the token - see below + + URL: a) 'u:http://w3c.org', b) 'u:w3c.org/', c) 'u:w3c.org/2017/index.php?issue=23' + URL_PARAM: a) 'U:id=export&r=123', b) 'U:id=export&r=123&_orientation=landscape&_page-size=a3' + FILE: a) 'f:fileadmin/example.png' + + * In URL_PARAM extra parameter used by `wkhtmltopdf` can be specified. All Parameter, starting with '-' + will be extracted from the regular URL_PARAM and instead forwarded as options to `wkhtmlpdf` + + * The base64 encoding is necessary: + * to deliver multiple elements with the same token (e.g. multiple PDF files to concatenate). + * special parameter names, like 'id', should not force the regular interpretation of 'id' during conversion to a SIP. + +During preparing and delivering the download, a popup shows a spinning gear by default. The popup itself will display an +individual message. The popup needs some HTML code (only once per T3 page). +Download links might be generated in `report` as well as in `subrecords of forms`. To trigger the generation of the HTML +popup code, a variable DOWNLOAD_POPUP_REQUEST in STORE_SYSTEM will be set to 'true' (string) in class Link(), as soon as +the first download link is rendered. During internal rendering of the download link, the const text token +DOWNLOAD_POPUP_REPLACE_TEXT and DOWNLOAD_POPUP_REPLACE_TITLE will be replaced with individual texts, defined per download link. + + +Print +----- + +tbd + Formelement type: DATE / DATETIME / TIME ---------------------------------------- * Available Formats: diff --git a/extension/Documentation/Manual.rst b/extension/Documentation/Manual.rst index b568b3afbe5e75b11d049b4ee457268044ddd71d..87fca0ba632f1a1918142f94fc6739f2a200a8a5 100644 --- a/extension/Documentation/Manual.rst +++ b/extension/Documentation/Manual.rst @@ -2374,7 +2374,7 @@ The sum of these three columns should always be 12. Multiple Elements per row ^^^^^^^^^^^^^^^^^^^^^^^^^ -Every row is by default wrapped in a `<div class='form-group'>` and every column is wrapped in a `<div class='col-md-?>`. +Every row is by default wrapped in a `<div class='form-group'>` and every column is wrapped in a `<div class='col-md-?'>`. To display multiple input elements in one row, the wrapping of the *FormElement* row and of the three columns can be customized via the checkboxes of `Label / Input / Note`. Every open and every close tag can be individually switched on or off. @@ -3275,7 +3275,7 @@ Column: _link +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ |x | |Page |p:<pageId> |p:impressum |Prepend '?' or '?id=', no hostname qualifier (automatically set by browser), default link class: internal, default value: {{pageId}} | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ -|x | |Download |d |d |If an image is specified, it will be rendered inside the link, default link class: internal. Link points to `api/download.php` | +|x | |Download |d:[<exportFilename>] |d:complete.pdf |Link points to `api/download.php`. Additonal parameter are encoded into a SIP. 'Download' needs an enabled SIP. See `download`_. | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ | | |Text |t:<text> |t:Firstname Lastname |- | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ @@ -3303,7 +3303,7 @@ Column: _link +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ | | |Tooltip |o:<text> |o:More information here |Tooltip text | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ -| | |Alttext |a:<text> |a:Name of person |Alttext for images | +| | |Alttext |a:<text> |a:Name of person |a) Alttext for images, b) Message text for download popup window. | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ | | |Class |c:[n|i|e|<text>] |c:i |CSS class for link. n:no class attribut, i:internal (ext_localconf.php)(default), e:external (ext_localconf.php), <text>: explicit named| +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ @@ -3317,6 +3317,10 @@ Column: _link +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ | | |SIP |s[:0|1] |s, s:0, s:1 |If 's' or 's:1' a SIP entry is generated with all non Typo 3 Parameters. The URL contains only parameter 's' and Typo 3 parameter | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ +| | |Mode |M:file|pdf|excel|zip |M:file, M:pdf, M:excel |Mode. Used to specify type of download. One or more element sources needs to be configured. See `download`_. | ++---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ +| | |File |f:<filename> |f:fileadmin/file.pdf |Element source for download mode file|pdf|zip. See `download`_. | ++---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ | | |Delete record | x[:a|r|c] |x, x:r, x:c |a: ajax (only QFQ internal used), r: report (default), c: close (current page, open last page) | +---+---+--------------+-----------------------------------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ @@ -3384,7 +3388,8 @@ Link Examples +-----------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ |SELECT "U:form=Person&r=123|x|t:Delete" as _link |<a href="typo3conf/ext/qfq/qfq/api/delete.php?s=badcaffee1234">Delete</a> | +-----------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ -|SELECT "d:U:1_pageId=req&1_id=12&1_mode=html2pdf" |<a href="typo3conf/ext/qfq/qfq/api/download.php?s=badcaffee1234">Download</a> | +|SELECT "s:1|d:full.pdf|M:pdf|U:id=det1&r=12|U:id=det2|f:cv.pdf| |<a href="typo3conf/ext/qfq/qfq/api/download.php?s=badcaffee1234">Download</a> | +| t:Download|a:Create complete PDF - please wait" as _link | | +-----------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------+ .. _question: @@ -3435,75 +3440,96 @@ Examples: | SELECT "p:form_person|q:Edit Person:::10:0" AS _link | The Alert will be shown 10 seconds and is not modal. | +------------------------------------------------------------+---------------------------------------------------------------------------+ +.. _download: + Download ^^^^^^^^ -Download links can be used to offer: +Download offers: -* to download a single file, or -* to concatenate several files and/or web pages (=HTML to PDF) into one output file, or -* to create an `Excel` export. +* download a single file (any type), +* concatenate several files (uploaded) and/or web pages (=HTML to PDF) into one PDF output file, +* create an `excel` export (based on a Typo3 page which creates XML output), +* create a ZIP archive, filled with several files ('uploaded' or 'HTML to PDF'-converted). The downloads are SIP protected. Only the current user can use the link to download files. By using the `_link` columnname: -* the option `d` enables the download mode -* setting `s` (or `s=1`) is mandatory for download mode -By using `_download` or `_Download` as columnname the options `d` and `s` will be set by automatically. +* the option `d` initiate creating the download link and optional specifies an export filename, +* the optional `M` (Mode) specifies the export type (file, pdf, excel, zip), +* setting `s` (or `s=1`) is mandatory for the download function, +* the alttext `a` specifies a message in the dowload popup. + +By using `_pdf`, `_Pdf`, `_file`, `_File`, `_excel`, `_Excel`, `_zip`, `_Zip` as columnname the options `d`, `m` and `s` +will be set by automatically. All files will be read by PHP - therefore the directory might be protected against direct web access. This way is the preferred way to offer secure downloads via QFQ. In case the download needs a persistant URL (no SIP, no user session), a regular -link, pointing directly to a file, have to be used - the download described here won't help. +link, pointing directly to a file, have to be used - the download functionality described here is not appropriate. .. _download-parameter-files: -Specify file(s)/web page(s) -''''''''''''''''''''''''''' +Parameter and (element) sources +''''''''''''''''''''''''''''''' -The following parameter have to be specified as 'U' parameter. The SIP encoded parameter remains on the server and -are not transferred to the client. This prohibits parameter manipulation. +* *download*: `d[:<exportFilename>]` -* *Link parameter 'U:.....'* : + * *exportFilename* = <filename for save as> - Name, offered in the 'File save as' browser dialog. Default: 'output.pdf'. + The user typically expect meaningfull and distinct filenames for different download links. - * *exportFilename* = <filename for save as> - Name, offered in the 'File save as' browser dialog. Default: 'output.pdf'. - The user typically expect meaningfull and distinct filenames for different downloads. +* *popupMessage*: `a:<text>` - will be displayed in the popup window during download. If the creating/download is fast, + the window might disapear quicly. - * *mode* = <file | pdf | excel> - This parameter is optional and can be skipped in most situations. +* *mode*: `m:<mode>` - * If `mode=file`, the mimetype is derived dynamically from the specified file. All others are fix (PDF or Excel). - * In case of multiple files and/or web pages, only `pdf` is supported. - * `excel` is not implemented now. - * The **default** depends on the number of specified `elements` (=file or web page). If only one `file` is specifed, - the default is `file`. If there is a) a page defined or b) multiple elements, the default is `pdf`. + * *mode* = <file | pdf | excel | zip> - This parameter is optional and can be skipped in most situations. Mandatory + for 'excel', 'zip'. - * For `web page` or `excel` export, all pages have to be specified with the necessary parameters (might - vary per page). The preceeding number `<i>` aggregates all parameter to one element (=`file` or `page`). The ordering has to start with `1` - and has to be consecutive. + * If `m:file`, the mimetype is derived dynamically from the specified file. In this mode, only one element source + is allowed per download link (no concatenation). - * *<i>_id* = <Typo3 pageId> - `id` is fix and mandatory for `web page` and `excel`. - * *<i>_<keyname>* = <value key i> - <keyname> is free of choice except `id` and `file`. + * In case of multiple element sources, only `pdf` or `zip` is supported. + * `m:excel` is not implemented now. + * If `m:zip` is used together with `U:...` oder `u:..`, those HTML pages will be converted to PDF and have technical + filenames inside the archive. + * If not specified, the **default** depends on the number of specified element sources (=file or web page). - * For `file` + * If only one `file` is specifed, the default is `file`. + * If there is a) a page defined or b) multiple elements, the default is `pdf`. - * *<i>_file* = <pathfilename> - `file` is fix and mandatory for direct `file` access. +* *element sources* - for `m:pdf` or `m:zip`, all of the following three element sources might be specified multiple times. + Any combination and order of the three options are allowed. + * *file*: `f:<pathFilename>` - relative or absolute pathFilename offered for a) download (single), or to be concatenated + in a PDF or ZIP. + * *urlParam*: `U:id=<t3 page>&<key 1>=<value 1>&<key 2>=<value 2>&...&<key n>=<value n> + * *url*: `u:<url>` - any URL, pointing to an internal or external destination. + + + * *Options* for `urlParam` or `url`: + + * The 'HTML to PDF' will be done via `wkhtmltopdf`. + * All possible options, suitable for `wkhtmltopdf`, can be submitted in the `u:...` or `U:...` element source. + Check https://wkhtmltopdf.org/usage/wkhtmltopdf.txt. Examples see below. Most of the other Link-Class attributes can be used to customize the link. Example: :: - # single `file` - SELECT "d|s|t:PDF|U:exportFilename=final.pdf&1_file=fileadmin/pdf/test.pdf" AS _link + # single `file`. Specifying a popup message window text is not necessary, cause a file directly accessed is fast. + SELECT "d:file.pdf|s|t:Download|f:fileadmin/pdf/test.pdf" AS _link # single `file`, with mode - SELECT "d|s|t:PDF|U:exportFilename=final.pdf&mode=file&1_file=fileadmin/pdf/test.pdf" AS _link - - # two pages (1_id, 2_id) and one file (3_file) - SELECT "d|s|t:PDF|U:exportFilename=final.pdf&1_id=exportP1&2_id=123&3_file=fileadmin/pdf/test.pdf" AS _link - # two pages (1_id, 2_id) and one file (3_file) with mode - SELECT "d|s|t:PDF|U:exportFilename=final.pdf&mode=pdf&1_id=exportP1&2_id=123&3_file=fileadmin/pdf/test.pdf" AS _link + SELECT "d:file.pdf|m:pdf|s|t:Download|f:fileadmin/pdf/test.pdf" AS _link + + # three sources: two pages and one file + SELECT "d:complete.pdf|s|t:Complete PDF|U:id=detail&r=1|U:id=detail2&r=1|f:fileadmin/pdf/test.pdf" AS _link AS _link + # three sources: two pages and one file + SELECT "d:complete.pdf|s|t:Complete PDF|U:id=detail&r=1|U:id=detail2&r=1|f:fileadmin/pdf/test.pdf" AS _link AS _link + # three sources: two pages and one file, the second page will be in landscape and pagesize A3 + SELECT "d:complete.pdf|s|t:Complete PDF|U:id=detail&r=1|U:id=detail2&r=1&--orientation=Landscape&--page-size=A3|f:fileadmin/pdf/test.pdf" AS _link AS _link .. diff --git a/extension/qfq/api/download.php b/extension/qfq/api/download.php index fd5b35784dff99beef5fc153198b4a1c57dc377b..07de5dd3ceb3b930ab1b5c2b058fd0cc5f987813 100644 --- a/extension/qfq/api/download.php +++ b/extension/qfq/api/download.php @@ -4,6 +4,8 @@ * User: crose * Date: 4/17/17 * Time: 5:51 PM + * + * Check: CODING.md > Download */ namespace qfq; @@ -11,19 +13,31 @@ namespace qfq; use qfq; require_once(__DIR__ . '/../qfq/report/Download.php'); -//require_once(__DIR__ . '/../qfq/store/Store.php'); require_once(__DIR__ . '/../qfq/Constants.php'); +require_once(__DIR__ . '/../qfq/exceptions/DownloadException.php'); +require_once(__DIR__ . '/../qfq/exceptions/CodeException.php'); +require_once(__DIR__ . '/../qfq/exceptions/DbException.php'); +require_once(__DIR__ . '/../qfq/exceptions/ErrorHandler.php'); + + +set_error_handler("\\qfq\\ErrorHandler::exception_error_handler"); + +$data = ''; try { $download = new \qfq\Download(); - // If all is fine - this function never returns! The output file is delivered and PHP is stopped after that. + // If all is fine - 'process()' never returns! The output file is delivered and PHP is stopped after that. $data = $download->process(); - +} catch (qfq\CodeException $e) { + $data = $e->formatMessage(); +} catch (qfq\DbException $e) { + $data = $e->formatMessage(); +} catch (qfq\DownloadException $e) { + $data = $e->formatMessage(); } catch (\Exception $e) { $data = "Exception: " . $e->getMessage(); } echo $data; - diff --git a/extension/qfq/qfq/Constants.php b/extension/qfq/qfq/Constants.php index 927d08c08865696d34e9170dd34b72462c88050a..18c5bb6ee7e3b15b19b97f8494e74ce2099e07dc 100644 --- a/extension/qfq/qfq/Constants.php +++ b/extension/qfq/qfq/Constants.php @@ -212,6 +212,7 @@ const ERROR_DOWNLOAD_CREATE_NEW_FILE = 1700; const ERROR_DOWNLOAD_NO_FILES = 1701; const ERROR_DOWNLOAD_NOTHING_TO_DO = 1702; const ERROR_DOWNLOAD_UNEXPECTED_MIMETYPE = 1703; +const ERROR_DOWNLOAD_UNEXPECTED_NUMBER_OF_SOURCES = 1704; // KeyValueParser const ERROR_KVP_VALUE_HAS_NO_KEY = 1900; @@ -366,6 +367,12 @@ const SYSTEM_REPORT_FULL_LEVEL = 'reportFullLevel'; // Keyname of SQL-column pro const SYSTEM_LDAP_1_RDN = 'LDAP_1_RDN'; // Credentials to access LDAP const SYSTEM_LDAP_1_PASSWORD = 'LDAP_1_PASSWORD'; // Credentials to access LDAP +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#'; +const DOWNLOAD_POPUP_REPLACE_TITLE = '#downloadPopupReplaceTitle#'; + // die folgenden Elemente sind vermutlich nicht noetig, wenn Store Klassen gloable Vars benutzt. //const SYSTEM_FORM_DEF = 'formDefinition'; // Type: SANITIZE_ALNUMX / AssocArray. Final form to process. Useful for error reporting. //const SYSTEM_FORM_ELEMENT_DEF = 'formElementDefinition'; // Type: SANITIZE_ALL / AssocArray. Formelement which are processed at the moment. Useful for error reporting. @@ -387,6 +394,10 @@ const SIP_FORM = CLIENT_FORM; const SIP_TABLE = 'table'; // delete a record from 'table' const SIP_URLPARAM = 'urlparam'; const SIP_MAKE_URLPARAM_UNIQ = '_makeUrlParamUniq'; // SIPs for 'new records' needs to be uniq per TAB! Therefore add a uniq parameter +const SIP_DOWNLOAD_PARAMETER = '_b64_download'; // Parametername, filled in SIP, to hold all download element parameter. + +const SIP_PREFIX_BASE64 = '_b64'; + // FURTHER: all extracted params from 'urlparam const ACTION_KEYWORD_SLAVE_ID = 'slaveId'; @@ -853,15 +864,54 @@ const DOWNLOAD_MODE = 'mode'; const DOWNLOAD_MODE_FILE = 'file'; const DOWNLOAD_MODE_PDF = 'pdf'; const DOWNLOAD_MODE_EXCEL = 'excel'; -const DOWNLOAD_EXPORT_FILENAME = 'exportFilename'; -const DOWNLOAD_PAGE_ID = 'id'; -const DOWNLOAD_FILE = 'file'; -const DOWNLOAD_FILE_PREFIX = 'qfq.temp'; +const DOWNLOAD_MODE_ZIP = 'zip'; +const DOWNLOAD_EXPORT_FILENAME = '_exportFilename'; +const DOWNLOAD_FILE_PREFIX = 'qfq.temp'; // temporary filename on server of single export file const DOWNLOAD_OUTPUT_PDF = 'output.pdf'; -const DOWNLOAD_PARAMETER_DELIMITER = '_'; + // HTML2PDF const HTML2PDF_PAGEID = 'id'; const HTML2PDF_PARAM_GET = 'paramGet'; const HTML2PDF_URL_PRINT = 'urlPrint'; +// Class: LINK +const PARAM_DELIMITER = '|'; + +const TOKEN_URL = 'u'; +const TOKEN_MAIL = 'm'; +const TOKEN_PAGE = 'p'; +const TOKEN_DOWNLOAD = 'd'; + +const TOKEN_TEXT = 't'; +const TOKEN_ALT_TEXT = 'a'; +const TOKEN_TOOL_TIP = 'o'; +const TOKEN_PICTURE = 'P'; +const TOKEN_BULLET = 'B'; +const TOKEN_CHECK = 'C'; +const TOKEN_DELETE = 'D'; +const TOKEN_EDIT = 'E'; +const TOKEN_HELP = 'H'; +const TOKEN_INFO = 'I'; +const TOKEN_NEW = 'N'; +const TOKEN_SHOW = 'S'; +const TOKEN_RENDER = 'r'; +const TOKEN_TARGET = 'g'; +const TOKEN_CLASS = 'c'; +const TOKEN_QUESTION = 'q'; +const TOKEN_ENCRYPTION = 'e'; +const TOKEN_SIP = 's'; +const TOKEN_URL_PARAM = 'U'; +const TOKEN_RIGHT = 'R'; +const TOKEN_FILE = 'f'; +const TOKEN_DOWNLOAD_MODE = 'M'; + +const TOKEN_ACTION_DELETE = 'x'; +const TOKEN_ACTION_DELETE_AJAX = 'a'; +const TOKEN_ACTION_DELETE_REPORT = 'r'; +const TOKEN_ACTION_DELETE_CLOSE = 'c'; + +const TOKEN_CLASS_NONE = 'n'; +const TOKEN_CLASS_INTERNAL = 'i'; +const TOKEN_CLASS_EXTERNAL = 'e'; + diff --git a/extension/qfq/qfq/QuickFormQuery.php b/extension/qfq/qfq/QuickFormQuery.php index 3910f1649d235fb8f87f9cd52c0811777e773782..2c54063aa3a38852df6dc49feb9e0542e8095f32 100644 --- a/extension/qfq/qfq/QuickFormQuery.php +++ b/extension/qfq/qfq/QuickFormQuery.php @@ -183,6 +183,7 @@ class QuickFormQuery { */ public function process() { $html = ''; + $rcHasDownloadLinks = false; if ($this->store->getVar(TYPO3_DEBUG_SHOW_BODY_TEXT, STORE_TYPO3) === 'yes') { $htmlId = HelperFormElement::buildFormElementId($this->formSpec[F_ID], 0, 0, 0); @@ -192,9 +193,16 @@ class QuickFormQuery { $html .= $this->doForm(FORM_LOAD); $html .= $this->doReport(); + // Only needed if there are download which show a popup during rendering/downloading. + if ($this->store->getVar(SYSTEM_DOWNLOAD_POPUP, STORE_SYSTEM) == DOWNLOAD_POPUP_REQUEST) { + $html .= $this->getModalCode(); + } + $class = $this->store->getVar(SYSTEM_CSS_CLASS_QFQ_CONTAINER, STORE_SYSTEM); - if ($class) + if ($class) { $html = Support::wrapTag("<div class='$class'>", $html); + } + // $feUidLoggedIn = isset($GLOBALS["TSFE"]->fe_user->user["uid"]) ? $GLOBALS["TSFE"]->fe_user->user["uid"] : false; // $feUidSession = $_SESSION[SESSION_NAME][SESSION_FE_USER_UID]; @@ -913,4 +921,35 @@ class QuickFormQuery { $this->store->setStore($tmpParam, STORE_SIP, true); } + + /** + * @return string + */ + private function getModalCode() { + + $code = <<<EOF + <!-- Modal --> + <div class="modal fade" id="qfqModal101" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> + <h4 class="modal-title" id="qfqModalTitle101">Loading Document</h4> + </div> + <div class="modal-body" style="text-align: center;"> + <span class="glyphicon glyphicon-cog glyphicon-spin text-large-with-margin text-primary"></span> + <p id="qfqModalText101">PDF Document is being generated. Please wait.</p> + </div> + <div class="modal-footer"> + <p>In progress..</p> + </div> + </div> + </div> + </div> +EOF; + + return $code; + } + + } \ No newline at end of file diff --git a/extension/qfq/qfq/report/Download.php b/extension/qfq/qfq/report/Download.php index 0c0330cfb6583257335f54fe7866d677b8d09386..8023199983d9a0e1eae4265a084ec81af935c26e 100644 --- a/extension/qfq/qfq/report/Download.php +++ b/extension/qfq/qfq/report/Download.php @@ -4,6 +4,8 @@ * User: crose * Date: 4/17/17 * Time: 11:32 AM + * + * Check: CODING.md > Download */ namespace qfq; @@ -65,44 +67,6 @@ class Download { $this->html2pdf = new Html2Pdf($this->store->getStore(STORE_SYSTEM)); } - /** - * Collect all elements, separated per element. - * Skip parameter which do not start with '<i>_' (i=integer). - * Create a numbered and sorted (by key) array, with all parameter belonging to one element in one subarray. - * - * E.g.: exportFilename=final.pdf&mode=pdf&1_id=exportP1&2_id=123&3_file=fileadmin/pdf/test.pdf&1_grId=78 - * result [ - * [1] => [ 'id' => 'exportP1', 'grId' = '78' ], - * [2] => [ 'id' => '123' ], - * [3] => [ 'file' => 'fileadmin/pdf/test.pdf' ], - * [mode] => 'pdf|file|excel', - * [exportFilename] => '....' - * ] - * - * @param array $vars - * @return array - */ - private function collectElement(array $vars) { - $final = array(); - - foreach ($vars as $key => $value) { - $splitArr = explode(DOWNLOAD_PARAMETER_DELIMITER, $key, 2); - $idx = $splitArr[0]; - if ((count($splitArr) == 2) && is_numeric($idx) && !isset($final[$idx])) { - $arr = OnArray::getArrayItemKeyNameStartWith($vars, $idx . DOWNLOAD_PARAMETER_DELIMITER); - if (count($arr) > 0) { - $final[$idx] = $arr; - } - - } - } - - // sort array - ksort($final, SORT_NATURAL); - - return $final; - } - /** * Concatenate all named files to one PDF file. Return name of new full PDF. * @@ -156,7 +120,11 @@ class Download { header("Content-type: $mimetype"); header("Content-Length: $length"); + // No idea if 'attachment' has disadvantages. +// header("Content-Disposition: attachment; filename=$outputFilename"); header("Content-Disposition: inline; filename=$outputFilename"); + header("Pragma: no-cache"); + header("Expires: 0"); print file_get_contents($filename); } @@ -178,32 +146,70 @@ class Download { } /** - * @param $element - * @return mixed + * Interprets $element and fetches corresponding content as file. + * + * @param string $element - U:id=myExport&r=12, u:http://www.nzz.ch/issue?nr=21, f:fileadmin/sample.pdf + * @return string filename - already ready or fresh exported. Fresh exported needs to be deleted later. * @throws DownloadException * @throws \exception */ - private function getFile($element) { -// $first = each($element); -// $key = $first['key']; -// $arr = explode(DOWNLOAD_PARAMETER_DELIMITER, $key, 2); -// $idx = $arr[0]; - - if (isset($element[DOWNLOAD_FILE])) { + private function getElement($element) { - $filename = $element[DOWNLOAD_FILE]; + $arr = explode(':', $element, 2); + if (count($arr) != 2) { + throw new DownloadException('Missing parameter for "' . $element . '"', ERROR_MISSING_REQUIRED_PARAMETER); + } - } elseif (isset($element[DOWNLOAD_PAGE_ID])) { + $token = $arr[0]; + $value = $arr[1]; - $filename = $this->html2pdf->page2pdf($element); + switch ($token) { + case TOKEN_URL: + case TOKEN_URL_PARAM: + $filename = $this->html2pdf->page2pdf($token, $value); + break; - } else { - throw new DownloadException('Neither found: ' . DOWNLOAD_FILE . ', ' . DOWNLOAD_PAGE_ID, ERROR_MISSING_REQUIRED_PARAMETER); + case TOKEN_FILE: + $filename = $value; + break; + default: + throw new DownloadException('Unknown token: "' . $token . '"', ERROR_UNKNOWN_TOKEN); + break; } return $filename; } + + /** + * Creates a ZIP Files of all given $files + * + * @param array $files + * @return string ZIP filename - has to be deleted later. + * @throws DownloadException + */ + private function zipFiles(array $files) { + + $zipFile = tempnam(sys_get_temp_dir(), DOWNLOAD_FILE_PREFIX); + if (false === $zipFile) { + throw new DownloadException("Error creating output file.", ERROR_DOWNLOAD_CREATE_NEW_FILE); + } + + $zip = new \ZipArchive(); + + if ($zip->open($zipFile, \ZipArchive::CREATE) !== TRUE) { + throw new DownloadException("Error creating/opening new empty zip file: $zipFile", ERROR_IO_OPEN); + } + + foreach ($files AS $filename) { + $localname = substr($filename, strrpos($filename, '/')); + $zip->addFile($filename, $localname); + } + $zip->close(); + + return $zipFile; + } + /** * exportFilename=<new filename> * mode=file | pdf | excel - default is 'file' in case of only one or 'pdf' in case of multiple sources. @@ -213,10 +219,11 @@ class Download { * Direct * <i>_file=<filename> * - * @param array $elements + * @param array $vars [ DOWNLOAD_EXPORT_FILENAME, DOWNLOAD_MODE, SIP_DOWNLOAD_PARAMETER ] * @throws DownloadException + * @internal param array $elements */ - private function doElements(array $elements, array $vars) { + private function doElements(array $vars) { $tmpFiles = array(); @@ -225,31 +232,35 @@ class Download { throw new DownloadException ("Error chdir($workDir)", ERROR_IO_CHDIR); } - $exportFilename = isset($vars[DOWNLOAD_EXPORT_FILENAME]) ? $vars[DOWNLOAD_EXPORT_FILENAME] : ''; + $exportFilename = empty($vars[DOWNLOAD_EXPORT_FILENAME]) ? DOWNLOAD_OUTPUT_PDF : $vars[DOWNLOAD_EXPORT_FILENAME]; + $downloadMode = $vars[DOWNLOAD_MODE]; + $elements = explode(PARAM_DELIMITER, $vars[SIP_DOWNLOAD_PARAMETER]); + // Get all files foreach ($elements as $element) { - $tmpFiles[] = $this->getFile($element); + $tmpFiles[] = $this->getElement($element); } - switch (count($tmpFiles)) { - case 0: - throw new DownloadException('Nothing to do.', ERROR_DOWNLOAD_NOTHING_TO_DO); + // Export, Concat File(s) + switch ($downloadMode) { + case DOWNLOAD_MODE_ZIP: + $filename = $this->zipFiles($tmpFiles); + break; + + case DOWNLOAD_MODE_EXCEL: + throw new DownloadException("Not implemented: $downloadMode", ERROR_NOT_IMPLEMENTED); break; - case 1: + case DOWNLOAD_MODE_FILE: $filename = $tmpFiles[0]; - if ($exportFilename == '') { - $exportFilename = strrchr($filename, '/'); - if ($exportFilename == false) { - $exportFilename = DOWNLOAD_OUTPUT_PDF; - } - } break; - default: + + case DOWNLOAD_MODE_PDF: $filename = $this->concatPdfFiles($tmpFiles); - if ($exportFilename == '') { - $exportFilename = DOWNLOAD_OUTPUT_PDF; - } + break; + + default: + throw new DownloadException("Unknown downloadMode: $downloadMode", ERROR_UNKNOWN_MODE); break; } @@ -267,8 +278,8 @@ class Download { public function process() { $vars = $this->store->getStore(STORE_SIP); - $elements = $this->collectElement($vars); - $this->doElements($elements, $vars); +// $elements = $this->collectElement($vars); + $this->doElements($vars); } } diff --git a/extension/qfq/qfq/report/Html2Pdf.php b/extension/qfq/qfq/report/Html2Pdf.php index 44c219e5d895141b318765de3d1119fa6ebfbe6a..1657dea81260a11fcb834ba403f7733d42ed649b 100644 --- a/extension/qfq/qfq/report/Html2Pdf.php +++ b/extension/qfq/qfq/report/Html2Pdf.php @@ -4,6 +4,9 @@ * User: crose * Date: 4/17/17 * Time: 10:17 PM + * + * Check: CODING.md > 'Print' and 'Download' + * */ namespace qfq; @@ -83,26 +86,44 @@ class Html2Pdf { } /** - * @return string + * Converts a Webpag (URL) to a PDF file. + * The URL might be a local Typo3 page or a full URL. + * + * @param string $token TOKEN_URL | TOKEN_URL_PARAM + * @param string $url id=exportPage&r=123, www.nzz.ch/issue?id=456 + * @return string rendered file - please delete later * @throws \exception */ - public function page2pdf(array $get) { + public function page2pdf($token, $url) { + $args = array(); + $urlParamNew = array(); - if (!isset($get[HTML2PDF_PAGEID]) || $get[HTML2PDF_PAGEID] == '') { - throw new \exception("Missing GET Parameter '" . HTML2PDF_PAGEID . "'."); + if ($token == TOKEN_URL_PARAM) { + $url = $this->config[SYSTEM_BASE_URL_PRINT] . '/?' . $url; } - $urlPrint = escapeshellarg($this->config[SYSTEM_BASE_URL_PRINT] . '/?' . KeyValueStringParser::unparse($get, '=', '&')); - $wkhtml = $this->config[SYSTEM_WKHTMLTOPDF]; + $urlParam = KeyValueStringParser::parse($url, '=', '&'); + foreach ($urlParam as $key => $value) { + if (substr($key, 0, 1) == '-') { + $args[$key] = $value; + } else { + $urlParamNew[$key] = $value; + } + } + + $args = OnArray::arrayEscapeshellarg($args); + $options = KeyValueStringParser::unparse($args, ' ', ' '); + $url = KeyValueStringParser::unparse($urlParamNew, '=', '&'); + + $urlPrint = escapeshellarg($url); + $wkhtmlToPdf = $this->config[SYSTEM_WKHTMLTOPDF]; - $rc = 0; $filename = tempnam(sys_get_temp_dir(), DOWNLOAD_FILE_PREFIX); $filenameEscape = escapeshellarg($filename); -// $cmd = $wkhtml . " '" . $urlPrint . "' " . $filename . " > $filename.log 2>&1"; -// $cmd = "$wkhtml '$urlPrint' $filename > $filename.log 2>&1"; - $cmd = "$wkhtml $urlPrint $filenameEscape"; + $cmd = "$wkhtmlToPdf $options $urlPrint $filenameEscape"; + $rc = 0; $line = system($cmd, $rc); if ($rc != 0) { @@ -118,9 +139,10 @@ class Html2Pdf { public function outputHtml2Pdf() { $get = $this->readCleanGetParam($_GET); + $urlParam = KeyValueStringParser::unparse($get, '=', '&'); $pageId = Support::setIfNotSet($get, HTML2PDF_PAGEID, 0); - $filename = $this->page2pdf($get); + $filename = $this->page2pdf(TOKEN_URL_PARAM, $urlParam); $this->setHeader('print.' . $pageId . '.pdf'); @readfile($filename); diff --git a/extension/qfq/qfq/report/Link.php b/extension/qfq/qfq/report/Link.php index b8e849dea513893303f46c9e7c27d563a8303330..05f549aed692df523430079c6cc3f3366a4f7b89 100644 --- a/extension/qfq/qfq/report/Link.php +++ b/extension/qfq/qfq/report/Link.php @@ -68,7 +68,9 @@ const NAME_URL = 'url'; const NAME_MAIL = 'mail'; const NAME_PAGE = 'page'; const NAME_TEXT = 'text'; -const NAME_DOWNLOAD = 'download'; +const NAME_DOWNLOAD = DOWNLOAD_EXPORT_FILENAME; +const NAME_DOWNLOAD_ELEMENTS = 'downloadElements'; // array with element sources +const NAME_DOWNLOAD_MODE = 'mode'; const NAME_ALT_TEXT = 'altText'; const NAME_TOOL_TIP = 'toolTip'; const NAME_TOOL_TIP_JS = 'toolTipJs'; @@ -88,6 +90,7 @@ const NAME_URL_PARAM = 'param'; const NAME_RIGHT = 'picturePositionRight'; const NAME_ACTION_DELETE = 'actionDelete'; const NAME_EXTRA_CONTENT_WRAP = 'extraContentWrap'; +const NAME_FILE = 'file'; const FINAL_HREF = 'finalHref'; const FINAL_ANCHOR = 'finalAnchor'; @@ -97,40 +100,6 @@ const FINAL_TOOL_TIP = 'finalToolTip'; const FINAL_CLASS = 'finalClass'; const FINAL_QUESTION = 'finalQuestion'; -const TOKEN_URL = 'u'; -const TOKEN_MAIL = 'm'; -const TOKEN_PAGE = 'p'; -const TOKEN_TEXT = 't'; -const TOKEN_DOWNLOAD = 'd'; -const TOKEN_ALT_TEXT = 'a'; -const TOKEN_TOOL_TIP = 'o'; -const TOKEN_PICTURE = 'P'; -const TOKEN_BULLET = 'B'; -const TOKEN_CHECK = 'C'; -const TOKEN_DELETE = 'D'; -const TOKEN_EDIT = 'E'; -const TOKEN_HELP = 'H'; -const TOKEN_INFO = 'I'; -const TOKEN_NEW = 'N'; -const TOKEN_SHOW = 'S'; -const TOKEN_RENDER = 'r'; -const TOKEN_TARGET = 'g'; -const TOKEN_CLASS = 'c'; -const TOKEN_QUESTION = 'q'; -const TOKEN_ENCRYPTION = 'e'; -const TOKEN_SIP = 's'; -const TOKEN_URL_PARAM = 'U'; -const TOKEN_RIGHT = 'R'; - -const TOKEN_ACTION_DELETE = 'x'; -const TOKEN_ACTION_DELETE_AJAX = 'a'; -const TOKEN_ACTION_DELETE_REPORT = 'r'; -const TOKEN_ACTION_DELETE_CLOSE = 'c'; - -const TOKEN_CLASS_NONE = 'n'; -const TOKEN_CLASS_INTERNAL = 'i'; -const TOKEN_CLASS_EXTERNAL = 'e'; - const LINK_ANCHOR = 'linkAnchor'; const LINK_PICTURE = 'linkPicture'; @@ -189,6 +158,7 @@ class Link { TOKEN_INFO => 'buildInfo', TOKEN_NEW => 'buildNew', TOKEN_SHOW => 'buildShow', + TOKEN_FILE => 'buildFile', ]; private $tableVarName = [ @@ -196,6 +166,7 @@ class Link { TOKEN_MAIL => NAME_MAIL, TOKEN_PAGE => NAME_PAGE, TOKEN_DOWNLOAD => NAME_DOWNLOAD, + TOKEN_DOWNLOAD_MODE => NAME_DOWNLOAD_MODE, TOKEN_TEXT => NAME_TEXT, TOKEN_ALT_TEXT => NAME_ALT_TEXT, TOKEN_TOOL_TIP => NAME_TOOL_TIP, @@ -217,6 +188,7 @@ class Link { TOKEN_URL_PARAM => NAME_URL_PARAM, TOKEN_RIGHT => NAME_RIGHT, TOKEN_ACTION_DELETE => NAME_ACTION_DELETE, + TOKEN_FILE => NAME_FILE, ]; // Used to find double definitions. @@ -305,11 +277,10 @@ class Link { * Build the whole link * * @param string $str Qualifier with params. 'report'-syntax. F.e.: A:u:www.example.com|G:P:home.gif|t:Home" - * @param string $rcHtmlIdModalDownloadDialog - If the link is a download link, set it to true. * @return string The complete HTML encoded Link like <a href='http://example.com' class='external'><img src='iconf.gif' title='help text'>Description</a> * @throws UserReportException */ - public function renderLink($str, &$rcHtmlIdModalDownloadDialog) { + public function renderLink($str) { $tokenGiven = array(); @@ -317,9 +288,13 @@ class Link { return ''; $vars = $this->fillParameter($str, $tokenGiven); - $vars = $this->processParameter($vars); + $vars = $this->processParameter($vars, $tokenGiven); $mode = $this->getModeRender($vars); + if (isset($tokenGiven[TOKEN_DOWNLOAD]) && $tokenGiven[TOKEN_DOWNLOAD] === true) { + $this->store->setVar(SYSTEM_DOWNLOAD_POPUP, DOWNLOAD_POPUP_REQUEST, STORE_SYSTEM); + } + $link = ''; // 0-4 URL, plain email @@ -354,11 +329,9 @@ class Link { case '3': // $link = $htmlUrl . $vars[NAME_URL] . '</a>' . $vars[NAME_TOOL_TIP_JS][1]; $link = Support::wrapTag($vars[FINAL_ANCHOR], $vars[FINAL_HREF]); - $rcHtmlIdModalDownloadDialog = $this->getHtmlIdModalDownloadDialog(); break; case '13': $link = Support::wrapTag($vars[FINAL_ANCHOR], $vars[FINAL_HREF]); - $rcHtmlIdModalDownloadDialog = $this->getHtmlIdModalDownloadDialog(); // $vars[NAME_TEXT] = $vars[NAME_MAIL]; // $link = $this->encryptMailtoJS($vars, true); break; @@ -366,11 +339,9 @@ class Link { // 4: <a href=url ...>Text</a> case '4': $link = Support::wrapTag($vars[FINAL_ANCHOR], $vars[FINAL_CONTENT]); - $rcHtmlIdModalDownloadDialog = $this->getHtmlIdModalDownloadDialog(); break; case '14': $link = Support::wrapTag($vars[FINAL_ANCHOR], $vars[FINAL_CONTENT]); - $rcHtmlIdModalDownloadDialog = $this->getHtmlIdModalDownloadDialog(); // $link = $this->encryptMailtoJS($vars, true); break; case '21': @@ -393,14 +364,14 @@ class Link { */ private function fillParameter($str, array &$tokenGiven) { - // define all possible vars: no more isset() + // Define all possible vars: no more isset(). $vars = $this->initVars(); $flagArray = array(); // str="u:http://www.example.com|c:i|t:Hello World|q:Do you really want to delete the record 25:warn:yes:no" - $param = explode("|", $str); + $param = explode(PARAM_DELIMITER, $str); - // Parse all parameter, fill variables + // Parse all parameter, fill variables. foreach ($param as $item) { // Skip empty entries @@ -422,15 +393,23 @@ class Link { if (!isset($this->tableVarName[$key])) { throw new UserReportException ("Unknown link qualifier: '$key' - do you forget the one character qualifier?", ERROR_UNKNOWN_LINK_QUALIFIER); } + $keyName = $this->tableVarName[$key]; // convert token to name - if ($value === '') { - $value = $this->checkEmptyValue($key); - } + $value = $this->checkForEmptyValue($key, $value); $value = $this->checkValue($key, $value); // Store value - $vars[$keyName] = $value; + if (isset($tokenGiven[TOKEN_DOWNLOAD]) && ($key == TOKEN_URL || $key == TOKEN_URL_PARAM || $key == TOKEN_FILE)) { + + $vars[NAME_DOWNLOAD_ELEMENTS][] = $key . ':' . $value; + + unset($tokenGiven[$key]); // Skip Bookkeeping for TOKEN_URL_PARAM | TOKEN_FILE | TOKEN_URL. + continue; + } else { + $vars[$keyName] = $value; + } + // Check for double anchor or picture definition if (isset($this->tokenMapping[$key])) { @@ -454,7 +433,7 @@ class Link { } // Final Checks - $this->checkParam($tokenGiven); + $this->checkParam($tokenGiven, $vars); return $vars; } @@ -483,6 +462,8 @@ class Link { NAME_TOOL_TIP_JS => '', NAME_URL_PARAM => '', NAME_EXTRA_CONTENT_WRAP => '', + NAME_DOWNLOAD_MODE => '', + NAME_DOWNLOAD_ELEMENTS => array(), NAME_RENDER => '0', NAME_RIGHT => 'l', @@ -509,10 +490,15 @@ class Link { * Verify Empty values. If appropriate, set defaults, if not throw an exception. * * @param string $key + * @param string $value * @return string * @throws UserReportException */ - private function checkEmptyValue($key) { + private function checkForEmptyValue($key, $value) { + + if ($value !== '') { + return $value; + } $value = ''; switch ($key) { @@ -587,7 +573,7 @@ class Link { * @param array $tokenGiven * @throws UserReportException */ - private function checkParam(array $tokenGiven) { + private function checkParam(array $tokenGiven, array $vars) { $countLinkAnchor = 0; $countLinkPicture = 0; @@ -617,18 +603,23 @@ class Link { if (isset($tokenGiven[TOKEN_MAIL]) && isset($tokenGiven[TOKEN_TARGET])) { throw new UserReportException ("Token Mail and Target at the same time not possible'" . TOKEN_PAGE . "'", ERROR_MULTIPLE_DEFINITION); } + + if (isset($tokenGiven[TOKEN_DOWNLOAD]) && count($vars[NAME_DOWNLOAD_ELEMENTS]) == 0) { + throw new UserReportException ("Missing element sources for download", ERROR_MISSING_REQUIRED_PARAMETER); + } } /** * Compute final link parameter. * * @param array $vars + * @param array $tokenGiven * @return string * @throws UserReportException */ - private function processParameter(array $vars) { + private function processParameter(array $vars, array $tokenGiven) { - $vars[FINAL_HREF] = $this->doHref($vars); // must be called before doToolTip() + $vars[FINAL_HREF] = $this->doHref($vars, $tokenGiven); // must be called before doToolTip() $vars[FINAL_TOOL_TIP] = $this->doToolTip($vars); $vars[FINAL_CLASS] = $this->doCssClass($vars); $vars[FINAL_SYMBOL] = $this->doSymbol($vars); @@ -639,16 +630,70 @@ class Link { return $vars; } + /** + * @param array $vars + * @return string + * @throws UserFormException + */ + private function getDownloadModeNCheck(array $vars) { + + $cnt = count($vars[NAME_DOWNLOAD_ELEMENTS]); + $mode = $vars[NAME_DOWNLOAD_MODE]; + + // Determine default. + if ($mode == '') { + if ($cnt == 1) { + $mode = (substr($vars[NAME_DOWNLOAD_ELEMENTS][0], 0, 1) == TOKEN_FILE) ? DOWNLOAD_MODE_FILE : DOWNLOAD_MODE_PDF; + } else { + $mode = DOWNLOAD_MODE_PDF; + } + } + + // Do some checks. + switch ($mode) { + case DOWNLOAD_MODE_PDF: + case DOWNLOAD_MODE_ZIP: + break; + case DOWNLOAD_MODE_FILE: + case DOWNLOAD_MODE_EXCEL: + if ($cnt > 1) { + throw new UserFormException("With 'downloadMode' = 'file' or 'excle', only one element source is allowed.", ERROR_DOWNLOAD_UNEXPECTED_NUMBER_OF_SOURCES); + } + break; + default: + throw new UserFormException("Unknown mode: $mode", ERROR_UNKNOWN_MODE); + break; + + } + + return $mode; + } + /** * Concat final HREF string * * @param array $vars + * @param array $tokenGiven * @return string + * @throws CodeException * @throws UserReportException */ - private function doHref(array &$vars) { + private function doHref(array &$vars, array $tokenGiven) { $urlNParam = ''; + + if (isset($tokenGiven[TOKEN_DOWNLOAD])) { + // Message in download popup. + $altText = ($vars[NAME_ALT_TEXT] == '') ? 'Please wait' : addslashes($vars[NAME_ALT_TEXT]); + $vars[NAME_EXTRA_CONTENT_WRAP] = str_replace(DOWNLOAD_POPUP_REPLACE_TEXT, $altText, $vars[NAME_EXTRA_CONTENT_WRAP]); + $vars[NAME_EXTRA_CONTENT_WRAP] = str_replace(DOWNLOAD_POPUP_REPLACE_TITLE, 'Download: ' . addslashes($vars[NAME_DOWNLOAD]), $vars[NAME_EXTRA_CONTENT_WRAP]); + + $tmpUrlParam[DOWNLOAD_MODE] = $this->getDownloadModeNCheck($vars); + $tmpUrlParam[DOWNLOAD_EXPORT_FILENAME] = $vars[NAME_DOWNLOAD]; + $tmpUrlParam[SIP_DOWNLOAD_PARAMETER] = base64_encode(implode(PARAM_DELIMITER, $vars[NAME_DOWNLOAD_ELEMENTS])); + $vars[NAME_URL_PARAM] = KeyValueStringParser::unparse($tmpUrlParam, '=', '&'); + } + if ($vars[NAME_ACTION_DELETE] !== '') { $vars[NAME_URL_PARAM] = $this->adjustDeleteParameter($vars[NAME_ACTION_DELETE], $vars[NAME_URL_PARAM]); } @@ -667,7 +712,7 @@ class Link { $urlNParam = $paramArray['_url']; if ($this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM) === 'yes') { - $vars[NAME_TOOL_TIP] .= PHP_EOL . PHP_EOL . OnArray::toString($paramArray, ' = ', PHP_EOL, "'"); + $vars[NAME_TOOL_TIP] .= PHP_EOL . PHP_EOL . $this->sip->debugSip($paramArray); } } // Link: MAILTO @@ -1069,13 +1114,20 @@ EOF; */ private function buildDownload($vars, $value) { - $htmlIdModal = $this->getHtmlIdModalDownloadDialog(); + $text = DOWNLOAD_POPUP_REPLACE_TEXT; + $title = DOWNLOAD_POPUP_REPLACE_TITLE; + + $attributes = 'class="btn btn-default" data-toggle="modal" data-target="#qfqModal101" data-backdrop="static" data-keyboard="false" '; + $onClick = <<<EOF + onclick="$('#qfqModalTitle101').text($(this).data('$title')); $('#qfqModalText101').text($(this).data('$text'));" +EOF; $vars[NAME_GLYPH] = GLYPH_ICON_FILE; $vars[NAME_GLYPH_TITLE] = "Download"; $vars[NAME_URL] = API_DIR . '/' . API_DOWNLOAD_PHP; $vars[NAME_LINK_CLASS_DEFAULT] = NO_CLASS; - $vars[NAME_EXTRA_CONTENT_WRAP] = '<button type="button" class="btn btn-default" data-toggle="modal" data-target="#' . $htmlIdModal . '" data-backdrop="static" data-keyboard="false">'; + $vars[NAME_EXTRA_CONTENT_WRAP] = '<button type="button" ' . $attributes . $onClick . '>'; + $vars[NAME_DOWNLOAD] = ($value == '') ? DOWNLOAD_OUTPUT_PDF : $value; // Download ExportFileName return $vars; } @@ -1322,11 +1374,4 @@ EOF; return $vars; } - - /** - * @return string - */ - private function getHtmlIdModalDownloadDialog() { - return 'qfqModal' . $this->ttContentUid; - } } \ No newline at end of file diff --git a/extension/qfq/qfq/report/Report.php b/extension/qfq/qfq/report/Report.php index b632eb596002229a5ec3d5c67863347eb7938000..8cac9aea3655fe137342edfe6dfe257680797a07 100644 --- a/extension/qfq/qfq/report/Report.php +++ b/extension/qfq/qfq/report/Report.php @@ -81,11 +81,6 @@ class Report { private $showDebugInfo = false; - /** - * @var string - will be set as soon as there is at least one download element. All download elements will reference this id. - */ - private $rcHtmlIdModalDownloadDialog = false; - /** * Report constructor. * @@ -481,43 +476,9 @@ class Report { } } - if ($this->rcHtmlIdModalDownloadDialog != '') { - $content .= $this->getModalCode($this->rcHtmlIdModalDownloadDialog); - } - return $content; } - private function getModalCode($htmlId, $title = "Loading Document", $message = "Document is being generated. Please wait.") { - /* - * <!-- Button trigger modal --> - <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#qfqModal101" data-backdrop="static" data-keyboard="false"> - <span class="glyphicon glyphicon-search"></span> - </button> - */ - - $code = <<<EOF - <!-- Modal --> - <div class="modal fade" id="$htmlId" tabindex="-1" role="dialog" aria-labelledby="myModalLabel"> - <div class="modal-dialog" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <h4 class="modal-title" id="myModalLabel">$title</h4> - </div> - <div class="modal-body" style="text-align: center;"> - <span class="glyphicon glyphicon-cog glyphicon-spin text-large-with-margin text-primary"></span> - <p>$message</p> - </div> - <div class="modal-footer"> - <p>In Progress..</p> - </div> - </div> - </div> - </div> -EOF; - - return $code; - } /** * Determine value: * 1) if one specified in line: take it @@ -608,7 +569,7 @@ EOF; //TODO: reserved names,not starting with '_' will be still accepted - stop this! switch ($columnName) { case "link": - $content .= $this->link->renderLink($columnValue, $this->rcHtmlIdModalDownloadDialog); + $content .= $this->link->renderLink($columnValue); break; case "exec": @@ -627,7 +588,7 @@ EOF; $pageColumnName = strtolower($columnName); $tokenizedValue = $this->doFixColPosPage($columnName, $columnValue); $linkValue = $this->doPage($pageColumnName, $tokenizedValue); - $content .= $this->link->renderLink($linkValue, $dummy); + $content .= $this->link->renderLink($linkValue); break; // Lowercase 'P' @@ -640,18 +601,18 @@ EOF; case COLUMN_PAGEN: case COLUMN_PAGES: $linkValue = $this->doPage($columnName, $columnValue); - $content .= $this->link->renderLink($linkValue, $dummy); + $content .= $this->link->renderLink($linkValue); break; case COLUMN_DDOWNLOAD: $tokenizedValue = $this->doFixColPosDownload($columnValue); $linkValue = $this->doDownload($tokenizedValue); - $content .= $this->link->renderLink($linkValue, $this->rcHtmlIdModalDownloadDialog); + $content .= $this->link->renderLink($linkValue); break; case COLUMN_DOWNLOAD: $linkValue = $this->doDownload($columnValue); - $content .= $this->link->renderLink($linkValue, $this->rcHtmlIdModalDownloadDialog); + $content .= $this->link->renderLink($linkValue); break; case "bullet": @@ -661,7 +622,7 @@ EOF; // r:3|B: $linkValue = TOKEN_RENDER . ":3|" . TOKEN_BULLET . ":" . $columnValue; - $content .= $this->link->renderLink($linkValue, $dummy); + $content .= $this->link->renderLink($linkValue); break; case "check": @@ -671,7 +632,7 @@ EOF; // "r:3|C: $linkValue = TOKEN_RENDER . ":3|" . TOKEN_CHECK . ":" . $columnValue; - $content .= $this->link->renderLink($linkValue, $dummy); + $content .= $this->link->renderLink($linkValue); break; case "img": @@ -1100,9 +1061,11 @@ EOF; case TOKEN_SIP: $defaultSip = ''; // if any of the img token is given: no default break; + case TOKEN_DOWNLOAD: $defaultDownload = ''; break; + default: break; } diff --git a/extension/qfq/qfq/store/Sip.php b/extension/qfq/qfq/store/Sip.php index dbbf3d287b37edda4ea8a2ddf9a20b20116f3dcb..c23f203236f32ab1e7496956b71f135364a7d20c 100644 --- a/extension/qfq/qfq/store/Sip.php +++ b/extension/qfq/qfq/store/Sip.php @@ -281,6 +281,32 @@ class Sip { return KeyValueStringParser::parse($sessionVar, "=", "&"); } + /** + * Formats the content of sip. Per variable one line. Decode base64 encoded variables. + * If $vars is an array, iterate over it. + * If $vars is a string, than this is the SIP - retrieve parameter from SIP and process those. + * + * @param string|array $vars + * @return string + * @throws \qfq\UserFormException + */ + public function debugSip($vars) { + + if (!is_array($vars)) { + $vars = $this->getVarsFromSip($vars); + } + + // Detect and decode base64 content + $len = strlen(SIP_PREFIX_BASE64); + foreach ($vars as $key => $value) { + if (substr($key, 0, $len) == SIP_PREFIX_BASE64) { + $vars[$key] = base64_decode($value); + } + } + + return OnArray::toString($vars, ' = ', PHP_EOL, "'"); + } + /** * Returns the sip for the given querystring. The querystring has to be sorted. * diff --git a/extension/qfq/qfq/store/Store.php b/extension/qfq/qfq/store/Store.php index e9575e77b11526b43842e73a444df312236b2597..273031d2c88e9f565de10a440496f9f371e03c51 100644 --- a/extension/qfq/qfq/store/Store.php +++ b/extension/qfq/qfq/store/Store.php @@ -91,8 +91,8 @@ class Store { // self::$session = Session::getInstance(self::$phpUnit); // This check is critical for some unwanted exception recursion during startup. - if(!function_exists('normalizer_normalize')) { - throw new CodeException("Function normalizer_normalize() not found - Please install 'php5-intl' / 'php7.0-intl'", ERROR_MISSING_INTL ); + if (!function_exists('normalizer_normalize')) { + throw new CodeException("Function normalizer_normalize() not found - Please install 'php5-intl' / 'php7.0-intl'", ERROR_MISSING_INTL); } self::$sanitizeClass = [ @@ -212,7 +212,7 @@ class Store { * @param array $config * @return array */ - private function doSystemPath(array $config) { + private static function doSystemPath(array $config) { // SYSTEM_PATH_EXT: compute only if not already defined. if (!isset($config[SYSTEM_PATH_EXT]) || $config[SYSTEM_PATH_EXT] === '' || $config[SYSTEM_PATH_EXT][0] !== '/') { @@ -416,6 +416,8 @@ class Store { $sanitizeClass = isset(self::$sanitizeClass[$key]) ? self::$sanitizeClass[$key] : SANITIZE_DEFAULT; } + $len = strlen(SIP_PREFIX_BASE64); + while ($useStores !== false) { $store = substr($useStores, 0, 1); // next store @@ -454,6 +456,9 @@ class Store { } return \qfq\Sanitize::sanitize($rawVal, $sanitizeClass, '', SANATIZE_EMPTY_STRING); } else { + if ($store == STORE_SIP && (substr($key, 0, $len) == SIP_PREFIX_BASE64)) { + $rawVal = base64_decode($rawVal); + } return $rawVal; } } @@ -668,18 +673,44 @@ class Store { * @throws \qfq\CodeException */ public static function getStore($store) { + $vars = array(); + // Check valid Storename - if (!isset(self::$sanitizeStore[$store])) + if (!isset(self::$sanitizeStore[$store])) { throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE); + } - if ($store === STORE_ZERO) + if ($store === STORE_ZERO) { throw new CodeException("getStore() for STORE_ZERO is impossible - there are no values saved.", ERROR_GET_STORE_ZERO); + } if (isset(self::$raw[$store])) { - return self::$raw[$store]; + $vars = self::$raw[$store]; + if ($store == STORE_SIP) { + $vars = self::checkDecodeBase64Arr($vars); + } } - return array(); + return $vars; + } + + /** + * Iterate over an array and look for keynames starting with SIP_PREFIX_BASE64. + * Found elements will be base64_decode(). + * + * @param array $vars + * @return array - incl. decoded base64 vars. + */ + private static function checkDecodeBase64Arr(array $vars) { + + $len = strlen(SIP_PREFIX_BASE64); + + foreach ($vars as $key => $value) { + if (substr($key, 0, $len) == SIP_PREFIX_BASE64) { + $vars[$key] = base64_decode($value); + } + } + return $vars; } /** @@ -739,7 +770,7 @@ class Store { * * @param string $sipTypo3Vars */ - public function fillTypo3StoreFromSip($sipTypo3Vars) { + public static function fillTypo3StoreFromSip($sipTypo3Vars) { $t3vars = self::getStore(STORE_TYPO3); // Do nothing if STORE_TYPO3 is already filled